IN子句超200值时MySQL放弃索引改全表扫描,应改用带主键的临时表+INNER JOIN并指定驱动表;需批量插入、确保目标表有索引、显式DROP临时表防复用冲突。

IN子句超过200个值,基本就别指望走索引了——MySQL会直接放弃索引,改用全表扫描。这不是配置问题,是优化器在eq_range_index_dive_limit(默认200)阈值触发后主动降级成本估算方式导致的。临时表+JOIN不是“听起来高级”的替代方案,而是唯一能稳住执行计划、复用索引、避免硬解析崩盘的实操路径。
为什么CREATE TEMPORARY TABLE必须带PRIMARY KEY
不加主键的tmp_ids表在JOIN时大概率触发type: ALL(全表扫描),因为MySQL无法高效定位单条记录。临时表没索引,JOIN就退化成嵌套循环,性能比原IN还差。
-
CREATE TEMPORARY TABLE tmp_ids (id BIGINT UNSIGNED PRIMARY KEY)—— 强制建唯一索引,驱动表顺序可控 - 若ID是字符串(如UUID),必须用
CHAR(36)+PRIMARY KEY,不能只建普通INDEX - 别用
ENGINE=MEMORY存超5万行,max_heap_table_size默认16MB,插满就报ERROR 1114 (HY000): The table 'tmp_ids' is full
INSERT要批量,但别一次塞10万行
单条INSERT INTO tmp_ids VALUES (1)跑1000次,网络往返和解析开销远超批量插入。但一次性VALUES (1),(2),...,(100000)又可能触发Packets larger than max_allowed_packet错误或客户端截断。
- 每批控制在
500–1000行:例如INSERT INTO tmp_ids VALUES (1),(2),...,(1000) - 如果ID来自另一个查询结果,直接用
CREATE TEMPORARY TABLE tmp_ids AS SELECT id FROM ... WHERE ...,省去建表+插入两步 - 确保目标表
target_table.id字段本身有索引,否则JOIN再快也白搭
JOIN写法必须明确驱动表,且不能用LEFT JOIN
写成SELECT t.* FROM target_table t LEFT JOIN tmp_ids i ON t.id = i.id WHERE i.id IS NOT NULL看似等价,实际会让优化器误判驱动表,常导致type: ALL扫target_table。正确姿势是让临时表当驱动表。
- 必须写成:
SELECT t.* FROM target_table t INNER JOIN tmp_ids i ON t.id = i.id - 加上
STRAIGHT_JOIN更保险:SELECT STRAIGHT_JOIN t.* FROM tmp_ids i INNER JOIN target_table t ON t.id = i.id - 别在JOIN条件里加额外WHERE,比如
AND t.status = 'active'——这会让tmp_ids失去驱动资格,优化器可能翻车
最容易被忽略的是显式DROP TEMPORARY TABLE tmp_ids。虽然会话结束自动销毁,但连接池复用时,残留的临时表会导致下一次调用报Table 'tmp_ids' already exists。哪怕只是加一行DROP TEMPORARY TABLE IF EXISTS tmp_ids,也能避开生产环境半夜告警。



















