MySQL
MVCC
基本解释
AI解释:
MySQL中的多版本并发控制(MVCC)是一种用于管理数据库并发操作的技术,允许多个事务并发地执行而不会相互阻塞,从而提高了数据库的性能和效率。MVCC的基本思想是通过维护数据的多个版本,来实现事务隔离和数据一致性。
前提:事务隔离级别(4种)、并发问题(3个)
多版本并发控制是Innodb引擎特有的技术,只要为了提高数据库并发性能,处理读写冲突,这里的读指的是快照读,不是当前读,当前读就是加锁(悲观锁),MVCC相当于乐观锁。
快照读:就是我们平常使用select查询
select * from user where id = 1;
当前读:就是使用锁
selct * from user where id = 1 SHARE MODE; 共享锁
selct * from user where id = 1 FOR UPDATE; 排他锁
具体实现
-
隐藏字段
- trx_id : 当前事务id select事务id都是0
- roll_pointer : undo log中记录的指针 这个指针可以找到undo log中的各个版本数据
-
undo log
事务日志 :相当于每个事务操作后会记录不同版本,不同规则可以查到不同版本的数据,其中roll_pointer就是找到每个版本的记录
-
ReadView
事务启动时,InnoDB创建一个Read View,用于确定哪些事务的修改是当前事务可见的。Read View包含以下信息:-
creator_trx_id ,创建这个 Read View 的事务 ID。
-
trx_ids ,表示在生成ReadView时当前系统中活跃的读写事务的 事务id列表 。
-
up_limit_id ,活跃的事务中最小的事务 ID。
-
low_limit_id ,表示生成ReadView时系统中应该分配给下一个事务的 id 值。low_limit_id 是系
统最大的事务id值,这里要注意是系统中的事务id,需要区别于正在活跃的事务ID。
-
ReadView规则
- 如果trx_id与creator_trx_id相同,说明就是当前事务进行的操作,可以读
- 如果trx_ids小于up_limit_id,说明是已经提交的事务,可以读
- 如果trx_id大于low_limit_id,说明记录(事务)是在创建ReadView之后改的,不能读
- 如果trx_id在up_limit_id和low_limit_id范围里
- 如果trx_id正好在ReadView的trx_ids里,说明事务还没提交,不能读
- 如果trx_id不在trx_ids里,说明事务都已提交,可以读
解决的问题
读已提交
每次select都生成一个ReadView,所以会出现不可重复读和幻读问题
可重复读
只在第一次select生成一个ReadView,所以可以解决不可重复读
例子
现在有两个事务10,20在执行
# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...
此刻,表student 中 id 为 1 的记录得到的版本链表如下所示:
# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1:Transaction 10、20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'
# 之后,我们把 事务id 为 10 的事务提交一下:
# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
COMMIT;
# 然后再到 事务id 为 20 的事务中更新一下表 student 中 id 为 1 的记录:
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...
UPDATE student SET name="钱七" WHERE id=1;
UPDATE student SET name="宋八" WHERE id=1;
此时undo log版本链:
读已提交 READ COMMITTED 查找id=1
# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1:Transaction 10、20均未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'
# SELECT2:Transaction 10提交,Transaction 20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'王五'
- 如果trx_id与creator_trx_id相同,说明就是当前事务进行的操作,可以读
- 如果trx_id小于up_limit_id,说明是已经提交的事务,可以读
- 如果trx_id大于low_limit_id,说明记录(事务)是在创建ReadView之后改的,不能读
- 如果trx_id在up_limit_id和low_limit_id范围里
- 如果trx_id正好在ReadView的trx_ids里,说明事务还没提交,不能读
- 如果trx_id不在trx_ids里,说明事务都已提交,可以读
读已提交
每次select都会创建ReadView -----trx_id
select1: 10和20都未提交
这里trx_id是有三个(20,10,8),creator_trx_id是0,up_limit_id是10,low_limit_id 是20,trx_ids是[10,20]
用上面的规则,自己理解一下 其实就是只能访问比活跃事务小的 或者在范围里切不相等的所以只有8符合,可以读到trx_id=8那个版本
select2: 10事务提交 20未提交
重新生成ReadView trx_id是[20],其他相同
这里trx_id是有三个(20,10,8),creator_trx_id是0,up_limit_id是10,low_limit_id 是20,trx_ids是[20]
这里20不可以,10直接就可以了,所以可以读到trx_id = 10这个版本
可重复读
只有第一次select时生成一个ReadView
# 使用REPEATABLE READ隔离级别的事务
BEGIN;
# SELECT1:Transaction 10、20均未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'
# SELECT2:Transaction 10提交,Transaction 20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值仍为'张三'
这里不详细解释了,相当于两次读trx_ids都是[10,20] 所以读到的都是trx_id等于8那个版本
@TODO
//MySQL可重复读也解决了幻读(后续扩展)