赞
踩
并发控制
;这项技术使得在 InnoDB 的事务隔离级别下执行 一致性读
操作有了保证。读-写冲突
。非阻塞并发读
,而这个读指的就是快照读,而非当前读。乐观锁思想
的一种方式。SELECT * FROM player WHERE ...
SELECT * FROM student LOCK IN SHARE MODE; # 共享锁
SELECT * FROM student FOR UPDATE; # 排他锁
INSERT INTO student values ... # 排他锁
DELETE FROM student WHERE ... # 排他锁
UPDATE student SET ... # 排他锁
MVCC 的实现依赖于:隐藏字段
、Undo Log
、Read View
。
字段 | 说明 |
---|---|
trx_id | 每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的 事务id 赋值给 trx_id 隐藏列。 |
roll_pointer | 每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo日志 中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。 |
Undo Log:根据隔离级别的需要,复现出记录之前版本的状态。
Read View
读视图
。当前活跃事务
的ID(活跃即是指启动了当仍未提交)。READ UNCOMMITTED
隔离级别的事务,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了。SERIALIZABLE
隔离级别的事务,InnoDB规定使用加锁的方式来访问记录。READ COMMITTED
和 REPEATABLE READ
隔离级别的事务,都必须保证读到 已经提交了的
事务修改过的记录。ReadView 要解决的主要问题就是判断版本链中的哪个版本是当前事务可见的。
组成 | 说明 |
---|---|
creator_trx_id | 创建这个 Read View 的事务 ID。只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都默认为0。 |
trx_ids | 表示在生成ReadView时当前系统中活跃的读写事务的 事务id列表 。 |
up_limit_id | 活跃的事务中最小的事务 ID。 |
low_limit_id | 表示生成ReadView时系统中应该分配给下一个事务的 id 值。 |
在访问某条记录时,需要按照 ReadView 的访问规则判断记录的某个版本是否可见。
creator_trx_id 值相同
,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。小于
ReadView 中的 up_limit_id 值
,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。大于或等于
ReadView 中的 low_limit_id 值
,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。up_limit_id 和 low_limit_id 之间
,那就需要判断一下 trx_id 属性值是不是在 trx_ids 列表中;如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。此时同样的查询语句都会重新获取一次 Read View,这时如果 Read View 不同,就可能产生不可重复读或者幻读的情况。
MVCC 只能在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作。
mysql> select * from student;
+----+--------+--------+
| id | name | class |
+----+--------+--------+
| 1 | 张三 | 一班 |
+----+--------+--------+
1 row in set (0.00 sec)
mysql>
# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...
# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1:Transaction 10、20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'
执行流程 | 说明 |
---|---|
1 | 在执行 SELECT 语句时会先生成一个 ReadView,其 trx_ids 列表内容为 [10, 20],up_limit_id 为 10,low_limit_id 为 21,creator_trx_id 为 0。 |
2 | 从版本链中挑选可见的记录,从图中看出,最新版本的列 name 的内容为 ‘王五’,该版本的 trx_id 值为 10,在 trx_ids 列表中,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本。 |
3 | 下一个版本的列 name 的内容为 ‘李四’,该版本的 trx_id 值也为 10,在 trx_ids 列表中,所以也不符合可见性要求,继续向下跳转。 |
4 | 再下一个版本的列 name 的内容是 ‘张三’,该版本的 trx_id 值为 8,小于 ReadView 中的 up_limit_id 值 10,符合访问规则。 |
# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
COMMIT;
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...
UPDATE student SET name="钱七" WHERE id=1;
UPDATE student SET name="宋八" WHERE 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的值为'王五'
执行流程 | 说明 |
---|---|
1 | 在执行 SELECT 语句时会再生成一个 ReadView,其 trx_ids 列表内容为 [20],up_limit_id 为 20,low_limit_id 为 21,creator_trx_id 为 0。 |
2 | 从版本链中挑选可见的记录,从图中看出,最新版本的列 name 的内容为 ‘宋八’,该版本的 trx_id 值为 20,在 trx_ids 列表中,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本。 |
3 | 下一个版本的列 name 的内容为 ‘钱七’,该版本的 trx_id 值也为 20,在 trx_ids 列表中,所以也不符合可见性要求,继续向下跳转。 |
4 | 再下一个版本的列 name 的内容是 ‘王五’,该版本的 trx_id 值为 10,小于 ReadView 中的 up_limit_id 值 20,符合访问规则。 |
# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...
# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1:Transaction 10、20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'
执行流程 | 说明 |
---|---|
1 | 在执行 SELECT 语句时会先生成一个 ReadView,其 trx_ids 列表内容为 [10, 20],up_limit_id 为 10,low_limit_id 为 21,creator_trx_id 为 0。 |
2 | 从版本链中挑选可见的记录,从图中看出,最新版本的列 name 的内容为 ‘王五’,该版本的 trx_id 值为 10,在 trx_ids 列表中,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本。 |
3 | 下一个版本的列 name 的内容为 ‘李四’,该版本的 trx_id 值也为 10,在 trx_ids 列表中,所以也不符合可见性要求,继续向下跳转。 |
4 | 再下一个版本的列 name 的内容是 ‘张三’,该版本的 trx_id 值为 8,小于 ReadView 中的 up_limit_id 值 10,符合访问规则。 |
# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
COMMIT;
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...
UPDATE student SET name="钱七" WHERE id=1;
UPDATE student SET name="宋八" WHERE id=1;
# 使用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的值仍为'张三'
执行流程 | 说明 |
---|---|
1 | 在执行 SELECT 语句时 不会再生成一个 ReadView,trx_ids 列表内容为 [10, 20],up_limit_id 为 10,low_limit_id 为 21,creator_trx_id 为 0。 |
2 | 从版本链中挑选可见的记录,从图中看出,最新版本的列 name 的内容为 ‘王五’,该版本的 trx_id 值为 10,在 trx_ids 列表中,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本。 |
3 | 下一个版本的列 name 的内容为 ‘李四’,该版本的 trx_id 值也为 10,在 trx_ids 列表中,所以也不符合可见性要求,继续向下跳转。 |
4 | 再下一个版本的列 name 的内容是 ‘张三’,该版本的 trx_id 值为 8,小于 ReadView 中的 up_limit_id 值 10,符合访问规则。 |
MySQL 的 MVCC 机制在 REPEATABLE READ 隔离级别下解决了幻读问题。
select * from student where id >= 1;
insert into student(id,name) values(2,'李四');
insert into student(id,name) values(3,'王五');
执行流程 | 说明 |
---|---|
1 | 首先 id=1 的这条数据,前面已经说过了,可以被事务 A 看到。 |
2 | 然后是 id=2 的数据,它的 trx_id=30,此时事务 A 发现,这个值处于 up_limit_id 和 low_limit_id 之间,因此还需要再判断 30 是否处于 trx_ids 数组内。由于事务 A 的 trx_ids=[20,30],因此在数组内,这表示 id=2 的这条数据是与事务 A 在同一时刻启动的其他事务提交的,所以这条数据不能让事务 A 看到。 |
3 | 同理,id=3 的这条数据,trx_id 也为 30,因此也不能被事务 A 看见。 |
读-写 、 写-读 操作并发执行
,从而提升系统性能。删除标志位,这主要就是为 MVCC 服务的
。问题 | 说明 |
---|---|
读写之间阻塞的问题 | 通过 MVCC 可以让读写互相不阻塞 |
降低了死锁的概率 | 这是因为 MVCC 采用了乐观锁的方式,读取数据并不需要加锁,对于写操作,也只锁定必要的行。 |
解决快照读的问题 | 当查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果。 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。