UPDATE语句有索引却全表扫描,主因是优化器误判选择性、复合条件顺序不匹配索引、隐式类型转换或函数导致索引失效;FORCE INDEX可强制走指定索引,但需紧贴表名后、配合WHERE使用,并验证key_len和rows是否显著下降。

UPDATE 语句走全表扫描,不是因为没索引,而是优化器“看走眼”了——FORCE INDEX 能强制它回头,但用错会更糟。
为什么 UPDATE 明明有索引还全表扫描
常见现象是:WHERE 条件字段都有单列索引,EXPLAIN 显示 key 字段非 NULL,但实际执行时 Rows_examined 接近总行数,慢日志里 Lock_time 远超 Query_time。根本原因不是索引不存在,而是:
- 优化器误判选择性:比如
status IN (0,1,2)占比突然从 5% 涨到 70%,统计信息未及时更新,优化器仍按旧分布估算,认为走索引不如扫表快 - 复合条件顺序与索引不匹配:建了
INDEX idx_org_pid (org_id, product_id),但 WHERE 写成WHERE product_id = ? AND org_id = ?,MySQL 可能弃用该索引(尤其在 5.7 之前) - 隐式类型转换:
org_id是VARCHAR,但传入整型参数,触发全表扫描;或字段带函数如WHERE DATE(created_at) = '2024-01-01' - UPDATE 涉及的列触发回表+锁升级:即使走了索引,若索引不覆盖所有被修改字段,InnoDB 需回聚簇索引取旧值做 MVCC 判断,过程中可能扩大锁范围
FORCE INDEX 在 UPDATE 中怎么写才生效
MySQL 的 FORCE INDEX 提示只对 SELECT 和 UPDATE(含 DELETE)有效,但语法位置和约束比 SELECT 更严格:
- 必须紧跟在表名后、WHERE 前,格式为:
UPDATE t1 FORCE INDEX (idx_name) SET ... WHERE ... - 括号内填的是索引名,不是列名;且该索引必须存在,否则报错
ERROR 1176 (HY000): Key 'xxx' doesn't exist in table 't1' - 不能跨 JOIN 使用:如
UPDATE t1 JOIN t2 ON ... FORCE INDEX (...) WHERE ...是非法的,FORCE 只作用于紧邻的那张表 - 必须配合 WHERE 条件:无 WHERE 的
UPDATE ... FORCE INDEX (...)会被忽略,仍全表更新 - 示例正确写法:
UPDATE partner_common_repay_plan FORCE INDEX (idx_org_pid_outer) SET cur_term = '6' WHERE org_id = 'XXX' AND product_id = 'XXX' AND outer_loan_id = 'XXX';
FORCE INDEX 后还要验证什么
加了 FORCE INDEX 不等于问题解决,必须立刻验证实际执行效果:
- 用
EXPLAIN FORMAT=TRADITIONAL看key是否为你指定的索引名,key_len是否符合预期(比如联合索引三列,key_len应接近各列字节和),rows是否显著下降(理想是百级而非百万级) - 查
INFORMATION_SCHEMA.INNODB_TRX和INNODB_LOCK_WAITS,确认事务持有的lock_structs和row_locks数量是否收敛——如果仍锁几百万行,说明索引虽被选中,但 WHERE 范围过大或索引覆盖不足 - 检查该索引是否为覆盖索引:UPDATE 修改的字段,最好都在索引里(如
INDEX idx_cover (org_id, product_id, outer_loan_id, cur_term)),避免回表带来的额外锁和 I/O - 留意
ANALYZE TABLE是否近期执行过;若数据分布突变(如某状态记录暴涨),强制索引可能让性能雪上加霜,此时应先更新统计信息再评估
比 FORCE INDEX 更稳的替代方案
FORCE INDEX 是手术刀,但容易切偏;多数场景优先考虑更可持续的解法:
- 重排联合索引字段顺序,严格匹配高频查询条件顺序,例如 WHERE 总是
org_id = ? AND product_id = ? AND outer_loan_id = ?,就建INDEX idx_org_pid_outer (org_id, product_id, outer_loan_id) - 用
USE INDEX替代FORCE INDEX:它只是建议优化器优先考虑这些索引,不强行压制其他路径,在统计信息准确时更安全 - 拆分大范围 UPDATE:比如把
WHERE status IN (0,1,2,3,4)改成多次小批量,每次限定id BETWEEN ? AND ?,配合主键索引,可控锁粒度 - 确认字段类型一致性:PHP 传参时用
string对应VARCHAR字段,避免 PDO 自动转整型引发隐式转换
真正危险的不是没加 FORCE INDEX,而是加了之后没验证 rows 和锁行数——索引被用了,不代表扫描范围小,也不代表锁得少。


















