没走索引的sql会锁住mysql扫描到的所有行;explain中type=all或index表示锁全表,key为null或rows值大说明锁范围广,联合索引顺序错误或where条件未匹配最左前缀会导致索引失效,主键或唯一索引更新锁粒度最小,覆盖索引可避免回表加锁。

没走索引的 SQL,锁多少行不是你说了算,是 MySQL 扫多少行它就锁多少行。
EXPLAIN 里 type=ALL 或 index 就等于锁全表
只要 EXPLAIN 显示 type 是 ALL(全表扫描)或 index(全索引扫描),基本可以断定:这条 SELECT FOR UPDATE、UPDATE 或 DELETE 会锁住所有被扫描到的聚簇索引记录——在大表上就是事实上的“锁全表”。
- 哪怕只想要改 1 行,WHERE 条件没索引,InnoDB 也得逐行比对,每扫一行就加一个行锁(还可能带间隙锁)
-
key列为NULL或空,说明压根没用上索引;rows值越大,锁的范围越吓人 - 特别注意
Extra出现Using where但没走索引——这往往是隐式转换(如user_id = '123'对 int 字段)或函数包裹(如WHERE DATE(create_time) = '2026-07-01')导致的
WHERE 条件字段必须建索引,且顺序不能错
联合索引不是“有就行”,顺序错一列,前面建的都白搭。锁范围直接由索引能截断多少扫描路径决定。
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
- 查询
WHERE tenant_id = 1 AND status = 'paid' AND create_time > '2026-06-01',索引应建为(tenant_id, status, create_time),而不是(create_time, tenant_id, status) - 如果只查
status = 'paid',哪怕有(tenant_id, status)索引,也完全用不上——最左前缀不匹配 - 非唯一二级索引 + 范围条件(如
status IN ('paid', 'shipped'))容易锁住多个索引项及其间隙,比主键查询锁得多得多
UPDATE/DELETE 尽量走主键或唯一索引
主键查询是锁粒度最小的路径:InnoDB 直接定位聚簇索引记录,只锁那一条,不带间隙锁(除非你写的是范围条件)。
- 把
UPDATE order SET status = 2 WHERE order_no = 'ORD20260702001'改成WHERE id = 12345,前提是id是主键 - 如果业务必须用
order_no,那就给它加唯一索引,否则即使走了索引,也可能锁住多条记录(因为非唯一索引需要回表确认,过程中可能触发额外锁定) - 避免
WHERE id IN (1,2,3,...,1000)这种大列表——优化器可能放弃使用索引,退化为全表扫描;拆成每批 50–100 个 ID 更稳
覆盖索引能减少回表带来的额外锁
就算 WHERE 走了索引,SELECT 的字段不在该索引里,InnoDB 还得回聚簇索引取数据——这个过程可能引入第二轮锁定,尤其在高并发下容易放大冲突。
- 例如校验版本号再更新:
SELECT id, version FROM t WHERE id = 123 FOR UPDATE,建联合索引INDEX idx_id_version (id, version),就能一步到位 - 绝对不要写
SELECT * FROM t WHERE id = 123 FOR UPDATE——表字段越多、含TEXT/BLOB,网络和内存开销越大,事务越慢,锁持有时间越长 - 联合索引字段顺序要按“WHERE 条件字段在前,SELECT 非 WHERE 字段在后”排,才能真正覆盖
锁不是配置出来的,是 SQL 执行路径决定的。看懂 EXPLAIN,盯住 type 和 rows,再回头检查索引定义和 WHERE 写法——这才是控制锁粒度最实在的入口。别等线上卡住才查,每次上线前跑一遍执行计划。










