事务的四大特性
- 原子性
数据库执行的最小单位,要么都执行,要么都不执行
例:张三给李四转账10元
李四账户增加10元,张三账户减少十元,要么都执行成功,要么都失败(回滚)
- 隔离性
两个事务的操作,不能相互影响
例:张三给李四转20元,王五给孙六转10元。
两个事务互不干扰,不能张三的钱转给孙六。
- 一致性
事务前后,数据总额一致
例:张三转给李四40元
张三和李四的总账户余额不变。
原子性和隔离性保证一致性
- 持久性
事务执行后存入数据库,即写入磁盘,对数据的改变是永久的。
事务的隔离级别
-
(READ UNCOMMITTED)读未提交
一个事务可以读到其他事务还没有提交的数据,会出现脏读。 -
(READ COMMITTED)读已提交
一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值,会出现不可重复读、幻读。 -
(REPEATABLE READ)可重复读
一个事务第一次读过某条记录后,即使其他事务修改了该记录的值并且提交,该事务之后再读该条记录时,读到的仍是第一次读到的值,而不是每次都读到不同的数据,这就是可重复读,这种隔离级别解决了不可重复,但是
还是会出现幻读。 -
(SERIALIZABLE)串行化
不允许读-写、写-读的并发操作,可以读-读操作
因为对同一条记录的操作都是串行的,所以不会出现脏读、不可重复读,幻读等现象。
注意
这四种隔离级别是SQL的标准定义。MYSQL默认级别是repeatable read ,且不会出现幻读。
MySQL通过MVCC实现事务控制
在介绍MVCC前,先介绍一下两个概念 版本链和ReadView
版本链
对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列( row_id并不是必要的,我们创建的表中有主键或者非NULL唯一键时都不会包含row_id列):
trx_id:每次对某条记录进行改动时,都会把对应的事务id赋值给trx_id隐藏列。
roll_pointer:每次对某条记录进行改动时,这个隐藏列会存一个指针,可以通过这个指针找到该记录修改前的信息
ReadView
对于使用READ UNCOMMITTED隔离级别的事务来说,直接读取记录的最新版本就好了,对于使用SERIALIZABLE隔离级别的事务来说,使用加锁的方式来访问记录。对于使用READ COMMITTED和REPEATABLE READ隔离级别的事务来说,就需要用到我们上边所说的版本链了,核心问题就是:需要判断一下版本链中的哪个版本是当前事务可见的。
ReadView中主要包含4个比较重要的内容:
- m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
- min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值。
- max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。
- creator_trx_id:表示生成该ReadView的事务的事务id。
注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1, 2, 3这三个事务,之
后id为3的事务提交了。那么一个新的读事务在生成ReadView时, m_ids就包括1和2, min_trx_id的值就是1,
max_trx_id的值就是4。
有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
1)如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
2)如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
3)如果被访问版本的trx_id属性值大于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
4)如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问
什么是MVCC
MVCC(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SEELCT操作时访问记录的版本链的过程。可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。READ COMMITTD、REPEATABLE READ这两个隔离级别的一个很大不同就是:生成ReadView的时机不同,READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView就好了。