EXPLAIN可直接用于UPDATE语句(MySQL 8.0.19+),通过type(非ALL)、key(非NULL)、rows(远小于总数)三列判断是否走索引,避免全表扫描导致的行锁爆炸。

UPDATE语句没走索引,不是“可能锁表”,而是几乎必然锁住所有扫描行——在大表上效果等同于锁全表。 根本原因不是语句本身,而是InnoDB的行锁必须依附于索引记录;没索引,就只能逐条扫描聚簇索引并加锁,锁范围直接爆炸。
EXPLAIN确认UPDATE是否真走了索引
MySQL 8.0.19+ 支持直接对 UPDATE 用 EXPLAIN,这是最可靠的验证方式:
EXPLAIN UPDATE users SET status = 2 WHERE city = 'Beijing';
重点看三列:type(应为 range/ref/const,非 ALL)、key(应显示实际使用的索引名,非 NULL)、rows(预估扫描行数,应远小于总行数)。
- 如果
type是ALL,说明全表扫描已发生,锁风险极高 -
key为NULL常见于隐式类型转换:比如user_id是INT,但写成WHERE user_id = '123' - 函数操作字段也会让索引失效:
WHERE DATE(create_time) = '2024-01-01'→ 应改用create_time BETWEEN '2024-01-01 00:00:00' AND '2024-01-01 23:59:59'
为WHERE条件字段添加高效索引
建索引不是加了就行,顺序和选择直接影响锁范围:
解析 MySQL 8.0 的 DDL 脚本,并将表结构导出为 Excel 格式。适用于用户需将 MySQL 的 `CREATE TABLE` 语句转换为结构化的 Excel 文件场景,导出内容包含字段名、数据类型、长度及精度等信息。支持多张表的批量处理,每张表对应 Excel 中的一个独立工作表。适用于数据库文档编写、数据字典生成及模式分析等任务。
- 优先建在
WHERE中的**等值查询字段**上,尤其是高选择性列(如order_no、user_id),避免建在低区分度字段(如status、is_deleted)上 - 复合索引要注意字段顺序:若查询是
WHERE category = ? AND created_at > ?,索引应为(category, created_at),而非反过来 - 不要依赖
UPDATE ... LIMIT N来“控制影响行数”——它不减少扫描量,WHERE仍会全表扫,锁照样加满 - 建索引后务必用
EXPLAIN再验证,避免因统计信息未更新导致误判
分批UPDATE时如何避免游标错位和重复执行
批量更新不能靠 LIMIT OFFSET,必须用有索引的单调字段做游标分段:
UPDATE users SET status = 2 WHERE id > 10000 AND id
- 游标字段必须有索引(最好是主键或唯一索引),且值单调递增/递减
- 每次执行后记录最大
id(如SELECT MAX(id) FROM ...),作为下一批的起点 - 每批单独事务,执行完立刻
COMMIT,锁立即释放 - 批大小建议 1000–5000 行:太小事务开销大;太大单次锁持有时间长,仍可能阻塞其他业务
SELECT FOR UPDATE锁多行的常见误判
SELECT ... FOR UPDATE 本身不决定锁多少行,真正起作用的是它的 WHERE 条件能否精确命中索引:
- 用唯一索引(如主键、
order_no)能精准锁单行:SELECT * FROM order WHERE order_no = 'ORD123' FOR UPDATE - 用非唯一二级索引(如
status)且无ORDER BY,可能锁住所有匹配记录 + 间隙,范围远超预期 - 仅需校验后更新时,可先用
SELECT ... LOCK IN SHARE MODE读取再判断,比直接FOR UPDATE更轻量(但需注意幻读) - 若查询涉及回表(索引不覆盖所有
SELECT字段),可能额外锁更多行,应尽量让索引覆盖查询所需字段
最容易被忽略的一点:索引建完不等于问题消失。InnoDB 的锁行为高度依赖执行计划是否稳定,而执行计划又受数据分布、统计信息、隔离级别共同影响。上线前必须在真实数据量级下压测,并用 SHOW ENGINE INNODB STATUS 观察实际锁记录数,不能只信 EXPLAIN 的预估值。










