MySQL 事务与MVCC
一、事务
1. 定义:事务是数据库管理系统执行过程中的一个逻辑单元,由有限的操作序列构成,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤销。事务的结束有两种,当事务中的所有步骤全部执行成功时,事务提交,如果其中的一个步骤失败,将发生回滚操作,撤销到事务开始时的所有操作。事务是在引擎层实现的。
2. 事务的ACID特性
A. 原子性(Atomicity):事务是数据库的逻辑工作单位,事务中包含的各项操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要全部应用到数据库,如果操作失败则不能对数据库有任何影响,是通过undo log来实现的;
B. 一致性(Consistency):事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态,是通过其他三大特性作用实现的;
C. 隔离性(Isolation):一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间是不能相互干扰的,是通过读写锁+MVCC来实现的;
D. 持久性(Durability):指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的,接下来的其他操作或者故障不应该对执行结果有任何影响,是通过redo log来实现的。
注意: 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大;
大多数的数据库默认隔离级别为Read Commited,例如SqlServer、Oracle,少数数据库默认隔离级别为Repeatable Read,例如MySQL。
二、 数据库的隔离级别
1. 分类
A. Read Uncommitted(读未提交):它充许另外一个事务可以看到这个事务未提交的数据,这是事务最低的隔离级别,隔离级别值为0;
B. Read Committed(读已提交):保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据,隔离级别值为1;
C. Repeatable Read(可重复读):指的是一个事务在执行过程中,看到的数据是和启动时看到的数据是一致的,未提交的更改对其他事务不可见,隔离级别值为2;
D. Serializable(串行化读):事务只能一个一个的按顺序执行,这是事务最高的隔离级别,隔离级别值为3,但执行效率慢,使用时慎重。
2. 隔离级别产生的问题
隔离级别 | 脏读 | 不可重复读 | 幻读 |
Read Uncommitted | √ | √ | √ |
Read Committed | × | √ | √ |
Repeatable Read | × | × | √ |
Serializable | × | × | × |
A. 脏读(Dirty Read):一个事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据,如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据;
B. 不可重复读(Non-Repeatable Read):一个事务中发生了多次读操作,第一次读操作和第N次操作之间,另外一个事务对数据进行了修改,这时候多次读取的数据是不一致的;
C. 幻读(Phantom Read):第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。
三、事务日志
1. undo log
A. 定义:undo log叫回滚日志,用来记录被修改前的信息;
B. 作用:用于系统错误或者rollback操作而回滚时,可以根据undo log的信息来进行回滚到没被修改前的状态;
C. 位置:默认存放在共享表空间ibdata1中。
2. redo log
A. 定义:redo log叫重做日志,用来记录已成功提交事务的修改信息;
B. 作用:用于实现事务的持久性,会把redo log持久化到磁盘,如果系统宕机在重启之后可以读取redo log恢复最新数据;
C. 构成:redo log由重做日志缓冲(redo log buffer)以及重做日志文件(redo log)构成,前者是存储在内存中,后者存储在磁盘中;
D. 位置:默认数据目录下ib_logfile0和ib_logfile1文件。
四、MVCC多版本并发控制
1. 定义:MVCC(Multiversion Concurrency Control)多版本并发控制主要是通过在每一行记录中增加三个字段,与undo log中相关记录配合使用,同时加上可见性算法,使得各个事务可以在不加行锁的情况下能够同时地读取到某行记录上的准确值,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能。主要用在Repeatable Read隔离级别。
2. 实现原理
A. MVCC的实现是通过保存数据在某个时间点的快照来实现的,这意味着一个事务无论运行多长时间,在同一个事务里能够看到的数据是一致的,根据事务开始的时间不同,同时也意味着在同一个时刻不同事务看到的相同表里的数据可能是不同的
B. DB_TRX_ID:长度为6字节,存储了插入或更新该行语句的最后一个事务的事务ID,每处理一个事务,其值自动加1;
C. DB_ROLL_PTR:回滚指针,长度为7字节,回滚指针指向写入回滚段的undo log日志记录,读取记录的时候会根据指针去读取undo log中的记录;
D. DB_ROW_ID:行ID,长度为6字节,该ID在插入新行时会递增,如果InnoDB自动生成聚集索引,该索引包含行ID值,否则该列不会出现在任何索引中。
3. MVCC SELECT原则
A. 只查询事务ID小于等于当前事务id的数据(等于是因为假如自己的事务插入了一条数据,会生成一条当前事务id的数据,所以必须包含本事务自己插入的数据);
B. 只查询未删除(回滚指针为空)或者回滚指针大于当前事务id的数据(不能等于是因为假如自己的事务删除了一条数据,会生成数据的回滚指针为当前事务id,所以必须排除掉自己删除的数据)。
4. MVCC INSERT
A. InnoDB为每个新增行记录当前系统版本号作为创建ID;
5. MVCC DELETE
A. InnoDB为每个删除行的记录当前系统版本号作为行的删除ID;
6. MVCC UPDATE
A. InnoDB复制了一行,这个新行的版本号使用了系统版本号,它也把系统版本号作为了删除行的版本;
B. begin->用排他锁锁定该行->记录redo log->记录undo log->修改当前行的值,写事务编号,回滚指针指向undo log中的修改前的行。
五、幻读问题
1. 解决方式
A. 快照读:基于历史数据而言,只对简单的select操作有效,不包括select ... lock in share mode、select ... for update;
B. 当前读:基于最新数据而言,对insert、update、delete及select ... lock in share mode、select ... for update都有效;
C. 在RR级别下,快照读是通过MVCC和undo log来实现的,当前读是通过加record lock(记录锁)和gap lock(间隙锁)来实现的。
可参考:MySQL的四种事务隔离级别