你好,是我琉忆。我们上一节聊了锁,这次我们聊聊脏读和不可重复读。这个都是面试中爱问的知识点。所以本次的知识点记得捡起来哦!
一般是通过锁机制,解决掉不可重复读和幻读的问题。是不是可以通过乐观锁的问题去解决不可重复读和幻读的问题,MySQL 采用的是 MVCC 机制来解决脏读、不可重复读的问题。
MVCC 英文全称是 Muitiversion Concurrency Control,多版本并发控制技术,原理是通过数据行的多个版本管理实现数据库的并发控制,通过保存数据的历史版本,可以通过比较版本号决定数据是否显示,读取数据的时候不需要加锁保证事务的隔离效果。
MVCC 是如何解决脏读的?
MVCC 解决了一致性读问题,当我们读取某个数据库在时间点的快照时,只能看到时间点之前提交更新的结果,不能看到时间点之后事务提交的更新结果。这样就避免了脏读问题。
MVCC 是如何解决不可重复读的?
在说如何解决不可重复读之前,先谈谈 MVCC 的实现原理。
快照读
快照读,读取的是快照数据,一般不加锁的 select 查询都是快照读。
select * from player where ...
当前读
当前读就是读的最新数据,而不是历史数据,加锁的 select,或者对数据进行增删改都会读取当前最新数据。
SELECT * FROM player LOCK IN SHARE MODE;
SELECT FROM player FOR UPDATE;
INSERT INTO player values ...
DELETE FROM player WHERE ...
UPDATE player SET ...
MVCC 是基于 Read View + 活跃事务表实现的。
行记录快照模型
数据表中的一行记录实际上有多个版本,每个版本有自己的事务 ID(row_trx_id)每次需要读取那个版本数据的时候,是通过当前版本,加 Undo Log 中存储的快照链表得到那个版本真实数据。
行记录快照是保存在 Undo Log 中,并且行记录快照是通过链表串联起来,每个快照都保存了 trx_id (事务 ID),如果要找到历史快照,只需要遍历回滚指针进行查找。
Read View
会有这么一个问题:一个事务开启,这个事务要查询数据,需要读取哪个版本的行记录呢?Read View 就是来解决这个问题的,简单的说 Read View 是可以帮助我们解决可见性问题的。
ReadView 中保存了当前活跃的事务列表。通过比较事务版本,可以判断当前行数据版本是不是对当前事务可见。
Read View 模型
如果当前行记录版本是 trx_id < 活跃的最小事务 ID (up_limit_id),说明行记录在这些活跃的事务创建前就已经提交,这个行记录对当前事务是可见的。
如果当前行记录事务版本 trx_id > 活跃的最大事务 ID (low_limit_id) 说明,这个行记录是在事务后创建的,这个行记录对当前事务不可见。
如果 up_limit_id < trx_id < low_limit_id,则有如下情况:
- 若 row trx_id 在数组 trx_ids 中,表示这个版本是由还没提交的事务生成的,不可见。
- 若 row trx_id 不在数组 trx_ids 中,表示这个版本是已经提交了的事务生成的,可见。
后话
在使用数据库的时候事务是被用得很多的,那么就避免不了遇到脏读、不可重复读的问题。自然我们需要对这个问题进行解决。所以这个也是不可或缺的一个技能!