数据库锁机制
→ 返回 数据库基础
锁是数据库保证并发安全的手段:多个事务同时访问同一资源时,通过加锁协调读写顺序,防止脏写、丢失更新等问题。InnoDB 中 MVCC 负责快照读,锁负责当前读和写,二者配合实现隔离性,见 MVCC、数据库事务。
按粒度分类
| 锁粒度 | 锁定范围 | 并发度 | 典型场景 |
|---|---|---|---|
| 全局锁 | 整个数据库 | 最低 | FLUSH TABLES WITH READ LOCK(全库备份) |
| 表锁 | 整张表 | 低 | MyISAM DML;InnoDB DDL(ALTER TABLE) |
| 页锁 | 数据页(16KB) | 中 | 部分引擎支持,InnoDB 不常用 |
| 行锁 | 索引记录 | 高 | InnoDB DML 默认(必须走索引,否则退化为表锁) |
| 间隙锁 | 索引记录之间的间隙 | — | InnoDB RR,防幻读 |
| 临键锁(Next-Key Lock) | 行锁 + 左开右闭间隙 | — | InnoDB RR 默认行锁算法 |
表锁 ──► 锁住整张 users 表,其他事务无法读写任何行
行锁 ──► 只锁 id=100 这一行,其他行可并发访问
间隙锁 ──► 锁 (10, 20) 区间,阻止 INSERT id=15
按模式分类(InnoDB)
共享锁(S 锁)与排他锁(X 锁)
| 已有 S 锁 | 已有 X 锁 | |
|---|---|---|
| 请求 S 锁 | ✅ 兼容 | ❌ 阻塞 |
| 请求 X 锁 | ❌ 阻塞 | ❌ 阻塞 |
-- S 锁:读锁,允许多事务同时读,阻止写
SELECT * FROM orders WHERE id = 1 LOCK IN SHARE MODE;
-- X 锁:写锁,独占,阻止其他读写
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- UPDATE / DELETE 自动加 X 锁
UPDATE orders SET status = 'paid' WHERE id = 1;意向锁(Intention Lock)
表级锁,不阻塞行锁,只用于表锁与行锁的协调:
| 意向锁 | 含义 |
|---|---|
| IS(意向共享锁) | 事务即将在某些行上加 S 锁 |
| IX(意向排他锁) | 事务即将在某些行上加 X 锁 |
事务 A 对某行加 X 锁 → 表上自动加 IX
事务 B 请求表级 S 锁 → 与 IX 冲突 → 阻塞
意向锁由 InnoDB 自动管理,开发者无需手动操作。
InnoDB 行锁三种算法
| 算法 | 锁定范围 | 隔离级别 |
|---|---|---|
| Record Lock | 单个索引记录 | RC / RR |
| Gap Lock | 索引记录之间的间隙(不含记录本身) | RR |
| Next-Key Lock | Record Lock + 前面的 Gap Lock(左开右闭) | RR 默认 |
间隙锁示例
索引 id 现有值:10、20、30。
-- RR 下
SELECT * FROM orders WHERE id > 15 AND id < 25 FOR UPDATE;
-- 加 Next-Key Lock:(10, 20]、(20, 30)
-- 其他事务无法 INSERT id=18(落在间隙中)RC 与 RR 的锁差异
| 隔离级别 | 间隙锁 | 幻读 |
|---|---|---|
| READ COMMITTED | 不使用 | 当前读可能幻读 |
| REPEATABLE READ | 使用 | 当前读被间隙锁抑制 |
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 间隙锁关闭,并发 INSERT 更高,但当前读可能幻读快照读 vs 当前读(与 MVCC 配合)
| 操作 | 类型 | 是否加锁 |
|---|---|---|
普通 SELECT | 快照读 | 否(MVCC ReadView) |
SELECT ... FOR UPDATE | 当前读 | X 锁 |
SELECT ... LOCK IN SHARE MODE | 当前读 | S 锁 |
INSERT / UPDATE / DELETE | 当前读 | X 锁 |
库存扣减正确写法:
START TRANSACTION;
-- 当前读 + X 锁,锁住该行
SELECT stock FROM products WHERE id = 100 FOR UPDATE;
UPDATE products SET stock = stock - 1 WHERE id = 100 AND stock > 0;
COMMIT;锁与索引
InnoDB 行锁加在索引上,不是加在数据行本身:
| 情况 | 实际加锁 |
|---|---|
WHERE id = 1(主键) | 精确行锁 |
WHERE name = 'Alice'(二级索引) | 二级索引记录 + 对应主键记录 |
WHERE age = 25(无索引) | 全表扫描 → 表锁 |
-- 无索引:锁全表
UPDATE users SET status = 0 WHERE nickname = 'test';
-- 有索引:只锁匹配行
UPDATE users SET status = 0 WHERE id = 100;设计 WHERE 条件时确保走索引,避免锁升级导致并发骤降。见 索引。
乐观锁 vs 悲观锁
| 策略 | 思路 | 实现 | 适用 |
|---|---|---|---|
| 悲观锁 | 假定会冲突,先加锁再操作 | SELECT ... FOR UPDATE | 冲突频繁、强一致(库存、余额) |
| 乐观锁 | 假定冲突少,提交时校验 | 版本号 / CAS | 读多写少、冲突低(文章点赞) |
乐观锁(版本号)
-- 表中有 version 字段
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 100 AND version = 5;
-- affected rows = 0 → 版本已被其他事务修改,业务层重试死锁
两个或多个事务互相等待对方持有的锁:
T1: 锁 row(id=1) → 等待 row(id=2)
T2: 锁 row(id=2) → 等待 row(id=1)
→ 循环等待 → 死锁
InnoDB 自动检测死锁,回滚代价最小(undo 量最少)的事务:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
预防与处理
| 手段 | 说明 |
|---|---|
| 固定加锁顺序 | 多行更新时按主键升序加锁 |
| 缩短事务 | 不在事务内做 HTTP 调用、MQ 发送 |
| 小批量更新 | 大批量改数据拆成多批 COMMIT |
| 应用层重试 | 捕获 1213 错误,指数退避重试 |
| 降低隔离级别 | RC 无间隙锁,死锁概率更低(需评估业务) |
排查死锁
-- 查看最近一次死锁信息
SHOW ENGINE INNODB STATUS\G
-- 关注 LATEST DETECTED DEADLOCK 段
-- 8.0+ 性能库
SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;元数据锁(MDL)
MySQL 对表结构变更使用的表级锁:
事务 A:SELECT users(持有 MDL 读锁)
事务 B:ALTER TABLE users(需 MDL 写锁)→ 阻塞
事务 C:SELECT users(需 MDL 读锁)→ 也阻塞(B 前面排队)
长事务 + DDL = 全库阻塞。变更表结构前确认无长查询。
全局锁与表锁速查
-- 全局读锁(全库只读,备份用)
FLUSH TABLES WITH READ LOCK;
UNLOCK TABLES;
-- 表锁(MyISAM 风格,InnoDB 也支持但不推荐 DML 使用)
LOCK TABLES users READ;
LOCK TABLES users WRITE;
UNLOCK TABLES;生产环境优先用 mysqldump --single-transaction(InnoDB 一致性快照)代替全局锁。
SERIALIZABLE 隔离级别
所有普通 SELECT 自动加 LOCK IN SHARE MODE,读写完全串行化:
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT * FROM orders; -- 自动加 S 锁性能最差,仅特殊场景使用。
常见坑
| 问题 | 原因 | 解决 |
|---|---|---|
| 更新慢、并发低 | WHERE 无索引,锁全表 | 加索引,EXPLAIN 验证 |
| 间隙锁导致 INSERT 阻塞 | RR + 范围当前读 | 改 RC 或缩小锁定范围 |
| 死锁频繁 | 多表加锁顺序不一致 | 统一按主键排序加锁 |
FOR UPDATE 锁不住 | 快照读与当前读混用 | 扣减场景全程当前读 |
| DDL 阻塞全表 | MDL 与长事务冲突 | 先查 PROCESSLIST,杀长事务 |
相关
- MVCC — 快照读、ReadView、版本链
- 数据库事务 — ACID、隔离级别
- SQL 基础查询 — DML 与事务控制
- MySQL 事务与锁
- Redis 分布式锁 — 跨服务场景的锁