时间:2021-07-01 10:21:17 帮助过:17人阅读
主键为id,唯一索引v3,二级普通索引v1。
以下所举的例子中,表中的数据均为上面select查询到的数据。
session1 | session2 |
---|---|
begin | |
begin | |
update test set v1=100 where id=10; | |
select * from test where id=10 for update; 阻塞 | |
select * from test where id=9 for update; Empty set (0.00 sec) | |
select * from test where id=11 for update; Empty set (0.00 sec) |
结论:此时只在对应的主键记录上加X锁即可。
session1 | session2 |
---|---|
begin | |
begin | |
update test set v2=100 where v3=10; | |
select * from test where id=10 for update; 阻塞 | |
select * from test where v3=10 for update; 阻塞 | |
select * from test where v2=10 for update; 阻塞 | |
select * from test where id=9 for update; Empty set (0.00 sec) |
为什么会在主键上加X锁呢?假设此时有个并发sql:delete from test where id=10
,那么并发的update 就会感知不到delete 语句的存在,违背了同一记录上的更新/删除需要串行执行的约束。
为什么select * from test where v2=10 for update;
会阻塞?因为v2上没有索引,MySQL判断走全表扫描对每个记录加X锁,但是表中id=10的记录有X锁了,两者不兼容,所以阻塞。
注意session1中update语句首先对表加了IX意向锁,session2判断表有IX锁,说明底层的记录有session在加X锁,所以直接阻塞。这样的花MySQL不用深入底层的每条记录,去判断每条记录是否有IX锁,这样太耗时了。(详细见MySQL 锁基础意向锁部分)
结论:此时需要加两个X锁,一个是唯一索引上v3=10的记录,还有聚簇索引上id=10的元组。
同上。区别是对所有满足SQL查询记录的加X锁,同时对应的主键也都加X锁。
session1 | session2 |
---|---|
begin | |
begin | |
update test set v2=1000 where v2=15; | |
select * from test where v1=4 for update; 阻塞 |
因为查询不能用到索引,只能进行全表扫描,对聚簇索引上的所有记录都加了X锁(不是加表锁,也不是在满足条件的记录上加行锁)。
为什么不是在满足条件的记录上加锁呢?如果一个条件无法通过索引快速过滤,那么存储引擎层面就会将所有记录加锁后返回,然后由MySQL Server 层进行过滤。因此也就把所有的记录都锁上了。
但是在5.1及更新的版本中,MySQL会在Server层过滤后,将不符合条件的记录全部释放锁,但是在更早期的版本中,MySQL只有在事务提交之后才释放锁。(高性能MySQL中文版第三版 P181)
结论:每条记录都加上X锁。
与查询主键查找 + RC一致。
与查询唯一索引查找 + RC一致。
session1 | session2 |
---|---|
begin | |
begin | |
update test set v2=1000 where v1=7; | |
update test set v1=6 where v1=9; 阻塞 | |
update test set v1=8 where v1=9; 阻塞 | |
update test set v1=5 where v1=9; 阻塞 | |
update test set v1=9 where v1=9; Query OK, 1 row affected (0.00 sec) |
与RC模式不同,RR模式要求不可幻读,即在同一个事务中,连续两次当前读 ,那么这两次当前读返回的是完全相同的记录。这里的session1的update test set v2=1000 where v1=7
就是当前读,为了保证不出现幻读,需要在v1=7的两端加入GAP锁,保证其他事务不能同时在这个范围内插入数据。
为什么唯一索引不用加GAP锁?因为唯一索引的唯一性保证了两次当前读一定会返回一条数据而不是两条,因为唯一性嘛。所以一定·不会有新的数据插入进来。但是如果第一次当前读update test set v2=100 where v3=10
没有符合条件的查询记录呢?MySQL还是会加GAP锁,来保证这一区间不会有数据插入。
但是这个个人不理解的是为什么GAP的两端点都是闭合的?即更新v1=5和v1=8都会阻塞?
这个综合以上几个例子比较好理解:会对每一个记录加X锁,其次,聚簇索引每条记录间的间隙(GAP),也同时被加上了GAP 锁。
参考更复杂的例子
MySQL首先在索引层加GAP锁,再在聚簇索引对应的主键加X锁,再在server层做过滤。而不是先过滤,再在聚簇索引主键加X锁。
where条件 | 定位条件 | 终止条件 | 加锁范围 |
---|---|---|---|
ID < X | infinum | X | (infinum,X] |
ID <= X | infinum | X的下一条记录 | (infinum,X的下一条记录] |
ID > X | X的下一条记录 | maxnum | (X,maxnum] |
ID >= X | X | maxnum | [X,maxnum] |
参考资料:
http://hedengcheng.com/?p=771
http://blog.sina.com.cn/s/blog_a1e9c7910102vnrj.html
MySQL锁总结
标签:例子 mysq let date 中文 delete tab 文章 插入数据