mysql 锁

对于MySQL的锁,我习惯从几个不同的维度去理解它。首先,从锁定粒度上,有我们熟知的行锁和表锁。其次,从锁的兼容性上,可以分为共享锁(S锁)和排它锁(X锁),这决定了它们是否可以共存。

在这两个基础维度之上,InnoDB为了提升性能,还引入了意向锁,它是一种表级锁,用来预示事务接下来想要加的行锁类型。

而在InnoDB的行锁实现中,又有三种具体的锁算法,这也是面试中的重点:记录锁、间隙锁和临键锁。它们是InnoDB在RR级别下解决幻读问题的核心。

最后,从编程思想的层面,我们还有乐观锁和悲观锁的概念,它们是我们在应用层进行并发控制的两种不同策略。”

MySQL 锁机制全面解析

前言

在高并发场景下,数据库锁是保证数据一致性和完整性的核心机制。对于 MySQL 的锁,我们可以从多个维度去理解它:锁定粒度锁的兼容性意向锁行锁算法以及编程思想层面的锁。本文将从这几个维度全面剖析 MySQL 的锁机制。


一、从锁定粒度划分:表锁与行锁

1.1 表锁(Table Lock)

表锁是 MySQL 中粒度最大的锁,它会锁定整张表

特点:

  • 开销小,加锁快
  • 不会出现死锁
  • 锁定粒度大,发生锁冲突的概率高,并发度低

适用场景:

  • MyISAM 存储引擎默认使用表锁
  • InnoDB 在执行 DDL 语句时也会使用表锁
-- 手动加表锁
LOCK TABLES table_name READ;   -- 读锁
LOCK TABLES table_name WRITE;  -- 写锁

-- 释放表锁
UNLOCK TABLES;

1.2 行锁(Row Lock)

行锁是粒度最细的锁,它只锁定需要操作的那一行数据

特点:

  • 开销大,加锁慢
  • 会出现死锁
  • 锁定粒度小,发生锁冲突的概率低,并发度高

适用场景:

  • InnoDB 存储引擎默认使用行锁
  • 适合高并发的 OLTP 场景

⚠️ 注意:InnoDB 的行锁是基于索引实现的。如果 SQL 语句没有使用索引,行锁会退化为表锁


二、从锁的兼容性划分:共享锁与排它锁

2.1 共享锁(Shared Lock,S 锁)

共享锁又称读锁,允许事务读取一行数据。

特点:

  • 多个事务可以同时持有同一资源的共享锁
  • 持有 S 锁时,其他事务只能再加 S 锁,不能加 X 锁
-- 显式加共享锁
SELECT * FROM table_name WHERE id = 1 LOCK IN SHARE MODE;
-- MySQL 8.0+ 推荐写法
SELECT * FROM table_name WHERE id = 1 FOR SHARE;

2.2 排它锁(Exclusive Lock,X 锁)

排它锁又称写锁,允许事务更新或删除一行数据。

特点:

  • 持有 X 锁时,其他事务既不能加 S 锁也不能加 X 锁
  • 保证在同一时间只有一个事务能对数据进行写操作
-- 显式加排它锁
SELECT * FROM table_name WHERE id = 1 FOR UPDATE;

-- DML 语句会自动加排它锁
UPDATE table_name SET name = 'test' WHERE id = 1;
DELETE FROM table_name WHERE id = 1;

2.3 兼容性矩阵

S 锁 X 锁
S 锁 ✅ 兼容 ❌ 冲突
X 锁 ❌ 冲突 ❌ 冲突

三、意向锁(Intention Lock)

3.1 为什么需要意向锁?

假设事务 A 对某行加了行级 X 锁,此时事务 B 想对整张表加表级 S 锁。如果没有意向锁,数据库需要逐行检查是否有行锁存在,效率极低。

意向锁的出现就是为了快速判断表中是否有行锁,从而提升加表锁时的效率。

3.2 意向锁的类型

  • 意向共享锁(IS):事务打算给某些行加 S 锁
  • 意向排它锁(IX):事务打算给某些行加 X 锁

3.3 加锁规则

  • 事务在获取行级 S 锁之前,必须先获取表的 IS 锁或更强的锁
  • 事务在获取行级 X 锁之前,必须先获取表的 IX 锁

3.4 意向锁兼容性矩阵

IS IX S X
IS
IX
S
X

💡 关键点:意向锁之间互相兼容,它们的作用仅仅是表明意向,真正的冲突检测发生在行锁级别。


四、InnoDB 行锁的三种算法(重点)

InnoDB 在 RR(可重复读) 隔离级别下,通过以下三种锁算法来解决幻读问题。

4.1 记录锁(Record Lock)

记录锁是精确锁定索引记录的锁,只锁定符合条件的那一行。

