【强烈建议收藏:MySQL面试必问系列之并发事务锁专题】

文章详细介绍了MySQL中的锁机制,包括事务并发控制的重要性,锁的基本概念,如行锁(记录锁、间隙锁、临键锁)和表锁(读锁、写锁),以及锁的兼容性和使用场景。行锁和表锁在并发处理、加锁特点、死锁等方面进行了对比,并提到了快照读和当前读的概念,用于解决幻读和不可重复读的问题。文章强调了MySQL锁机制在高并发环境中的关键作用,是面试和技术理解的重点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

一.知识回顾

上节课我们一起学习了MySQL面试必问系列之事务,没有学习的同学可以看一下上一篇文章,肯定对你会有帮助,学习过的同学肯定知道,上节课我们留了一个小尾巴,这个小尾巴是什么呢?就是没有详细展开学习的MySQL事务并发锁的专题相关内容。那么这篇文章我们就一起来学习关于这方面的的内容,把我们整个知识框架搭建起来,话不多说,盘它。

二.什么是锁?锁的基本概念你知道吗?

2.1 锁的背景

再讲锁基本概念之前,我们先看这样一个例子,目的就是为了更好的理解锁的基本概念。
电商想必大家都知道,那么双十一肯定也是非常清楚的,活动当天的人流量是千万、亿级别的,但是商家的库存是有限的,不可能保证我们所有需要的用户都能抢到。所以,系统为了保证商家的商品库存不发生超卖现象,会对商品的库存进行锁控制。每次进行秒杀的时候都会锁住我们的库存,进行减库存的操作,当有用户正在下单某款商品最后一件时,系统会立马对该件商品进行锁定,防止其他用户也重复下单,直到支付动作完成才会释放,如果支付成功则立即减库存售罄,但是如果用户放弃支付,那么支付失败,我们会放出该库存,留给其他用户进行抢购。

上述过程是所有电商以及一些高并发场景以及系统开发过程中必须解决的一个问题,解决这个问题的关键就在于

2.2 锁的基本概念

在大致了解了锁的由来之后,接下来我们来了解一写关于锁的基本概念。

  1. 锁是计算机用以协调多个进程间并发访问同一共享资源的一种机制。MySQL中为了保证数据访问的一致性与有效性等功能,实现了锁机制,MySQL中的锁是在服务器层或者存储引擎层实现的。
  2. 在数据库中,除传统计算资源(CPU、RAM、I\O等)的争抢,数据也是一种供多用户共享的资源。如何保证数据并发访问的一致性,有效性,是所有数据库必须要解决的问题。锁冲突也是影响数据库并发访问性能的一个重要因素,因此锁对数据库尤其重要。
  3. 加锁是消耗资源的,锁的各种操作,包括获得锁、检测锁是否已解除、释放锁等 ,都会增加系统的开销。

三.MySQL中锁的类型

3.1 行锁

3.1.1 行锁的基本概念

行锁MySQL的官方文档给出的定义

英文版

  • A record lock is a lock on an index record. 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.

中文版

  • 记录锁是索引记录上的锁。记录锁始终锁定索引记录,即使表的定义没有索引也是如此。对于这种情况,InnoDB 会创建一个隐藏的聚集索引,并使用此索引进行记录锁定。

从官方文档我们可以看出,行锁是作用在索引上的,即使我们在建表的时候没有定义一个索引,InnoDB也会创建一个聚簇索引并将其作为锁作用的索引。这个地方就必须在补充一下关于聚簇索引相关的知识。

拓展:
聚簇索引:
1.每一个InnoDB表都需要一个聚簇索引,有且只有一个。
2.如果我们在定义表的时候定义了主键,那么MySQL将使用主键作为聚簇索引;如果没有定义主键,那么MySQL将会把第一个唯一索引(而且要求NOT NULL)作为聚簇索引;如果上诉两种情况都没有满足,那么MySQL将自动创建一个名字为GEN_CLUST_INDEX的隐藏聚簇索引。

因为是聚簇索引,所以B+树上的叶子节点都存储了数据行,那么如果现在是二级索引呢?InnoDB中的二级索引的叶节点存储的是主键值(或者说聚簇索引的值),所以通过二级索引查询数据时,还需要将对应的主键去聚簇索引中再次进行查询。

3.1.2 行锁的种类
  1. 读锁(read lock),也叫共享锁(shared lock):加了锁的记录,所有事务都能去读取但不能修改,同时阻止其他事务获得相同数据集的排他锁;

  2. 写锁(write lock),也叫排他锁(exclusive lock):允许已经获得排他锁的事务去更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁;

  3. 意向锁(InnoDB表锁)

    知识补充:
    问题1:什么是意向锁呢?
    意向锁也是表级锁,分为读意向锁(IS锁)和写意向锁(IX锁)。当事务要在记录上加上行锁时,要首先在表上加上意向锁。这样判断表中是否有记录正在加锁就很简单了,只要看下表上是否有意向锁就行了,从而就能提高效率。
    问题2:为什么要引入意向锁呢?
    由于表锁和行锁虽然锁定范围不同,但是会相互冲突。当你要加表锁时,势必要先遍历该表的所有记录,判断是否有排他锁。这种遍历检查的方式显然是一种低效的方式,MySQL引入了意向锁,来检测表锁和行锁的冲突。
    问题3:意向锁的分类
    意向共享锁(IS):一个事务给一个数据行加共享锁时,必须先获得表的IS锁;
    意向排它锁(IX):一个事务给一个数据行加排他锁时,必须先获得该表的IX锁。
    问题4:意向锁之间存在并发安全的问题吗?
    意向锁之间是不会产生冲突的,它只会阻塞表级读锁或写锁。意向锁不于行级锁发生冲突。

