在 MySQL InnoDB 中,执行如下 SQL:
UPDATE t SET age = 20 WHERE id = 100;
其中:
- id 是 主键(聚簇索引)
- age 是 二级索引(非唯一)
我们来分析 InnoDB 如何加锁。
✅ 结论(先说重点)
该语句只会对聚簇索引中 id = 100 的记录加排他锁(X 锁),不会对二级索引 age 加任何行锁(Record Lock)。
但在更新过程中,会临时删除旧的二级索引项、插入新的二级索引项 —— 这个过程是原子的,不需要显式加锁保护。
详细加锁过程解析
步骤 1:通过主键定位行(聚簇索引)
- 由于 id 是主键,InnoDB 直接在 聚簇索引 中找到 id = 100 的记录。
- 对该 聚簇索引记录加 X 锁(排他锁)。 这是真正的“行锁”,防止其他事务并发修改或读取(取决于隔离级别)。
步骤 2:读取旧行数据(包括 age 值)
- 假设原 age = 10。
步骤 3:更新聚簇索引中的 age 字段
- 直接修改聚簇索引页中的行数据(age 从 10 → 20)。
步骤 4:维护二级索引 idx_age
- 删除旧的二级索引项:(age=10, id=100)
- 插入新的二级索引项:(age=20, id=100)
⚠️ 关键点:
这个“删 + 插”操作是在同一个事务内、持有聚簇索引 X 锁的前提下完成的,因此不需要对二级索引单独加 Record Lock!
为什么?
- 由于 只有当前事务能修改这条记录(已持 X 锁)
- 其他事务无法同时修改 id=100 的行,也就无法并发修改其二级索引项
- 所以 二级索引的变更不会引发并发冲突,无需额外加锁
验证:会不会锁住 age=10 或 age=20 的其他记录?
不会!
例如:
- 表中还有 id=200, age=10
- 执行 UPDATE t SET age = 20 WHERE id = 100
不会阻塞 其他事务对 id=200 的操作(即使 age 也是 10)
由于:
- InnoDB 只锁了 id=100 的聚簇索引记录
- 二级索引上的 (10, 100) 和 (20, 100) 变更是内部维护的,不对外加锁
❓ 那什么情况下会锁二级索引?
只有当 查询条件使用了二级索引 时,才会在二级索引上加锁。
例如:
-- 会锁二级索引 idx_age 上 age=10 的记录(以及回表锁聚簇)
SELECT * FROM t WHERE age = 10 FOR UPDATE;
但你的语句是 通过主键更新,未使用二级索引作为查询条件,所以二级索引只是被“被动维护”,不参与加锁决策。
补充:唯一二级索引 vs 普通二级索引
- 如果 age 是 唯一二级索引,且你尝试将 age 改为一个 已存在的值(如 age=30 已被其他行占用),则: InnoDB 会在 唯一索引上检查冲突 可能对目标唯一索引项加插入意向锁(Insert Intention Lock)或间隙锁(Gap Lock) 但这属于 唯一约束检查,不是本例的情况(本例是普通索引,且无冲突)
本例中 age 是普通二级索引,且 20 是否存在不影响加锁行为。
✅ 总结
|
问题 |
答案 |
|
是否锁聚簇索引? |
✅ 是,对 id=100 的聚簇索引记录加 X 锁 |
|
是否锁二级索引 age? |
❌ 否,不加任何 Record Lock / Gap Lock |
|
二级索引如何更新? |
在持有聚簇索引 X 锁的前提下,原子地删除旧项、插入新项 |
|
会影响其他 age=10 的行吗? |
❌ 不会,完全无影响 |
|
是否可能死锁? |
可能(如果多个事务交叉更新不同主键但涉及一样二级索引范围),但本例单条语句不会 |
核心原则:
InnoDB 的行锁由查询路径决定,而非更新字段决定。
你用什么条件找数据,就锁什么索引;更新哪些字段,只影响索引维护,不额外加锁。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...


