S 锁与 X 锁,当前读与快照读!

MySQL 中的锁还是蛮多的,在之前的文章中,松哥和大家介绍过 MySQL 中的 MDL 锁(为什么执行 alter 更新表要慎重?),今天我们再来看看 MySQL 中比较重要的两个锁:S 锁和 X 锁。
1. S 锁 S 锁,英文为 Shared Lock,中文译作共享锁,有时候我们也称之为读锁,即 Read Lock。S 锁之间是共享的,或者说是互不阻塞的。
当事务读取一条记录时,需要先获取该记录的 S 锁。
举个例子:
事务 T1 对记录 R1 加上了 S 锁,那么事务 T1 可以读取 R1 这一行记录,但是不能修改 R1,其他事务 T2 可以继续对 R1 添加 S 锁,但是不能添加 X 锁,只有当 R1 上面的 S 锁释放了,才能加上 X 锁。
举一个加 S 锁的例子,如下图:
S 锁与 X 锁,当前读与快照读!
文章图片

此时,对于 id=1 的这条记录,只能读取不能修改了。假设在另外一个事务 T 中,执行如下 SQL 是没问题的,因为 S 锁是共享锁,S 锁和 S 锁之间是兼容的:

select * from user where id=1 lock in share mode;

但是如果执行如下 SQL 则会被阻塞,因为修改数据需要获取 X 锁,而 S 锁和 X 锁不兼容:
update user set username='javaboy' where id=1;

上面这个更新语句内部会获取 X 锁,对于一些手动添加了 X 锁的查询语句,也会阻塞,例如下面这个:
S 锁与 X 锁,当前读与快照读!
文章图片

可以看到,这个 SQL 执行之后就被阻塞了。
2. X 锁 X 锁,英文为 Exclusive Lock,中文译作排他锁,有时候我们也称之为写锁,即 Write Lock。如同它的名字,X 锁是具有排他性的,即一个写锁会阻塞其他的 X 锁和 S 锁。
当事务需要修改一条记录时,需要先获取该记录的 X 锁。
举个例子:
事务 T1 对记录 R1 加上了 X 锁,那么事务 T1 即可以读取 R1 也可以修改 R1,而其他事务则不能对 R1 再添加任何锁,直到 T1 释放了 R1 上的锁。
如上文图示,锁定读的格式是这样的:
select .... for update;

3. 当前读与快照读 由上面这两种锁,又引申出来两种读:
3.1 快照读
快照读(SnapShot Read)是一种一致性不加锁的读,是 InnoDB 存储引擎并发如此之高的核心原因之一。
在可重复读的隔离级别下,事务启动的时候,就会针对当前库拍一个照片(快照),快照读读取到的数据要么就是拍照时的数据,即事务开启那一瞬间数据库中的数据,要么就是当前事务自身插入/修改过的数据。
我们日常所用的不加锁的查询,都属于快照读,这个我就不演示了。
3.2 当前读
与快照读相对应的就是当前读,当前读就是读取最新数据,而不是历史版本的数据,换言之,在可重复读隔离级别下,如果使用了当前读,也可以读到别的事务已提交的数据。
松哥举个例子:
MySQL 事务开启两个会话 A 和 B。
首先在 A 会话中开启事务并查询 id 为 1 的记录:
S 锁与 X 锁,当前读与快照读!
文章图片

接下来我们在 B 会话中对 id 为 1 的数据进行修改,如下:
S 锁与 X 锁,当前读与快照读!
文章图片

注意 B 会话不要开启事务或者开启了及时提交事务,否则 update 语句占用一把排他锁会导致一会在 A 会话中用锁时发生阻塞。
接下来,回到 A 会话中继续做查询操作,如下:
S 锁与 X 锁,当前读与快照读!
文章图片

可以看到,A 会话中第一个查询是快照读,读取到的是当前事务开启时的数据状态,后面两个查询则是当前读,读取到了当前最新的数据(B 会话中修改后的数据)。
4. 小结 【S 锁与 X 锁,当前读与快照读!】好啦,一个小小的知识点,日积月累,fighting!

    推荐阅读