关系型数据库事务一:概念

2021年11月20日 阅读数:3
这篇文章主要向大家介绍关系型数据库事务一:概念,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

笔者在写上一篇文章Java并发简介 中脑子里面同时也闪烁着,程序中有并发问题,那数据库中也有相似问题吗? 让咱们一块儿看一下吧!html

事务是将一组读写操做组合在一块儿造成一个逻辑单元。这些操做要么所有执行成功提交(commit),要么所有停止失败(abort,rollback),不会留下一个中间状态的烂摊子。因此,失败后程序能够安全的重试,分析缘由等。 相反,若是没有对事务的支持,数据库可能持久化不少中间状态,留下没法解释的业务,开发人员处理起来也很麻烦。因此,事务是为了简化编程,提供数据安全/正确性/一致性。固然,任何便利都是有代价的,事务也有一些问题,因此NoSQL数据库,分布式数据库在某种程度上会弱化事务。有些甚至彻底放弃事务。Let's dig into most of the aspects of transaction!java

ACID特性

谈到事务,都想到ACID。每一个字母分别表明原子性(Atomicity),一致性(Consistency),隔离性(Isolation),持久性 (Durability)。搞清楚了ACID,就至关于搞清楚了事务的精髓。数据库

原子性(Atomicity)

和JAVA并发中的原子性不一样,程序中的原子性表明被规定的原子操做的中间状态不会被别的并发线程看到。而这里的原子性更多的是表达失败和成功的结果。并发问题是在隔离性(Isolation)里面谈及的。当有屡次写入的时候,中间可能会出现各类问题(进程崩溃,断电,网络故障,硬盘满,违反约束等),若是这些操做被分配到一个事务中, 那么提交(commit)动做会失败,事务随即停止,可是数据库会保证故障以前对系统作的任何修改,写入都被撤销。随后,能够安全地重试失败的事务。 若是没有原子性保障,而把这种失败处理交给开发人员或者客户端处理,那么将是很是困难的,客户端很难知道哪些被写入了数据库,而重试则更是雪上加霜,让错误进一步扩大。编程

一致性(Consistency)

有人认为,一致性是强加在ACID里凑数的。由于这个东西不是数据库要保证的,而是应用程序须要定义和关注的。有一些道理。
咱们常常认为,主外键约束达成了某种一致性,你不能在子表里面插入父表没有的键值,这是数据库给保证了一致性,可是这种一致性也是根据业务由开发人员定义的。若是你向数据库插入违反业务逻辑的假数据,数据库并无这种约束阻止你。因此,一致性是经过事务的其余特性(原子性,隔离性)达成的,它并不属于数据库和事务的属性。安全

隔离性(Isolation)

当多个客户端同时访问数据库的同一对象时,就会有并发问题,或者叫竞态条件(race condition)。下图1的简单计数器例子说明了此问题,当两个客户端同时给计数器加1的时候,咱们指望的结果如同他们串行化完成同样,但是在并发环境中没有一些保证的话,结果会像是丢失更新(lost update)同样,实际只增长了一次。
网络

隔离性保证同时执行的事务是相互隔离的,它们不能互相影响。简言之,一个事务只能看到另外一个事务开始以前或者结束以后的结果,不能看到任何中间状态,反之亦然。结果就如同他们串行化(Serializability)完成同样,尽管实际上它们是并发运行的。隔离性分好几种级别,每一种隔离级别都在权衡性能和某种安全保障,This is a kind of trade-off. 咱们在下一篇文章会分析这些隔离级别。多线程

持久性(Durability)

持久性保证当用户提交事务并完成后,数据最终会被永久安全地保存到磁盘中,而不论是否发生故障或者系统崩溃。在单节点的数据库中,经过日志,能够保证系统在崩溃以后起来,依然可以自动完成一致性和持久化的要求(rollforward,rollback)。经过归档日志,当磁盘损坏后,还能恢复到某个时间点。为了进一步保证持久性,对日志和归档日志能够进行多副本设置。并发

