当前位置: 首页 > news >正文

数据库学习笔记2——MySQL 的锁机制

1. 总体分类

MySQL 的锁可以按锁定范围使用思想来理解:

分类角度类型说明
按锁范围全局锁锁住整个 MySQL 实例中的所有库、所有表
按锁范围表级锁锁住一张表,或者锁住表结构的元数据
按锁范围行级锁锁住某些行或某些索引范围,主要由 InnoDB 提供
按并发控制思想悲观锁先加锁,再操作
按并发控制思想乐观锁不先加锁,更新时检查数据是否被别人改过

在实际开发中,InnoDB 是 MySQL 最常用的事务型存储引擎,它主要依靠MVCC + 行锁 + 间隙锁 / 临键锁来保证并发安全和事务隔离。


2. 全局锁

2.1 什么是全局锁

MySQL 里最常见的全局锁是:

FLUSH TABLES WITH READ LOCK;

它会关闭所有打开的表,并对所有数据库中的所有表加一个全局读锁。加锁后,其他会话通常可以继续读数据,但不能修改数据。释放锁使用:

UNLOCK TABLES;

官方文档中说明,FLUSH TABLES WITH READ LOCK获取的是 global read lock,而不是普通表锁,它会锁住所有数据库中的所有表,常用于文件系统快照或备份场景。

2.2 全局锁的典型场景:全库备份

全局锁常用于全库逻辑备份或物理快照。原因是:如果备份过程中先导出 A 表,再导出 B 表,而此时 B 表被修改,就可能导致 A、B 两张表的数据不是同一个时间点的数据。

例如订单系统中:

  • A 表是orders

  • B 表是order_items

  • 备份orders后,用户又新增了订单明细

  • 再备份order_items

这样备份出来的数据就可能出现主表和明细表不一致的问题。

所以,全局锁的作用是让整个实例在备份期间保持一个相对静止的数据状态。

2.3 InnoDB 为什么可以不依赖全局锁完成一致性备份?

InnoDB 支持 MVCC,也就是多版本并发控制。普通SELECTREPEATABLE READREAD COMMITTED隔离级别下通常是一致性非锁定读。它不是去等最新数据,而是基于某个时间点的 Read View 读取快照数据。

因此,InnoDB 做逻辑备份时,可以使用:

mysqldump --single-transaction

这个参数会在一个事务中创建一致性快照,从而尽量避免用全局锁阻塞业务写入。MySQL 官方文档也说明,mysqldump --single-transaction可以在不锁住其他客户端的情况下创建一致性快照。,仍然可能破坏快照一致性或导致异常。


3. 表级锁

3.1 表级锁是什么

表级锁是锁住整张表。相比全局锁,它的范围更小;相比行锁,它的粒度更大,开销较低,但并发能力较弱。

MySQL 可以显式使用:

LOCK TABLES table_name READ; LOCK TABLES table_name WRITE; UNLOCK TABLES;

其中:

表锁类型含义
READ当前会话可以读,不能写;其他会话也可以读,但不能写
WRITE当前会话可以读写;其他会话不能读也不能写

官方文档说明,持有READ锁的会话可以读表但不能写表,多个会话可以同时持有READ锁;持有WRITE锁的会话可以读写表,并且只有持有该锁的会话可以访问这张表,其他会话会被阻塞。

3.2 哪些存储引擎常用表级锁

MyISAM、MEMORY、MERGE 等存储引擎主要使用表级锁,同一时间通常只允许一个会话更新表,因此更适合读多写少、只读或单用户场景。官方文档也指出,MySQL 对 MyISAM、MEMORY、MERGE 表使用 table-level locking,这会降低写并发。

InnoDB 主要使用行级锁,但并不代表完全没有表级锁。InnoDB 中也存在一些表级相关的锁,例如:

  • 意向锁:ISIX

  • 自增锁:AUTO-INC Lock

  • 元数据锁:Metadata Lock

  • 显式表锁:LOCK TABLES

  • DDL 场景下的表结构锁


4. 行级锁

4.1 行级锁是什么

行级锁是 InnoDB 的核心锁机制。它锁的不是整张表,而是某些行,准确地说,InnoDB 的行锁大多数情况下锁的是索引记录。即使表没有显式索引,InnoDB 也会创建隐藏聚簇索引用于记录锁。如果没有合适索引,扫描范围变大,锁的范围也可能变大。

行级锁的优点是并发能力强,不同行之间可以并发修改;缺点是锁管理开销更大,也更容易出现死锁、锁等待等问题。

4.2 共享锁 S Lock

共享锁,又叫读锁,英文是 Shared Lock,简称S Lock

特点:

  • 持有共享锁的事务可以读取该行;

  • 多个事务可以同时对同一行持有共享锁;

  • 如果某行已经有共享锁,其他事务不能对它加排他锁。

MySQL 8.0 以后推荐写法:

SELECT * FROM user WHERE id = 1 FOR SHARE;

旧写法是:

SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE;

4.3 排他锁 X Lock

排他锁,又叫写锁,英文是 Exclusive Lock,简称X Lock