-- 假设 id 是主键,以下语句会对 id=1 的记录加记录锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;

特点:

  • 锁定的是索引记录本身
  • 如果表没有索引,InnoDB 会使用隐藏的聚簇索引

4.2 间隙锁(Gap Lock)

间隙锁锁定的是索引记录之间的间隙,不包括记录本身。

-- 假设表中有 id: 1, 5, 10 三条记录
-- 以下语句会锁定 (5, 10) 这个间隙
SELECT * FROM users WHERE id > 5 AND id < 10 FOR UPDATE;

特点:

  • 防止其他事务在间隙中插入新记录
  • 间隙锁之间不互斥(都是为了防止插入)
  • 只在 RR 隔离级别下生效

4.3 临键锁(Next-Key Lock)

临键锁是记录锁 + 间隙锁的组合,锁定的是一个左开右闭的区间。

-- 假设表中有 id: 1, 5, 10 三条记录
-- 以下语句的锁范围是 (5, 10],包含记录 10 以及 10 之前的间隙
SELECT * FROM users WHERE id = 10 FOR UPDATE;

特点:

  • InnoDB 默认的行锁算法
  • 有效解决幻读问题
  • 锁定范围:(前一个索引值, 当前索引值]

4.4 三种锁的关系图示

索引值:    1        5        10       15
          |--------|--------|--------|--------|

记录锁:           [5]                          -- 只锁记录 5

间隙锁:          (1, 5)                        -- 锁 1 和 5 之间的间隙

临键锁:          (1, 5]                        -- 间隙 + 记录 5

4.5 加锁规则总结

查询类型 等值查询(唯一索引) 等值查询(非唯一索引) 范围查询
锁类型 记录锁 临键锁 + 间隙锁 临键锁

五、编程思想层面:乐观锁与悲观锁

乐观锁和悲观锁不是数据库真正的锁,而是并发控制的两种设计思想。

5.1 悲观锁(Pessimistic Lock)

悲观锁假设冲突一定会发生,因此在操作数据前先加锁。

实现方式:

-- 使用数据库的锁机制
BEGIN;
SELECT * FROM products WHERE id = 1 FOR UPDATE;  -- 加排它锁
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;

适用场景:

  • 写操作频繁
  • 冲突概率高
  • 对数据一致性要求严格

5.2 乐观锁(Optimistic Lock)

乐观锁假设冲突不太会发生,只在提交时检查是否有冲突。

实现方式一:版本号机制

-- 查询时获取版本号
SELECT id, stock, version FROM products WHERE id = 1;
-- 假设查到 version = 1

-- 更新时校验版本号
UPDATE products 
SET stock = stock - 1, version = version + 1 
WHERE id = 1 AND version = 1;

-- 如果 affected_rows = 0,说明有其他事务修改过,需要重试

实现方式二:CAS(Compare And Swap)

-- 直接比较原值
UPDATE products SET stock = 99 WHERE id = 1 AND stock = 100;

适用场景:

  • 读操作频繁
  • 冲突概率低
  • 追求高并发性能

5.3 对比总结

特性 悲观锁 乐观锁
实现层面 数据库层(真实锁) 应用层(逻辑控制)
冲突假设 一定会冲突 不太会冲突
性能 有锁等待开销 无锁等待,但可能重试
死锁 可能产生 不会产生
适用场景 写多读少 读多写少

六、总结

MySQL 的锁机制是一个多层次的体系,我们可以用下图来总结:

                        MySQL 锁机制
                            │
        ┌───────────────────┼───────────────────┐
        │                   │                   │
    锁定粒度            锁的兼容性          编程思想
        │                   │                   │
    ┌───┴───┐           ┌───┴───┐           ┌───┴───┐
  表锁    行锁        S锁     X锁        乐观锁  悲观锁
                        │
                    意向锁(IS/IX)
                        │
                ┌───────┼───────┐
            记录锁   间隙锁   临键锁

核心要点回顾:

  1. 表锁 vs 行锁:粒度越小,并发越高,但开销也越大
  2. S 锁 vs X 锁:S 锁兼容 S 锁,X 锁与任何锁都互斥
  3. 意向锁:快速判断表中是否存在行锁,提升加表锁效率
  4. 记录锁/间隙锁/临键锁:InnoDB 在 RR 级别解决幻读的核心
  5. 乐观锁/悲观锁:根据业务场景选择合适的并发控制策略

理解 MySQL 锁机制,不仅能帮助我们写出高效的 SQL,更能在排查死锁、优化性能时游刃有余。


参考资料

  • MySQL 官方文档:InnoDB Locking
  • 《高性能 MySQL》第三版
  • 《MySQL 技术内幕:InnoDB 存储引擎》
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容