UPDATE t SET age = 20 WHERE id = 100 如何加锁

内容分享2小时前发布 贵替
7 0 0

在 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 的行锁由查询路径决定,而非更新字段决定。
你用什么条件找数据,就锁什么索引;更新哪些字段,只影响索引维护,不额外加锁。

© 版权声明

相关文章

暂无评论

none
暂无评论...