特点:

  • 持有排他锁的事务可以更新或删除该行;

  • 如果某行已经有排他锁,其他事务不能再对它加共享锁或排他锁;

  • UPDATEDELETESELECT ... FOR UPDATE通常会涉及排他锁。

示例:

SELECT * FROM user WHERE id = 1 FOR UPDATE; UPDATE user SET name = 'Tom' WHERE id = 1; DELETE FROM user WHERE id = 1;

5. 当前读与快照读

5.1 快照读

快照读就是普通SELECT

SELECT * FROM user WHERE id = 1;

在 InnoDB 中,普通SELECT通常不加锁,而是通过 MVCC 读取历史版本快照。

快照读的特点:

  • 不读取未提交数据;

  • 不阻塞其他事务修改;

  • 其他事务修改数据也通常不阻塞当前快照读;

在 InnoDB 默认的REPEATABLE READ隔离级别下,同一个事务中的一致性读会读取第一次读建立的快照。

5.2 当前读

当前读是读取数据的最新版本,并且通常需要加锁,防止读到后马上被别人改掉。

常见当前读包括:

SELECT * FROM user WHERE id = 1 FOR UPDATE; SELECT * FROM user WHERE id = 1 FOR SHARE; UPDATE user SET name = 'Tom' WHERE id = 1; DELETE FROM user WHERE id = 1; INSERT INTO user(id, name) VALUES(1, 'Tom');

SELECT ... FOR UPDATESELECT ... FOR SHAREUPDATEDELETE这类 locking read 或写操作,会根据是否使用唯一索引、是否是范围条件来加记录锁、间隙锁或临键锁。


6. 记录锁、间隙锁、临键锁

这三个是理解 InnoDB 行锁的重点。

6.1 记录锁 Record Lock

记录锁锁住的是某一条索引记录

例如:

SELECT * FROM user WHERE id = 10 FOR UPDATE;

如果id是主键或唯一索引,那么 InnoDB 通常只锁住id = 10这一条索引记录。

注意:InnoDB 的记录锁本质上是锁索引记录,而不是直接锁“表中的物理行”。

6.2 间隙锁 Gap Lock

间隙锁锁住的是两个索引值之间的空隙,不是某条具体记录。

例如表中有索引值:

10, 20, 30

如果锁住(10, 20)这个间隙,那么其他事务不能在这个区间插入新的索引值,比如 15。

间隙锁的主要作用是:防止其他事务在范围中插入新数据,从而解决幻读问题

6.3 临键锁 Next-Key Lock

临键锁也叫 Next-Key Lock,它可以理解为:

临键锁 = 记录锁 + 该记录前面的间隙锁

例如索引中有:

10, 11, 13, 20

那么可能的临键锁范围包括:

(-∞, 10] (10, 11] (11, 13] (13, 20] (20, +∞)

6.4 为什么非唯一索引容易产生临键锁?

假设有表:

CREATE TABLE user ( id INT PRIMARY KEY, age INT, KEY idx_age(age) );

数据如下:

age: 10, 20, 20, 30

执行:

SELECT * FROM user WHERE age = 20 FOR UPDATE;

如果age是非唯一索引,InnoDB 不能只锁一条记录,因为可能有多条age = 20,而且还要防止其他事务继续插入新的age = 20。所以它可能会锁住相关的索引记录以及附近间隙。

如果使用的是唯一索引并且查询条件也是唯一等值查询,例如:

SELECT * FROM user WHERE id = 10 FOR UPDATE;

这种情况下通常只需要记录锁,不需要锁前面的间隙。使用唯一索引查找唯一行时不需要 gap lock;如果没有索引或使用非唯一索引,就可能锁住前面的间隙。


7. 意向锁

7.1 什么是意向锁

意向锁是 InnoDB 中的一种表级锁,用于支持“表锁和行锁共存”。

它不是真的要锁整张表的数据,而是用来声明:

我这个事务准备在这张表的某些行上加锁。

意向锁分为两类:

类型含义
ISIntention Shared Lock,表示事务准备对某些行加共享锁
IXIntention Exclusive Lock,表示事务准备对某些行加排他锁

例如:

SELECT * FROM user WHERE id = 1 FOR SHARE;

会先加IS意向共享锁。

SELECT * FROM user WHERE id = 1 FOR UPDATE;

会先加IX意向排他锁。

7.2 为什么需要意向锁

假设事务 A 已经锁住了表userid = 1的这一行。

此时事务 B 想对整张表加写锁:

LOCK TABLES user WRITE;

如果没有意向锁,MySQL 可能要一行一行检查表里是否存在行锁,这个代价很高。

有了意向锁后,事务 A 在加行锁之前会先在表上加IX锁。事务 B 想加整表写锁时,只需要检查表上是否有冲突的意向锁即可,不用扫描所有行。

一句话总结:

意向锁是为了让表锁和行锁能够高效判断冲突。


8. 乐观锁与悲观锁

8.1 悲观锁

悲观锁的思想是:

我认为数据很可能被别人修改,所以我先加锁,再操作。

