上篇文章我们通过学习 MVCC 机制了解了 MySQL 通过 undo log 版本链和 ReadView 解决脏读、不可重复度和幻读等问题。那么接下来我们来考虑一个问题,那就是当有多个事务同时并发更新一行数据的时候,会有脏写的问题发生吗?

答案是会的,脏写是绝对不允许的,那么这个脏写是靠什么防止呢?靠锁机制

假设缓存页里有一行数据,一个事务来了想要更新这条数据,这时事务会看看这行数据有没有人加锁。如果没有,说明他是第一个人,那么该事务就会创建一个锁,里面包含了自己的 trx_id 和 等待状态,然后把锁跟行数据关联在一起。

如果其他事务过来,比如事务 B,它也想更新这行数据却发现已经被别人加锁了,那么事务 B 也加锁,并且开始排队等待这行数据锁消失。而自己的锁结构里,trx_id 是自己的事务 id,等待状态为 true,意思是我在等待。
image.png

如果事务 A 这个时候更新完了数据,就会把自己的锁释放掉。锁一旦释放了,就会去找,此时还有没有其他事务也对这行数据加锁了。然后发现 B 在等待,就将 B 事务唤醒,其等待状态修改为 false,让 B 获得锁去更新数据。

以上就是锁的基本原理。

独占锁和共享锁

上一节我们讲了多个事务同时更新一行数据,此时会加锁,只有该事务锁执行完毕提交释放了,才能唤醒别的事务继续执行。那么他们加的是什么锁呢?其实是 X 锁,也就是独占锁。当有一个事务加了独占锁之后,此时其他事务再要更新这行数据,都要加独占锁的,但是只能生成独占锁在后面等待。

那如果有人在更新数据的时候,其他事务可以读取这行数据吗?默认情况下需要加锁吗?答案是:不用。因为默认情况下,有人在更新数据的时候你去读取这行数据,直接开启了 MVCC 机制。

查询的时候可以加锁吗?当然可以,加的是一种共享锁,也就是 S 锁。这个共享锁的语法为: select * from table lock in share mode。所以 lock in share mode 的意思是加共享锁。

有了共享锁,别的事务来加独占锁可以吗?当然不可以。共享锁和共享锁是不互斥的,而独占锁和其他锁都互斥。但是查询一般很少加共享锁,如果是业务逻辑需要,反而加分布式锁更优秀。

表锁

MySQL 表锁很鸡肋,并且也很少会被用。首先表锁分为两种,一种是表锁,一种是表级的意向锁。我们来看一下加锁语句。

  1. LOCK TABLES XXX READ; -- 这是加表级共享锁
  2. LOCK TABLES xxx WRITE; -- 这是加表级独占锁