mysql跨表update/delete按索引记录加行级锁,全表扫描会升级为间隙锁或临键锁;select...for update只锁显式引用的表;隐式锁等待不报死锁但致阻塞;批量操作应拆小事务并确保索引与执行计划正确。

跨表 UPDATE 或 DELETE 事务会锁哪些行?
MySQL 的 InnoDB 在跨表事务中不是“锁整张表”,而是按实际访问的索引记录加行级锁(RECORD LOCK),但前提是你用的是可重复读(REPEATABLE READ)隔离级别,且语句能走索引。如果 UPDATE t1 JOIN t2 ON ... 中任意一张表扫描了全表(比如缺少关联字段索引),就可能升级为间隙锁(GAP LOCK)甚至临键锁(NEXT-KEY LOCK),导致意外阻塞。
实操建议:
- 检查执行计划:
EXPLAIN FORMAT=TREE看是否走了索引,特别注意t2表的ON条件列是否有索引 - 避免在事务里写
UPDATE t1, t2 SET ... WHERE t1.id = t2.t1_id AND t2.status = 'pending'—— 若t2.status没索引,InnoDB 可能对全表 t2 加锁 - 跨表操作优先拆成单表语句 + 应用层控制,比一条 JOIN 更可控
SELECT ... FOR UPDATE 跨表时锁范围怎么算?
SELECT ... FOR UPDATE 在多表场景下只锁定 FROM 子句中**显式引用且实际读取到的行**,不会自动延伸到 JOIN 的另一张表——除非你把它也写进 FROM 或子查询中。很多人误以为 SELECT t1.* FROM t1 JOIN t2 ON t1.id = t2.t1_id FOR UPDATE 会锁 t2 的行,其实不会,t2 只是被用来过滤,不参与锁定。
常见错误现象:事务 A 执行该语句后,事务 B 仍能修改 t2 的对应行,导致 A 后续 UPDATE t1 时依据的 t2 数据已变。
实操建议:
- 真要锁 t2 的行,得显式写进
FROM:SELECT t1.*, t2.* FROM t1 JOIN t2 ON t1.id = t2.t1_id FOR UPDATE - 若只关心 t1 数据一致性,但逻辑依赖 t2 的某个字段值,应在事务开始前用
SELECT ... LOCK IN SHARE MODE先锁住 t2 的相关行 - 注意:MySQL 8.0.22+ 支持
FOR UPDATE OF t2语法,可精确指定锁定哪张表
死锁日志里看到 “WAITING FOR THIS LOCK TO BE GRANTED” 却没报错?
这是典型的隐式锁等待,不是死锁,但容易被忽略。InnoDB 死锁检测只触发于循环等待(A 等 B、B 等 A),而跨表事务中更常见的是线性阻塞:事务 A 锁了 t1.id=100 和 t2.id=200,事务 B 先锁 t2.id=200 再试图锁 t1.id=100 —— B 会卡在第二步,但不会报死锁,只是无限等待(直到 innodb_lock_wait_timeout 超时,默认 50 秒)。
BUYMALL2.30增加了: 商品销销售分析,进销库存分析,销售地区分析,客户类型分析 ,结算方式分析 ,产品销量分析。会员分析 ,会员地区分析 ,会员年龄分析,活跃会员 TOPBUYMALL系统将以丰富的功能、良好的稳定性,最大化节省SERVER资源,来得到用户的认可。系统特色:网上连锁分销模块、商品/列表HTML网页自动生成、繁简中文自动切换(试用版未含此项)。
性能影响明显:QPS 下降、连接堆积、监控里 innodb_row_lock_waits 持续上升。
实操建议:
- 查实时锁等待:
SELECT * FROM performance_schema.data_locks;
结合SELECT * FROM performance_schema.data_lock_waits;
- 降低风险:跨表更新尽量按固定顺序访问表(如总是先 t1 后 t2),避免不同事务反向加锁
- 应用层设置合理超时:
SET innodb_lock_wait_timeout = 10(会话级),让失败更快暴露
批量跨表操作为什么越跑越慢?
不是因为数据量大,而是锁粒度随事务增长而恶化。一个事务里执行 1000 次 UPDATE t1 JOIN t2,InnoDB 会累积持有所有涉及的行锁,直到 COMMIT。期间任何其他事务只要碰其中任意一行,就阻塞。更糟的是,长事务还会拖慢 purge 线程,导致 history_list_length 上升、MVCC 快照膨胀。
实操建议:
- 拆成小事务:每 100 行
COMMIT一次,用WHERE id BETWEEN ? AND ?分片,别用LIMIT(易漏行) - 避免在事务里做非 DB 操作(如 HTTP 请求、文件写入),否则锁持有时间不可控
- 确认 binlog 格式:
binlog_format = ROW是必须的,否则跨表 DML 在 statement 模式下可能主从不一致或锁范围异常
最常被忽略的一点:跨表事务的锁行为高度依赖执行计划,而执行计划又受统计信息、索引选择、优化器开关(如 optimizer_switch='index_merge=on')影响。上线前务必在生产镜像环境用真实数据压测锁表现,不能只看开发库的小表结果。










