1. 什么是事务?
- 原子性(Atomicity):事务是最小的执行单位,是不允许被分割的,事务中的操作要么全都执行,要么全都不执行,不会出现部分执行、部分不执行的情况。如果在执行过程中出现了错误,会被回滚到事务开启前的状态,整体看上去好像没有执行任何操作。
- 一致性(Consistency):事务会使得数据库从一个一致性的状态转移到另一个一致性的状态,一致性与原子性密不可分。
- 隔离性(Isolation):通常来说,并发访问数据库时,一个事务不会被其他事务所干扰,这个事务也不能够干扰其他事务。一个事务在提交之前,对其他事务是不可见的。
- 持久性(Durability):事务一旦提交,他对数据库中存储的数据的改变是永久性的,接下来的操作甚至是故障不会对其造成影响。
- 在并发情况下,这些特性不一定会完全满足
- 数据库在进行持久化存储时并不会直接对数据进行修改,而是使用“事务日志”,存储引擎在修改表的数据时只修改其内存拷贝,再把这种修改行为记录到持久在硬盘上的事务日志中,事务日志持久之后,内存中的被修改的数据可以慢慢刷回到磁盘,这叫做“预写式日志”,修改数据需要写两次磁盘。
3. 并发事务会带来的问题?
- 脏读(Dirty read):当一个事务正在对数据库进行修改,但是这种修改还没有进行提交,这时另一个事务对这些数据进行了访问,然后使用了这些数据。由于这些数据最终不一定会真正提交到数据库中,所以第二个事务读取到的数据叫做“脏数据”,依据这些数据所做的操作可能是不正确的。
- 丢失修改(Lost to Modify):两个事务几乎同时读取了该条数据,并对其进行了修改,这样先提交的事务所做的修改可能会丢失。例如:数据库中存储一条数据
num = 0,事务一二都读取到num为0,事务一修改num = num + 1,并且先提交,事务二再进行修改num = num + 2,后进行提交,如此操作完后数据库中存储的num = 2,也就是说事务一所做的修改被丢失了,数据库的一致性受到了影响。 - 不可重复读(Unrepeatableread):在一个事务中不能第二次读取数据,或者说一个事务中两次对数据读取的结果是不相同的。在某一个时间,事务一访问数据,在事务一执行事务的同时,事务二对这段数据进行修改并成功提交,这样事务一再进行读取时获取到的数据与第一次读取到的数据存在差异。
- 幻读(Phantom):与不可重复读类似,在事务一成功进行第一次读取并进行处理,但是还未提交时,事务二对段数据进行了添加或者删除并成功提交,事务一第二次读取时获得的数据相对于第一次读取获得的记录条数不一致,好像产生了幻觉,所以将其称作幻读。
- 读未提交(READ UNCOMMITTED):事务中未提交的数据对于其他事务是可见的。很少使用
- 读提交/不可重复读(READ COMMITTED):一个事务在提交之前所做的修改对于其他事务不可见。
- 可重复读(REPEATABLE READ):MySQL默认的隔离级别。
- 可串行化(SERIALIZABLE):最高的隔离级别,强制事务串行执行,避免了幻读但是会导致加锁读,可能会超时和锁争用。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 | | :—-: | :—-: | :—-: | :—-: | :—-: | | 读未提交 | √ | √ | √ | | | 读提交 | | √ | √ | | | 可重复读 | | | √ | | | 可串行化 | | | | √ |
- 事务的隔离级别与数据库的并发性能是相矛盾的,隔离级别越高,并发性能越差。
可串行化虽可以解决幻读问题,但其并发性能过差,只有在非常需要确保数据的一致性且可以接受没有并发的情况下才会使用。InnoDB采用多版本并发控制(MVCC)技术解决了可能产生的幻读问题。
5. MySQL中的事务
MySQL提供了InnoDB和NDB Cluster两种事务型引擎。
- MySQL默认采用自动提交模式,当没有显式地开启一个事务时,每个查询都被当作是一个事务。
在同一个事务中使用多种不同的存储引擎时不可靠的,因为当事务出现错误需要回滚时,使用非事务型引擎(如MyISAM)存储的表上的修改时无法撤销的。
6. 多版本并发控制(MVCC)
不同的存储引擎实现MVCC的方式不太一样,典型的有乐观并发控制和悲观并发控制。InnoDB中的MVCC是乐观锁的一种实现方式
- 要注意的是,MVCC只能工作在读提交和可重复读两个隔离级别,在读未提交中总是会读取最新的行而不是符合事务版本的行,而可串行化中会对所有读取的行加锁。
在InnoDB中,MVCC的实现方法时通过在每一行子路后面保存两个隐藏的列来实现的。这两个列一个保存了行的创建时间,一个保存了行的过期(或删除)时间。这里所说的时间实际上是一个系统版本号,系统版本号会在每次开启新的事务时递增。下面说明在REPEATABLE READ隔离级别下,MVCC是如何工作的。
SELECT
- InnoDB会根据以下两个条件查询
- InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号要小于等于事务的系统版本号),这样可以确保事务读取的行是事务开启之前已经存在的,要么是事务自身插入或者修改过的。
- 行的删除版本要么未定义,要么大于当前事务版本号,这样可以保证事务读取到的行在事务开始之前还没有被删除。
- InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号要小于等于事务的系统版本号),这样可以确保事务读取的行是事务开启之前已经存在的,要么是事务自身插入或者修改过的。
INSERT
- InnoDB为新插入的行保存当前的系统版本号作为行版本号
DELETE
- InnoDB为删除的每一行保存当前系统版本号作为行删除标志
UPDATE
- InnoDB为插入一行新记录,保存当前的系统版本号作为行版本号,同时保存当前的版本号到原来的行作为行删除标志