3.1.3 行锁如何上锁
  1. 意向锁是 InnoDB 自动加的,不需要用户干预;
  2. 对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及的数据集加上排他锁;
  3. 对于普通的SELECT语句,InnoDB不会加任何锁;
  4. 事务可以通过以下语句显示给记录集添加共享锁或排他锁:

共享锁(S):此时其他 session 仍然可以查询记录,并也可以对该记录加 share mode 的共享锁。但是如果当前事务需要对该记录进行更新操作,则很有可能造成死锁。

select * from table_name where ... lock in share mode

# 1. 相关说明:in share mode 子句的作用就是将查找的数据加上一个share锁,这个就是表示其他的事务只能对这些数据进行简单的 select 操作,而不能进行 DML 操作。
# 2. 使用场景:为了确保自己查询的数据不会被其他事务正在修改,也就是确保自己查询到的数据是最新的数据,并且不允许其他事务来修改数据。与select for update不同的是,本事务在查找完之后不一定能去更新数据,因为有可能其他事务也对同数据集使用了 in share mode 的方式加上了S锁;
# 3. 性能分析:select lock in share mode 语句是一个给查找的数据上一个共享锁(S 锁)的功能,它允许其他的事务也对该数据上S锁,但是不能够允许对该数据进行修改。如果不及时的commit 或者rollback 也可能会造成大量的事务等待。

排他锁(X):其他session可以查询记录,但是不能对该记录加共享锁或排他锁,只能等待锁释放后在加锁。

select * from table_name where ... for update

# 1. 相关说明:在执行这个 select 查询语句的时候,会将对应的索引访问条目加上排他锁(X锁),也就是说这个语句对应的锁就相当于update带来的效果;
# 2. 使用场景:为了让确保自己查找到的数据一定是最新数据,并且查找到后的数据值允许自己来修改,此时就需要用到select for update语句;
# 3. 性能分析:select for update语句相当于一个update语句。在业务繁忙的情况下,如果事务没有及时地commit或者rollback可能会造成事务长时间的等待,从而影响数据库的并发使用效率。

行锁如何上锁,看图更直观,如下图所示:
在这里插入图片描述

3.1.4 锁模式的兼容矩阵

下面表显示了了各种锁之间的兼容情况:
注意:

  1. 下面的X与S是说表级的X锁和S锁,意向锁不和行级锁发生冲突;
  2. 如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;
  3. 如果两者不兼容,那么该事务就需要等待锁的释放。
XIXSIS
X
IX兼容兼容
S兼容兼容
IS兼容兼容兼容
3.1.5 行锁的类型
3.1.5.1 记录锁(Record Lock)
  1. 记录锁最简单的一种行锁形式。
  2. 行锁是加在索引上的,如果当你的查询语句不走索引的话,那么它就会升级到表锁,最终造成效率低下,所以在写SQL语句时需要特别注意。
3.1.5.2 间隙锁(Gap Lock)
  1. 当我们使用范围条件而不是相等条件去检索,并请求锁时,InnoDB就会给符合条件的记录的索引项加上锁;
  2. 键值在条件范围内但并不存在的记录,就叫做间隙,InnoDB在此时也会对间隙加锁
  3. 间隙锁是可以共存的,共享间隙锁与独占间隙锁之间是没有区别的,两者之间并不冲突。其存在的目的都是防止其他事务往间隙中插入新的纪录,故而一个事务所采取的间隙锁是不会去阻止另外一个事务在同一个间隙中加锁的。
  4. 当然也不是在什么时候都会去加间隙锁的。在 RU 和 RC 两种隔离级别下,即使你使用 select in share mode 或 select for update,也无法防止幻读(读后写的场景)。因为这两种隔离级别下只会有行锁,而不会有间隙锁。而如果是 RR 隔离级别的话,就会在间隙上加上间隙锁。
3.1.5.3 临键锁(Next-key Lock)
  1. 临键锁是记录锁与与间隙锁的结合,所以临键锁与间隙锁是一个同时存在的概念,并且临键锁是个左开有闭的区间。
  2. MySQL 默认隔离级别是RR,在这种级别下,如果你使用 select in share mode 或者 select for update 语句,那么InnoDB会使用临键锁(记录锁 + 间隙锁),因而可以防止幻读;
