MySQL存储引擎InnoDB的索引和锁深学习:
电商案例:
订单表构建:
mysql
CREATE TABLE `orders` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '订单ID',
`order_no` VARCHAR(32) NOT NULL COMMENT '订单号',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
`status` TINYINT NOT NULL COMMENT '订单状态',
`pay_status` TINYINT NOT NULL COMMENT '支付状态',
`total_amount` DECIMAL(10,2) NOT NULL COMMENT '订单总金额',
`created_at` DATETIME NOT NULL COMMENT '创建时间',
`paid_at` DATETIME DEFAULT NULL COMMENT '支付时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`) # 设置唯一索引,允许数据为null
) ENGINE=InnoDB;
核心查找场景:
场景一:根据订单号查询
mysql
SELECT * FROM orders WHERE order_no = ?;
索引命中:
索引命中唯一索引,属于二级索引,回表查询一次
锁行为:
普通,快照读,无锁
行锁:唯一索引,精确命中,行锁,无间隙锁
mysql
select...for update
场景二:用户订单列表(分页)
user_id和created_at为复合索引
mysql
SELECT * FROM orders
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT 20;
RR 隔离级别:
- Next-Key Lock
- 锁定:
- user_id = 100
- created_at 范围内的 20 条 + 间隙
- 会阻塞同一用户的新订单插入
场景三:订单状态流转(高风险)
mysql
UPDATE orders
SET status = 2
WHERE order_no = ? AND status = 1;
索引命中
uk_order_no命中- status 是过滤条件,但不在索引中
锁行为
- 通过唯一索引定位记录
- 对该行加 行锁
- 不会锁其他订单
✅ 安全写法
❌ 错误写法(常见事故)
mysql
UPDATE orders
SET status = 2
WHERE status = 1;
锁行为(灾难)
- status 无索引
- 全表扫描
- 锁全表所有行
🚨 高并发下直接拖垮数据库
场景四:索引失效导致锁升级
mysql
SELECT * FROM orders
WHERE user_id = 100
AND DATE(created_at) = '2026-01-01'
FOR UPDATE;
❌ DATE(created_at) 使索引失效
👉 实际效果:
- 全表扫描
- 锁全表
✅ 正确写法:
mysql
WHERE user_id = 100
AND created_at >= '2026-01-01 00:00:00'
AND created_at < '2026-01-02 00:00:00'
疑惑一:范围查询没用索引,是不是会导致全表锁?
如果是"当前读",是的;如果是"快照读",不是。
拆解说明
| 场景 | 是否加锁 | 锁到哪 |
|---|---|---|
| SELECT(无 FOR UPDATE) | ❌ | 不加锁 |
| SELECT ... FOR UPDATE(无索引) | ✅ | 全表所有行 |
| UPDATE / DELETE(无索引) | ✅ | 全表所有行 |
📌 关键不是"范围查询",而是"是不是当前读"
疑惑二:是不是快照读即使没有索引也不会锁?
不会锁
快照读只使用 MVCC,不需要锁最新数据版本,因此不会加行锁。
疑惑三:当前读一定会锁吗?
正确,这是一道分水岭,只要是当前读,就一定加锁,差别在于锁几行
| SQL | 是否当前读 |
|---|---|
| SELECT ... FOR UPDATE | ✅ |
| SELECT ... LOCK IN SHARE MODE | ✅ |
| UPDATE | ✅ |
| DELETE | ✅ |
疑惑四:是不是update和delete天生就有排他锁?
update和delete本身就是当前读,执行时一定会加入排他锁(x lock)
你不用写,for update 引擎内部就已经帮你做了。
| 对比 | 表锁 | 无索引 UPDATE |
|---|---|---|
| 锁类型 | 表级 | 行级 |
| 实现 | MySQL | InnoDB |
| 锁对象 | 整表 | 每一行 |
| 业务感知 | ❌ 写不了 | ❌ 写不了 |
可以直接背下来的 6 句"工程级定论"
1️⃣ SELECT 默认是快照读,不加锁
2️⃣ UPDATE / DELETE 一定加排他锁
3️⃣ 是否锁全表,取决于是否命中索引
4️⃣ 没索引的当前读 = 锁全表所有行
5️⃣ RR 比 RC 锁得更"广",但不救无索引
6️⃣ 索引设计就是锁设计