死磕数据库事务:锁的实现

China☆狼群 提交于 2019-11-29 06:54:11

上一篇关于事务与锁的文章最后其实留下了一个悬而未决的问题。像SELECT ... LOCK IN SHARE MODE;这种可能会扫表的操作,明确会添加的是IS锁,剩下会加哪些锁其实和具体的WHERE子句有关。那么到底有几个锁实例的存在怎么确定呢?

mysql> SELECT * FROM account;
+----+------------+---------+
| id | account_id | balance |
+----+------------+---------+
|  1 |         10 |    3000 |
|  2 |         20 |    3000 |
|  3 |         30 |    3000 |
+----+------------+---------+
3 rows in set (0.01 sec)

mysql> SELECT * FROM account WHERE account_id < 10 LOCK IN SHARE MODE;
Empty set (0.04 sec)

mysql> SHOW ENGINE INNODB STATUS \G;
---TRANSACTION 476C0DC, ACTIVE 29 sec
3 lock struct(s), heap size 376, 2 row lock(s)
MySQL thread id 43477964, OS thread handle 0x7f162ccf6700, query id 485892583 127.0.0.1 root

这篇博文WHERE子句限定的是PRIMARYUNIQUE和普通字段的情况进行了分析。可以看出我们的例子中的3种锁结构:其中一个是IS锁对应的结构,另外两个是记录锁和区间锁吗?而记录锁有两个(实例),这是为什么呢?按照前文的分析,Next-Key锁和插入意向锁可以实现为同一个结构、X锁和S锁可以实现为同一个结构。那么实际究竟是不是这样的,本文就来一探究竟。

锁的互斥关系

很明显,如果所有锁都是独占式的实现方式,问题讨论会变得简单。从引入了读锁(共享锁)、以及读锁和写锁(独占锁)的互斥关系,把锁的讨论变复杂了。试想,如果记录只支持独占锁,不管你要读还是要写,都需要获取一个独占锁,此时整个系统退化为顺序一致性模型,在SQL里也被称作可串行化(Serializable),这也SQL标准定义的最严格的隔离级别。关于可串行化的前世今生,可以参考这篇干货满满的文章。可串行化的原则就一个,每份数据同一时刻只能有一个事务可以访问。这显然是十分安全的,这个安全是以牺牲性能为代价的;同时如果我们要分析互斥关系也变的简单,甚至说没有锁的互斥关系,原因很简单,只有一把锁

  • 每行数据只有一把记录锁,它是独占锁,所有读、写该记录的操作需要先获取这把锁
  • 每个区间只有一把区间锁,它是独占锁,所有读、写该区间的操作需要先获取这把锁

那么在我们最常用的隔离级别可重复读(Repeatable Read) 下,情况是怎么变复杂的?因为每个共享单元(区间、行)有了多把锁

  • 每个区间上,我们需要插入意向锁和Next-Key锁的联动,它们之间是互斥的。这是为了提高性能,同时防止幻读(phantom read)
  • 每个记录上,我们需要X锁和S锁的联动,它们之间也是互斥的。这还是为了提高性能,同时防止脏读(dirty read)

既然同一个记录上有多把锁,就很自然地涉及到锁之间的互斥关系:两把锁是否可以同时被征用。比如同一个记录上的读锁和写锁就不兼容(Conflict),而不同记录上的读锁和写锁就相互兼容(Compitable)。这么讲不是很直观,我们看一下死锁的例子:

在所有锁都是独占锁的可串行化(Serializable) 下,两个事务只要存在ABBA的锁关系,就构成了死锁;而 可重复读(Repeatable Read) 下,ABCD中,要明确A和D互斥、B和C互斥,才构成了死锁的潜在条件——我们可以另:A为记录1的读锁,D为记录1的写锁,B为记录2的读锁,C为记录2的写锁,死锁条件构成了,但这个时候,ABBA不构成死锁了(因为都是共享锁)。可以看到,锁之间的互斥关系引入,为性能带来提升的同时,也把模型变得更复杂。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!