3.1.5.4 插入意向锁(Insert Intention Lock)
  1. 插入意向锁不影响其他事务加其他任何锁。也就是说,一个事务已经获取了插入意向锁,对其他事务是没有任何影响的;
  2. 插入意向锁与间隙锁和 Next-key 锁冲突。也就是说,一个事务想要获取插入意向锁,如果有其他事务已经加了间隙锁或 Next-key 锁,则会阻塞。
  3. 插入意图锁是一种间隙锁,在行执行 INSERT 之前的插入操作设置。如果多个事务 INSERT 到同一个索引间隙之间,但没有在同一位置上插入,则不会产生任何的冲突。假设有值为4和7的索引记录,现在有两事务分别尝试插入值为 5 和 6 的记录,在获得插入行的排他锁之前,都使用插入意向锁锁住 4 和 7 之间的间隙,但两者之间并不会相互阻塞,因为这两行并不冲突。
  4. 插入意向锁只会和 间隙或者 Next-key 锁冲突,正如上面所说,间隙锁作用就是防止其他事务插入记录造成幻读,正是由于在执行 INSERT 语句时需要加插入意向锁,而插入意向锁和间隙锁冲突,从而阻止了插入操作的执行。
3.1.6 不同类型锁之间的兼容
RECOREDGAPNEXT-KEYII GAP(插入意向锁)
RECORED兼容兼容
GAP兼容兼容兼容兼容
NEXT-KEY兼容兼容
II GAP兼容兼容
3.1.7 行锁默认存储引擎

行锁默认的存储引擎InnoDB,但是也支持表锁。

3.1.8 行锁加锁的特点
  1. 对数据库表中一行数据进行加锁
  2. 开销大
  3. 加锁效率慢
  4. 会出现死锁
  5. 锁粒度小,发生锁冲突概率最低,并发性高
3.1.8 行锁事务并发带来的问题
  1. 更新丢失
    解决:让事务变成串行操作,而不是并发的操作,即对每个事务开始—对读取记录加排他锁
  2. 脏读
    解决:发生在隔离级别为Read uncommitted,设置为其它隔离级别
  3. 不可重读
    解决:使用Next-Key Lock算法来避免
  4. 幻读
    解决:间隙锁(Gap Lock)

3.2 表锁

3.2.1 表锁的基本概念

表锁,顾名思义,就是直接给我们数据库的表加锁,在会话开始的地方使用 lock 命令将后续需要用到的表都加上锁,在表释放前,只能访问这些加锁的表,不能访问其他表,直到最后通过 unlock tables 释放所有表锁。

除了使用 unlock tables 显示释放锁之外,会话持有其他表锁时执行lock table 语句会释放会话之前持有的锁;会话持有其他表锁时执行 start transaction 或者 begin 开启事务时,也会释放之前持有的锁。

3.2.2 表锁种类
  1. 读锁(read lock),也叫共享锁(shared lock)针对同一份数据,多个读操作可以同时进行而不会互相影响(select)
  2. 写锁(write lock),也叫排他锁(exclusive lock)当前操作没完成之前,会阻塞其它读和写操作(update、insert、delete)
3.2.3 表锁如何上锁

表锁如何上锁,看图更直观,如下图所示:
在这里插入图片描述

3.2.4 表锁默认存储引擎

表锁默认的存储引擎MyISAM

3.2.5 表锁加锁的特点
  1. 对整张表加锁
  2. 开销小
  3. 加锁快
  4. 无死锁
  5. 锁粒度大,发生锁冲突概率大,并发性低
3.2.6 结论
  1. 读锁会阻塞写操作,不会阻塞读操作
  2. 写锁会阻塞读和写操作
  3. MyISAM的读写锁调度是写优先,这也是MyISAM不适合做写为主表的引擎,因为写锁以后,其它线程不能做任何操作,大量的更新使查询很难得到锁,从而造成永远阻塞。

四.什么是快照读和当前读?

4.1 快照读

快照读就是读取的是快照数据,读取的是历史数据,不加锁的简单Select 都属于快照读。

SELECT * FROM user WHERE ...

4.2 当前读

当前读就是读的是最新数据,而不是历史的数据。加锁的 SELECT,或者对数据进行增删改都会进行当前读。

SELECT * FROM user LOCK IN SHARE MODE;
SELECT FROM user FOR UPDATE;
INSERT INTO user values ...
DELETE FROM user WHERE ...
UPDATE user SET ...

五.MySQL如何解决幻读和不可重复度?

MySQL如何解决幻读和不可重复读?

深入学习MySQL事务:ACID特性的实现原理

六.行锁和表锁排查锁问题

参考图片的地址放到了最下面,感兴趣的同学可以看看该内容
在这里插入图片描述

七.死锁

参考图片的地址放到了最下面,感兴趣的同学可以看看该内容
在这里插入图片描述

总结

MySQL系列的文章也是面试官最喜欢问的一个点,所以大家一定要好好准备,是你拿下理想offer、理想薪资必备的点。

特别感谢

参考文章地址

一张图彻底搞懂 MySQL 的锁机制

【MySQL】MySQL中的锁机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

硕风和炜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值