MysqlMVCC原理和幻读解决
Mysql MVCC原理和幻读解决
1、MVCC全称(Multi-Version Concurrency Control),即多版本并发控制,主要是为了提⾼数据库的并发性能,解决幻读问题。
2、快照读、当前读
快照读:顾名思义,就是读取的是快照数据,不加锁的普通select都是快照读
当前读:就是读取最新数据,⽽不是历史数据,或者说不是快照数据,是加锁的select,或者对数据进⾏正删改都会进⾏当前读。
3、MVCC解决问题:
并发读-写场景:可以做到读操作不阻塞写操作,同时写操作也不会阻塞读操作。
解决脏读、幻读、不可重复读等事务隔离问题。但不能解决并发写-写场景问题。
4、MVCC原理
实现原理主要是版本链。undo⽇志、ReadView来实现的。
InnoDB 存储引擎,表中的聚簇索引都包含三个隐藏列(row_id、trx_id、roll_pointer)。
row_id:创建的表中有主键或者⾮NULL的unique键时都不会包含row_id列。
trx_id:事务ID,每次⼀个事务对某条聚簇索引记录进⾏改动时,都会把该事务的事务id赋值给trx_id隐藏列。
roll_pointer:回滚指针,每次对某条聚簇索引记录进⾏改动时,都会把旧的版本写⼊到undo⽇志中,然后这个隐藏列就相当于⼀个指针,可以通过它来到该记录修改前的信息。
版本链:每次对记录进⾏改动,都会记录⼀条 undo ⽇志,每条 undo ⽇志也都有⼀个 roll_pointer 属性(INSERT 操作对应的 undo ⽇志没有该属性,因为该记录并没有更早的版本),可以将这些 undo ⽇志都连起来,串成⼀个链表。版本链的头节点就是当前记录最新的值。
5、undo⽇志
undo log 主要⽤于记录数据被修改之前的⽇志,在表信息修改之前先会把数据拷贝到undo log⾥。当事务进⾏回滚时可以通过 undo log ⾥的⽇志进⾏数据还原。
⽤途:
保证事务进⾏rollback时的原⼦性和⼀致性,当事务进⾏回滚的时候可以⽤undo⽇志的数据进⾏恢复。
⽤于MVCC快照读的数据。在MVCC多版本控制中,通过读取undo⽇志的历史版本数据可以实现不同事务版本号都拥有⾃⼰独⽴的快照数据版本。
类别:
insert undo log
代表事务在insert新记录时产⽣的undo log , 只在事务回滚时需要,并且在事务提交后可以被⽴即丢弃。
update undo log
事务在进⾏ update 或 delete 时产⽣的 undo log,不仅在事务回滚时需要,在快照读时也需要。所以不能随便删除,只有在快速读或事务回滚不涉及该⽇志时,对应的⽇志才会被 purge 线程统⼀清除。
6、ReadView
改动的记录都存在在 undo ⽇志中,那如果⼀个⽇志需要查询⾏记录,需要读取哪个版本的⾏记录呢?
对于使⽤ READ UNCOMMITTED 隔离级别的事务来说,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了。
对于使⽤SERIALIZABLE隔离级别的事务来说,InnoDB 使⽤加锁的⽅式来访问记录,不存在并发问题。
⽽对于使⽤READ COMMITTED和REPEATABLE READ隔离级别的事务来说,都必须保证读到已经提交了的事务修改过的记录,也就是说假如另⼀个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的。
核⼼问题就是:READ COMMITTED和REPEATABLE READ隔离级别在不可重复读和幻读上的区别在哪⾥?这两种隔离级别对应的不可重复读与幻读问题都是指同⼀个事务在两次读取记录时出现不⼀致的情况,这两种隔离级别关键是需要判断版本链中的哪个版本是当前事务可见的。
ReadView 就是⽤来解决这个问题的,可以帮助我们解决可见性问题。事务进⾏快照读操作的时候就会产⽣ Read View,它保存了当前事务开启时所有活跃的事务列表(这⾥的活跃指的是未提交的事务。)
每⼀个事务在启动时,都会⽣成⼀个 ReadView,⽤来记录⼀些内容,ReadView 中主要包含 4 个⽐较重要的属性:
m_ids:事务id列表,⽣成 ReadView 时当前系统中活跃的读写事务的事务 id 列表。
min_trx_id:最⼩事务id,⽣成 ReadView 时当前系统中活跃的读写事务中最⼩的事务 id 也就是 m_ids 中的最⼩值。
sql 嵌套max_trx_id:下⼀个事务id,⽣成 ReadView 时系统中应该分配给下⼀个事务的 id 值。
creator_trx_id:当前ReadView所属事务id,⽣成该 ReadView 的事务的事务 id,指定当前的 ReadView 属于哪个事务。
其中,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 之后,在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
trx_id = creator_trx_id,可访问
意味着当前事务在访问它⾃⼰修改过的记录,所以该版本可以被当前事务访问。
trx_id < min_trx_id,可访问
表明⽣成该版本的事务在当前事务⽣成 ReadView 前已经提交,所以该版本可以被当前事务访问。
trx_id >= max_trx_id,不可访问
表明⽣成该版本的事务在当前事务⽣成 ReadView 后才开启,所以该版本不可以被当前事务访问。
min_trx_id <= trx_id < max_trx_id,存在m_ids列表中不可访问
如果在,说明创建 ReadView 时⽣成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时⽣成该版本的事务已经被提交,该版本可以被访问。
某个版本的数据对当前事务不可见
数据编程是干什么的如果某个版本的数据对当前事务不可见的话,那就顺着版本链到下⼀个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后⼀个版本。如果最后⼀个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。
退出sqlite3命令
在 MySQL 中,READ COMMITTED 和 REPEATABLE READ 隔离级别的的⼀个⾮常⼤的区别就是它们⽣成 ReadView 的时机不同。7、MVCC下的幻读
幻读是⼀个事务按照某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录,⽽这个记录来⾃另⼀个事务添加的新记录,也就是说幻读是指新插⼊的⾏。
在 REPEATABLE READ 隔离级别下,事务 A 第⼀次执⾏普通的 SELECT 语句时⽣成了⼀个 ReadView(且在 RR 下只会⽣成⼀个RV),之后事务 B 向 user 表中新插⼊⼀条记录并提交。
ReadView 并不能阻⽌事务 A 执⾏ UPDATE 或者 DELETE 语句来改动这个新插⼊的记录(由于事务 B 已经提交,因此改动该记录并不会造成阻塞),但是这样⼀来,这条新记录的 trx_id 隐藏列的值就变成了事务 A 的事务 id。之后 A 再使⽤普通的 SELECT 语句去查询这条记录时就可以看到这条记录了,也就可以把这条记录返回给客户端。
因为这个特殊现象的存在,我们也可以认为 MVCC 并不能完全禁⽌幻读。
8、解决幻读问题
我们知道数据库的读操作分为当前读和快照读,⽽在 RR 隔离级别下,MVCC 解决了在快照读的情况下的幻读,⽽在实际场景中,我们可能需要读取实时的数据,⽐如在银⾏业务等特殊场景下,必须是
需要读取到实时的数据,此时就不能快照读。
毫⽆疑问,在并发场景下,我们可以通过加锁的⽅式来实现当前读,⽽在 MySQL 中则是通过Next-Key Locks来解决幻读的问题。(关于MySQL 中的锁的介绍可以看看这篇⽂章:)。
Next-Key Locks包含两部分:记录锁(⾏锁,Record Lock),间隙锁(Gap Locks)。记录锁是加在索引上的锁,间隙锁是加在索引之间的。
Record Lock
记录锁,单条索引记录上加锁。
Record Lock 锁住的永远是索引,不包括记录本⾝,即使该表上没有任何索引,那么innodb会在后台创建⼀个隐藏的聚集主键索引,那么锁
住的就是这个隐藏的聚集主键索引。
记录锁是有 S 锁和 X 锁之分的,当⼀个事务获取了⼀条记录的 S 型记录锁后,其他事务也可以继续获取该记录的 S 型记录锁,但不可以继续获取 X 型记录锁;当⼀个事务获取了⼀条记录的 X 型记录锁后,其他事务既不可以继续获取该记录的 S 型记录锁,也不可以继续获取 X 型记录锁。
Gap Locks
间隙锁,对索引前后的间隙上锁,不对索引本⾝上锁。前开后开区间。
MySQL 在 REPEATABLE READ 隔离级别下是可以解决幻读问题的,解决⽅案有两种。
1. 可以使⽤ MVCC ⽅案解决
2. 也可以采⽤加锁⽅案解决(间隙锁)。
但是在使⽤加锁⽅案解决时有问题,就是事务在第⼀次执⾏读取操作时,那些幻影记录尚不存在,我们⽆法给这些幻影记录加上记录锁。所以我们可以使⽤间隙锁对其上锁。
索引对间隙锁会产⽣什么影响?
1. 对主键或唯⼀索引,如果当前读时,where 条件全部精确命中(=或in),这种场景本⾝就不会出现幻读,所以只会加⾏锁,也就是说间
隙锁会退化为⾏锁(记录锁)。
2. ⾮唯⼀索引列,如果 where 条件部分命中(>、<、like等)或者全未命中,则会加附近间隙锁。例如,
某表数据如下,⾮唯⼀索引
2,6,9,9,11,15。如下语句要操作⾮唯⼀索引列 9 的数据,间隙锁将会锁定的列是(6,11],该区间内⽆法插⼊数据。
3. 对于没有索引的列,当前读操作时,会加全表间隙锁,⽣产环境要注意。
mysql怎么读英语
app用户登录界面Next-Key Locks
next-key locks 是索引记录上的⾏锁和索引记录之前的间隙锁的组合,包括记录本⾝,每个 next-key locks 是前开后闭区间(同样说明锁住的范围更⼤,影响并发度),也就是说间隙锁只是锁的间隙,没有锁住记录⾏,next-key locks 就是间隙锁基础上锁住右边界⾏数据。
对于可重复读默认使⽤的就是next key lock,但是对于“唯⼀索引”,⽐如主键的索引,next key lock会降级成⾏锁,⽽不会锁住⼀个区间。因此,如果上⾯的事务1的update使⽤的是主键,事务2也使⽤主键进⾏插⼊,那么实际上事务2根本不会被阻塞,可以⽴即插⼊并返回。⽽对于⾮唯⼀索引,next key lock则不会降级。
9、结论:
MySQL InnoDB的可重复读并不保证避免幻读,需要应⽤使⽤加锁读来保证。⽽这个加锁读使⽤到的机制就是next-key locks。
Read Committed隔离级别:每次select都⽣成⼀个快照读。
Read Repeatable隔离级别:开启事务后第⼀个select语句才是快照读的地⽅,⽽不是⼀开启事务就快照读。
在RR级别下,快照读是通过MVVC(多版本控制)和undo log来实现的,当前读是通过加record lock(记录锁)和gap lock(间隙锁)来实现的。
在mysql中,提供了两种事务隔离技术,第⼀个是mvcc,第⼆个是next-key技术。这个在使⽤不同的语句的时候可以动态选择。不加lock inshare mode之类的快照读就使⽤mvcc。否则当前读使⽤next-key。mvcc的优势是不加锁,并发性⾼。缺点是不是实时数据。codeblocks使用教程
next-key的优势是获取实时数据,但是需要加锁。
在rr级别下,mvcc完全解决了重复读,但并不能真正的完全避免幻读,只是在部分场景下利⽤历史数据规避了幻读
要完全避免幻读,需要⼿动加锁将快照读调整为当前读(mysql不会⾃动加锁),然后mysql使⽤next-key完全避免了幻读,⽐如rr下,锁1(0,2,3,4),另⼀个线程的insert 3即被阻塞,在rc下,另⼀个线程仍然可以⼤摇⼤摆的插⼊,如本线程再次查询⽐如count,则会不⼀致。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。