脏读:脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
不可重复读:不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
幻读:事务1查询某个范围的数据时,事务2在该范围内插入新的数据(或者删除),事务1再次查询该范围时(未必是和之前同样的查询语句),返回不同的结果集。
MySQL数据库为我们提供的四种隔离级别:
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
③ Read committed (读已提交):可避免脏读的发生。
④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
实际上,mysql在可重复读级别下,利用MVCC(多版本并发控制)及临键锁等在一定程度上解决了幻读的问题(一部分幻读的情况)。
(1)数据结构
在Mysql中MVCC是在Innodb存储引擎中得到支持的,Innodb为每行记录都实现了三个隐藏字段:
6字节的事务ID(DB_TRX_ID )
7字节的回滚指针(DB_ROLL_PTR)
隐藏的ID
6字节的事物ID用来标识该行所述的事务,7字节的回滚指针需要了解下Innodb的事务模型。
(2)事务日志
为了支持事务,Innbodb引入了下面几个概念:
redo log:redo log就是保存执行的SQL语句到一个指定的Log文件,当Mysql执行recovery时重新执行redo log记录的SQL操作即可。当客户端执行每条SQL(更新语句)时,redo log会被首先写入log buffer;当客户端执行COMMIT命令时,log buffer中的内容会被视情况刷新到磁盘。redo log在磁盘上作为一个独立的文件存在,即Innodb的log文件。
undo log:与redo log相反,undo log是为回滚而用,具体内容就是copy事务前的数据库内容(行)到undo buffer,在适合的时间把undo buffer中的内容刷新到磁盘。undo buffer与redo buffer一样,也是环形缓冲,但当缓冲满的时候,undo buffer中的内容会也会被刷新到磁盘;与redo log不同的是,磁盘上不存在单独的undo log文件,所有的undo log均存放在主ibd数据文件中(表空间),即使客户端设置了每表一个数据文件也是如此。
rollback segment:回滚段这个概念来自Oracle的事物模型,在Innodb中,undo log被划分为多个段,具体某行的undo log就保存在某个段中,称为回滚段。可以认为undo log和回滚段是同一意思。
下面演示下事务对某行记录的更新过程:
1. 初始数据行
F1~F6是某行列的名字,1~6是其对应的数据。后面三个隐含字段分别对应该行的事务号和回滚指针,假如这条数据是刚INSERT的,可以认为ID为1,其他两个字段为空。
2.事务1更改该行的各字段的值
当事务1更改该行的值时,会进行如下操作:
用排他锁锁定该行
记录redo log
把该行修改前的值Copy到undo log,即上图中下面的行
修改当前行的值,填写事务编号,使回滚指针指向undo log中的修改前的行
3.事务2修改该行的值
与事务1相同,此时undo log,中有有两行记录,并且通过回滚指针连在一起。
因此,如果undo log一直不删除,则会通过当前记录的回滚指针回溯到该行创建时的初始内容,所幸的时在Innodb中存在purge线程,它会查询那些比现在最老的活动事务还早的undo log,并删除它们,从而保证undo log文件不至于无限增长。
Innodb的实现实际上并不是纯粹的MVCC,因为并没有实现核心的多版本共存,undo log中的内容只是串行化的结果,记录了多个事务的过程,不属于多版本共存。但理想的MVCC是难以实现的,当事务仅修改一行记录使用理想的MVCC模式是没有问题的,可以通过比较版本号进行回滚;但当事务影响到多行数据时,理想的MVCC据无能为力了。
这里有一点要注意的是,在MVCC模式下,只有进行读操作才能保证不出现幻读的情况(因为对数据进行了快照),如果需要对数据进行更新、插入、删除操作,这些操作会转为当前读。特别是如果这些操作在快照数据范围内,有可能导致快照数据被更新(出现别的事务的操作结果)。比如事务T1读取出某行,事务T2对该行的字段2进行了更新并提交,事务T1再对该行的字段1进行更新。此时事务T1再读取该行,不仅会展示字段1更新后的结果,还会展示字段2更新的结果。
临键锁其实是行锁和间隙锁的组合。
Record Lock(行锁): 在索引上对单行记录加锁.
Gap Lock(间隙锁): 锁定一个范围的记录,但不包括记录本身.锁加在未使用的空闲空间上,可能是两个索引记录之间,也可能是第一个索引记录之前或最后一个索引之后的空间.
Next-Key Lock(临键锁): 行锁与间隙锁组合起来用就叫做Next-Key Lock。锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。
对于临键涣,准确的理解是,当隔离级别是可重复读,且禁用innodb_locks_unsafe_for_binlog的情况下,在搜索和扫描index的时候使用的next-key locks可以避免幻读。
上图是一个临键锁的简单示例,它不仅会锁住相应的行记录,还会锁住相应的范围。这里有一点要注意的是,仅在非唯一索引下等值查询会做如下处理。如果命中的是唯一索引,等值查询会只锁定相应的行,范围查询才会锁定相应的间隙锁和行锁。通过获得相应的间隙锁及行锁,临键锁可以避免其它事务对锁范围内的记录进行操作(更新、插入、删除)从而避免锁定范围内的幻读。
从以上理解可以看出,临键锁要避免幻读,需要在相应的记录上加上相应的行锁和间隙锁。如果事务T1的查询条件下,并没有相应的行及范围,那么也就无法对相应的索引行(非唯一索引)及记录行加锁。此时,事务T2在该条件下插入记录,事务T1再进行该条件的查询,则可以看到新插入的记录(幻读)。
相关资料收集
https://blog.csdn.net/luzhensmart/article/details/88134189
https://blog.csdn.net/ymybxx/article/details/80496313
https://blog.csdn.net/ashic/article/details/53735537
https://blog.csdn.net/chen77716/article/details/6742128