引言
在并发场景下,数据库需要解决三类核心问题:
- 数据一致性:不能读到脏数据
- 并发安全:多个事务同时修改数据时不出错
- 性能权衡:在一致性和并发性能之间取得平衡
mysql(准确来说是 innodb 存储引擎)通过一整套多层次锁机制来解决这些问题。这些锁并不是“随便加的”,而是紧密围绕 事务隔离级别、索引结构、并发访问模式 设计出来的。
本文将从三个层次展开:
- mysql 有哪些锁?
- 表锁和行锁到底解决了什么问题?
- 真实并发场景下,sql 会不会阻塞?为什么?
一、mysql 有哪些锁?——三大类锁体系
从作用范围来看,mysql 的锁可以分为三大类:
全局锁 → 表级锁 → 行级锁
全局锁(global lock)
1.1 什么是全局锁?
全局锁会锁住整个数据库实例,使其进入只读状态。
最典型的加锁方式是:
flush tables with read lock;
此时:
- 所有表都只能读
- 所有写操作(insert / update / delete)都会被阻塞
1.2 全局锁的典型使用场景
逻辑备份
mysqldump --single-transaction ...
在早期 mysql 版本中,为了保证备份过程中数据一致,通常需要加全局读锁。
1.3 全局锁的问题
- 整库不可写
- 在线业务几乎不可接受
现在更多使用 mvcc + 一致性快照,避免全局锁
表级锁(table-level lock)
表级锁的作用范围是 单张表,开销小,但并发能力有限。
2.1 表锁(table lock)
lock table t_order read; lock table t_order write;
- read:其他线程可读,不可写
- write:其他线程既不可读,也不可写
innodb 一般不推荐使用显式表锁
2.2 元数据锁(mdl,metadata lock)
mdl 是自动加的锁,很多人“被它坑过,但不知道是它”。
- 对表进行 crud → 加 mdl 读锁
- 对表进行 ddl(alter / drop) → 加 mdl 写锁
读写互斥!
-- 事务 a select * from t_order; -- 持有 mdl 读锁 -- 事务 b alter table t_order add column xxx; -- 等待
线上 ddl 阻塞业务的元凶
2.3 意向锁(intention lock)
意向锁是 innodb 行锁的“前置声明”,存在于表级别。
- 意向共享锁(is)
- 意向排他锁(ix)
作用:让表锁与行锁能快速判断是否冲突
“我要锁某几行了,你别直接给我整个表上锁”
行级锁(row-level lock)
行锁是 innodb 的核心竞争力,并发性能的关键。
3.1 记录锁(record lock)
锁住 某一条索引记录
- s 锁(共享锁):读
- x 锁(排他锁):写
select * from t_order where id = 10 lock in share mode; update t_order set status = 1 where id = 10;
3.2 间隙锁(gap lock)
锁住的是 索引区间之间的“空隙”
- 只存在于 rr(可重复读)隔离级别
- 目的:防止幻读
select * from t_order where id between 10 and 20 for update;
锁住 (10, 20) 这个区间,阻止插入新记录
间隙锁之间是兼容的
3.3 临键锁(next-key lock)
next-key lock = record lock + gap lock
- 锁住记录本身
- 同时锁住前面的间隙
这是 innodb rr 隔离级别的默认加锁策略
二、表锁和行锁解决了什么问题?
| 锁类型 | 解决的问题 |
|---|---|
| 表锁 | ddl 与 dml 冲突 |
| 行锁 | 并发更新同一行 |
| 间隙锁 | 防止幻读 |
| 临键锁 | 范围查询 + 插入一致性 |
本质目标只有一个:
在并发环境下,保证事务隔离语义成立
三、并发场景实战分析:到底会不会阻塞?
场景 1:两个事务同时 update 同一条记录
update t_order set status = 1 where id = 100;
会阻塞
- 第一个事务获取 id=100 的 x 锁
- 第二个事务只能等待
场景 2:更新不同主键的记录
-- 事务 a update t_order set status = 1 where id = 100; -- 事务 b update t_order set status = 1 where id = 200;
不会阻塞
- 行锁粒度是“记录级”
- 主键索引精确定位
场景 3:更新主键范围(存在索引)
update t_order set status = 1 where id between 100 and 200;
- 加的是 next-key lock
- 会锁住区间
其他事务在该区间 insert 会被阻塞
场景 4:where 条件未命中索引
update t_order set status = 1 where order_no = 'xxx';
如果 order_no 没有索引
- innodb 会 全表扫描
- 锁升级为 锁住大量记录甚至整表
这才是线上“莫名其妙阻塞”的根源
总结
锁不是 sql 层决定的,而是:
sql + 索引 + 隔离级别 + 引擎共同作用的结果
关键记忆点
- rr 隔离级别 ≠ 只有行锁
- next-key lock 是默认策略
- 无索引 ≈ 放弃行锁优势
- mdl 是 ddl 阻塞的幕后黑手
实战建议
- 所有更新 sql 必须走索引
- 范围更新要评估间隙锁影响
- 线上 ddl 使用 online ddl
- 用
show engine innodb status排查锁等待
到此这篇关于mysql 锁机制全解析:从锁的分类到并发更新是否阻塞的文章就介绍到这了,更多相关mysql 锁机制内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论