固然,没有完美的持久性,若是因为机房起火,全部数据(备份,日志等)都销毁。因此,更严格的保证能够经过异地备份实现,但也不是完美的,不抬杠了。分布式

单对象和多对象操做

咱们举例子来讲明一下事务在单对象和多对象操做中的做用。性能

多对象操做

下图(图2)是一个关于未读消息数量的例子,当用户有新邮件时,则会使相应的计数器加1,用户看了邮件后,则计数器减1。 消息和计数器为两个不一样对象。咱们假设初始状态没有新邮件,计数器为0。 若是没有事务的话,用户2看到了本身有一份新邮件,可是未读邮件数量倒是0. 由于用户2看到了用户1未提交的写入(插入的新邮件),称做脏读。事务的隔离性能够解决这种问题。

若是新邮件到来时,用户1在更新计数器的时候,发生某种崩溃而失败,那么邮箱和计数器则会不一致,失去同步(见图3)。 事务的原子性能够保证:若是计数器更新失败,事务会停止,插入的新邮件会被撤销(回滚). 介于BEGIN TRANSACTION 和 COMMIT之间的代码被认为在同一事务之中。

另外一方面,许多非关系数据库并无将多个操做组合一块儿的方法,即便存在表面的多对象API(例如,键值存储可能具备在一个操做中更新多个键的操做),但并不意味它具备事务的特性,该操做可能在一些键上更新成功,在其余键上失败,这种部分更新也就体如今了数据库端。

单对象写入

对于单对象的写入,原子性和隔离性是毋庸置疑的,若是你正在向数据库写入一个20KB的JSON文档:

  • 若是在发送第一个10KB以后网络中断,数据库是否存储不可解析的10KB JSON片断?
  • 若是在覆盖前一个文档的过程当中断电,是否最终将新旧值拼在一块儿?
  • 若是另外一个客户端在写入的过程当中读取文档,是否看到部分更新的值?

这些问题很是使人困惑,因此存储引擎几乎都实现了:对单节点上的单个对象(好比键值对)上提供原子性和隔离性。原子性能够经过日志来实现崩溃恢复,经过每一个对象上的锁来实现隔离(每次只容许一个线程访问对象)。Apache HBase就是一个例子。

一些数据库也提供了复杂一些的原子操做,如自增操做(定义的自增主键)。还有CAS操做(比较和设置),当值没有被其余人修改过期,才容许执行写操做。这些保证颇有用,防止在多客户端同时写入一个对象时丢失更新。但它们不是一般意义上的事务。这些单一对象保证被称做“轻量级事务”,甚至出于营销目的被称为“ACID”,是有误导性的。事务一般被理解为:将多个对象上的多个操做合并为一个执行单元的机制。   

仔细想一想,若是从事务的角度去理解多线程编程中的计数器自增问题,更多的不是由于没有原子性,而是由于没有隔离性,因此才靠同步(锁),volatile等机制。本质上,都是同样的(并发引发的问题)。

在你的应用中,单对象的保证是否足够,多对象操做的协同是否必须?  咱们不能简单地实现功能来交差,更重要的是要正确地实现。错误虽然不可避免,但许多软件开发人员倾向于只考虑乐观状况,而不是错误处理的复杂性。

事务停止重试

以前咱们说过,事务停止会回滚事务开始到停止以前的写入,所以能够安全地重试,但也不够完美

  • 若是事务执行成功,可是因为网络故障,形成成功消息没有被返回给客户端(客户端认为事务没有成功),那么事务会被致使执行两次。(经过应用排除)
  • 若是错误是因为负载过大形成,重试会将问题变得更糟糕。能够限制重试次数,并单独处理与过载相关的错误。
  • 在发生永久性错误(例如违法约束)后重试是毫无心义的。仅在临时性错误(死锁,网络故障等)后才重试。
  • 若是事务对外部系统有影响,好比发送邮件。 重试则极可能形成重复给用户发邮件

 下一节咱们讨论隔离级别

上一篇: 二叉树详解
下一篇: EM算法理解