阅读:49
即上篇 《Python进阶篇:MySQL隔离级别详解》 感觉有必要讲清楚mysql是如何在可重复读解决了幻读问题,这篇锁详解可以结合事务一起看。 |
锁是计算机解决并发情况下多线程/进程造成数据不一致问题的一种机制。通过锁来保证数据的一致性。但是锁也很大程度影响了数据库并发的效率。
以下是innodb中的锁:
本文主要介绍共享与排他锁(Shared and Exclusive Locks),意向锁(Intention locks),间隙锁(Gap Locks),记录锁(Record Locks),临键锁(Next-Key Locks)。
行锁: 锁住一行记录的锁
表锁: 锁住一张表的锁
效率:表>行 直接锁住整张表,干脆直接
粒度:表>行 表锁住的数据资源更大
冲突:表>行 表锁住的资源更大,冲突几率更大
性能:表<行 冲突几率越小,并发性能更高
共享锁也叫S锁,读锁。多个事务能共享读取同一数据,但是在有事务获取到共享锁时,其它事务不能修改,从而保证了数据的一致性。 加锁的方式为在查询语句后加Lock in share mode;
关闭事务自动提交,开启双窗口模拟两个事务,并进行查询。
事务1加锁查询:
事务2查询:
事务2修改:
排它锁,也叫X锁,写锁。有事务获取了某行排它锁后,其它事务读写都将被排斥。 排他锁加锁的方式分为手动加锁和自动加锁,手动加锁的方式是在查询语句后加for update;自动加锁表示delete/update/insert语句会自动加X锁。
事务1加锁:
事务2操作:
意向锁是表锁,由InnoDB自己维护,用户无法操作。
在假设的事务可以获得对某假定行的S 锁定之前,它必须首先获得对包含该行的表的一个IS 或者更强的锁定。
在假设的事务可以获得对某假定行的X 锁定之前,它必须首先获得对包含该行的表的一个IX 锁定。
下图是锁兼容矩阵图:
锁到底锁住了什么?是行还是列?接下来通过一系列操作来验证锁到底锁住了什么。
事务1给id为1的行上锁:
事务2操作id为2的行:
结论:锁住的不是行,那是否是列呢?
事务1 操作列name:
事务2操作列id:
结论:锁锁住的也不是列。
那到底锁住了什么呢?是索引。InnoDB引擎下的所有表都有索引,如果你不创建任何索引,那么它会选择隐藏的RowID作为聚集索引。有兴趣的同学可以去验证一下。接下来要谈到的就是索引中的三种锁算法。
A record lock is a lock on an index record. For example, SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;
prevents any other transaction from inserting, updating, or deleting rows where the value of t.c1
is 10
.
Record locks always lock index records, even if a table is defined with no indexes. For such cases, InnoDB
creates a hidden clustered index and uses this index for record locking.
为了更好的演示,修改了user表,并且配出主键间隙图。
主键间隙图:
记录锁:在使用主键索引或者唯一索引查询时自动使用,锁住的就是where条件指定的记录行,此时该记录只能被查询,不能被修改。
事务1查询id=1:
事务2查询id=1:
间隙锁:在操作id1-4之间空隙记录时,会在1-4加上一个间隙锁,主要的目的是为了防止其它事务在该间隙更改了数据,从而影响了数据的一致性。
事务1查询不存在的记录2:
事务2插入该间隙数据:
上述记录锁在使用范围查询时则不行,而间隙锁在>=/<=这种条件下也会失效。所以将记录锁与间隙锁的锁定范围合并,就成了临建锁,临建锁在条件为范围查询时生效,临建锁是左开右闭的形式。并且在查询条件变更时会自动变成对应条件的记录锁或间隙锁。
事务1开启范围查询:
此时会锁住到10这个主键,另外事务查询10会被阻塞
事务2查询10:
通过这几种锁的学习,可以明白MySQL是如何在可重复读隔离级别下是如何去解决幻读问题来保证读一致性。还有一些点就不一一详述了,有兴趣的同学可以参考:https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html#innodb-gap-locks。