常见实现:

BEGIN; SELECT * FROM product WHERE id = 1 FOR UPDATE; UPDATE product SET stock = stock - 1 WHERE id = 1; COMMIT;

适合场景:

  • 写冲突多;

  • 库存扣减、余额扣减等强一致性场景;

  • 不允许多个事务同时修改同一份数据。

缺点:

  • 容易出现锁等待;

  • 并发性能可能下降;

  • 事务写得不好容易死锁。

8.2 乐观锁

乐观锁的思想是:

我先不加锁,提交更新时再检查数据有没有被别人改过。

常见实现是增加version字段:

SELECT id, stock, version FROM product WHERE id = 1; UPDATE product SET stock = stock - 1, version = version + 1 WHERE id = 1 AND version = 3;

如果更新影响行数为 0,说明版本号已经变化,当前事务更新失败,需要重试或提示用户。

适合场景:

  • 读多写少;

  • 冲突概率低;

  • 希望减少数据库锁等待。


9. 死锁与锁等待

行锁粒度小,但也更容易出现死锁。

死锁例子:

事务 A:

BEGIN; UPDATE account SET balance = balance - 100 WHERE id = 1; UPDATE account SET balance = balance + 100 WHERE id = 2; COMMIT;

事务 B:

BEGIN; UPDATE account SET balance = balance - 100 WHERE id = 2; UPDATE account SET balance = balance + 100 WHERE id = 1; COMMIT;

事务 A 先锁id = 1,事务 B 先锁id = 2,然后双方都等待对方释放锁,就会产生死锁。

死锁可能发生在多个事务以相反顺序锁住多张表或多个范围时;减少死锁的方法包括缩短事务、多个事务按相同顺序访问表和行、为SELECT ... FOR UPDATEUPDATE ... WHERE中的条件列建立索引等。

排查死锁可以使用:

SHOW ENGINE INNODB STATUS;

10. 总结

MySQL 的锁可以分为全局锁、表级锁和行级锁。全局锁常用于全库备份,但 InnoDB 可以通过 MVCC 和一致性快照减少对全局锁的依赖。表锁粒度大、开销小、并发差,MyISAM 等引擎主要使用表级锁;InnoDB 主要使用行锁,同时也有意向锁、元数据锁、自增锁等表级锁。InnoDB 行锁本质上是索引记录锁,普通SELECT是快照读,不加锁;SELECT ... FOR UPDATEUPDATEDELETE属于当前读,会加锁。为了防止幻读,InnoDB 在REPEATABLE READ下会使用间隙锁和临键锁,其中临键锁可以理解为记录锁加前一个间隙锁。

http://www.gsyq.cn/news/1637748.html

相关文章:

  • 编译原理第三版第五章课后题1-2题
  • 硅胶密封件实测:2026年7月亲测排行
  • 【计算机毕业设计】基于Java的智能停车场预约收费系统
  • 炉石传说脚本Hearthstone-Script:5分钟实现智能自动化对战的终极指南
  • 局域网文件共享实战:从“账户被禁用”到成功互传文件
  • Dify工作流与MCP服务:构建可嵌入IDE的AI智能副驾
  • 我用 Codex 复刻了一个 Windows 11 计算器,过程比想象中真实多了
  • 告别西门子依赖!C# 实现信捷 XD 系列 PLC 通信与数据采集
  • Window系统Claude Code安装教程
  • Java后端面试与职业发展:从核心技能到AI应用集成
  • 商品条码查询API实战:调用免费接口快速获取产品信息
  • 小红书数据采集终极指南:Python xhs库完整实战教程
  • LangChain LCEL 链式调用:从管道运算符到可组合的 AI 应用
  • ncmdump终极指南:3分钟解锁网易云音乐加密文件
  • AI代码生成能力整合:从对话到执行的范式迁移与实战指南
  • IIC通信(STM32笔记)
  • trae接如claudecode
  • 167、PCIE硬件设计概述:PCB与连接器
  • 2026年AI论文软件测评:5款神器从大纲到答辩全链路通关攻略
  • 私有化部署Dify:四步在Windows本地搭建开源AI应用开发平台
  • 打通运维知识壁垒:以 CentOS7 与数据库为核心,搭建系统 - 网络 - 数据一体化运维体系
  • 用运筹学与强化学习构建个人发展量化分析模型
  • 图像和视频处理的核心概念(在图像上画直线)
  • Perplexity vs 秘塔AI vs Google SGE:三大AI搜索引擎横评
  • 四类芯片对比(一)
  • 【极简监控·番外篇】被逼无奈的“降维打击”:Java Remote Debug 救火指南
  • Allegro 生产文件导出:Gerber 274X 与钻孔文件 5 步标准化检查清单
  • 【算法从零到千】【32-41】位运算(详细讲解+题目运用)
  • 教育学论文降AI工具免费推荐:2026年教育学毕业论文AIGC超标4.8元亲测99.26%知网完整方案
  • 羽球联盟 HarmonyOS NEXT 实战系列 (03/20):四Tab首页容器与资讯首屏搭建