首页 - 信息 - MySQL数据库InnoDB锁详解

MySQL数据库InnoDB锁详解

2023-10-01 10:14

前言

本文主要是对MySQL锁的实现进行分析和总结。中间有很长一段是关于各种查询情况下的锁分析,并且搭配了完整的脚本和图例。如果您有兴趣,可以按照文章中的步骤进行验证。其实总结的内容不多,耐心看完,一起消除MySQL锁原理的迷雾。

锁具分类

下面将从不同角度对InnoDB锁进行分类。每种锁定模式(lock mode)都有对应的英文代号。锁定周期是事务的开始和提交。

共享锁和独占锁

从锁属性来看,可以分为排它锁(排除锁)和共享锁(共享锁):

排他锁(X):修改一行记录时,防止其他人同时修改。

我们想象N个人在同一张纸上写文章来类比独占锁的概念。大家写的文章必须是独立的,否则就会散乱不堪,难以阅读。为了保证这一点,我们获得了一张独一无二的优惠券,大家可以一起抢。只有拿到这张凭证的人才能在纸上写文章。有的人一边写,有的人就得等待,等别人写完之后还回凭证,然后再去抢凭证。

总结就是,当多个线程(N个人)修改同一个数据(在同一张纸上写一篇文章)时,为了保证并发数据安全,需要线程抢锁(写文章的人的凭证)是可以修改的。没有抢到锁的线程需要等待持有锁的线程释放锁(返回凭证)然后再次竞争。

共享锁(S):读取一行记录时,防止他人修改。

基于以上想象,我们考虑另一个需求。现在有N个人想要阅读这些写好的文章。因为阅读文章和写文章是两件事,所以我们单独创建了无限量阅读文章的优惠券。所有想阅读的人只有拿到这张凭证后才能查看。

阅读文章不会改变纸上的内容,因此没有限制谁可以阅读该文章,您可以随心所欲地阅读。

但是为了保证大家看到的内容是完整的,我们就得限制一下,等别人写完了才可以看;

另一方面,人们在阅读时不能更改纸上的内容,否则阅读时看到的内容将是不完整的。

综上所述,持有共享锁的N个线程不需要互相等待,可以并行查询数据;当一个线程持有共享锁时,想要获取排它锁的线程需要等待所有共享锁被释放;当存在排他锁时,想要获取共享锁的线程需要等待。这个思想其实和JDK的读写锁(ReentrantReadWriteLock)是一样的。

以下排它锁和共享锁 并发 等待时,互斥意味着需要等待其他锁释放:


X S
X 互斥 互斥
S 互斥 不互相排斥

但是是否加锁其实是由业务线程本身决定的,就像上面文章的阅读者和写者决定是否遵守这个规则一样。不遵守规则就会导致数据混乱。

InnoDB默认对SQL的增删改查执行锁定行为。对于查询语句,需要在末尾添加关键字来实现排他锁(FOR UPDATE)和共享锁(LOCK IN SHARE MODE)。

表级锁和行级锁

MySQL锁按照锁粒度可以分为表级锁和行级锁。锁粒度越小,资源锁定的范围越小,并发度越高,性能越高。锁粒度越大,反之亦然。

让我们继续上面想象的场景。由于有锁,同时只能一个人在纸上写文章,白天一篇一篇写的产出肯定会很低。事实上,很多人想在纸上写出完全不同的位置,而不是把它们混在一起。为了提高效率,把纸撕成N张,分不同的部分写。如果写相同的部分,就需要争夺凭证,分别阅读。这就是锁粒度从粗(一张纸)到细(分为N份)、表锁和行锁的区别。

  • • 表级锁有两种:意向锁(I)和自增锁。它们的作用和意义如下

意向锁加锁属性可以分为意向排它锁(IX)和意向共享锁(IS)。功能可以参考这篇文章掘金-MySql InnoDB中意向锁的功能[1]

自增锁是一种特殊的表级锁,用在自增字段上。

本文主要关注行级锁,因此这部分内容略过。

  • • 行级锁分为三种类型

记录锁定(锁定rec但不锁定gap):锁定指定索引条件;

间隙锁定(间隙):将间隙锁定在较小索引值的方向。比如有两个索引值10和20,20的gap锁范围是10

临时锁:记录锁+间隙锁,即锁定某行记录的索引,以及该索引与下一个索引的间隙。

通过测试分析MySQL在各种情况下是如何加锁的

至此,我们通过命令行登录MySQL数据库,来分析在可重复读和读已提交隔离级别下,各种情况下MySQL是如何加锁的。看来这背后的内容还真不少。其实,看了几种情况找到加锁规则后,你会发现太简单了。现在我们开始测试前的准备工作。

  • • 先登录数据库

# 登录数据库
mysql -u root -p
  • • 打开锁定信息开关,我们可以通过命令行查看具体的锁定信息。

# 打开输出锁定信息开关
set global innodb_status_output_locks = 1;

#
查询锁定信息开关
显示类似'%innodb的变量_status_output_locks%';

检查开关开启前后的状态。