MySQL查询性能异常缓慢求助:耗时超8分钟处理90万行
解决MySQL慢查询(90万数据耗时8分钟)的实操方案
哇,90万条数据跑8分钟确实够头疼的——尤其是你说索引都已经正确设置了,那咱们得从查询本身的结构和MySQL的特性来找突破口。先拆解下你的查询里几个拖慢速度的核心问题:
核心性能瓶颈分析
- 前缀模糊匹配(
%Panama%、%test%):MySQL的B-tree索引对%xxx这种前缀带通配符的LIKE完全无效,因为索引是按字符顺序排序的,没法快速定位开头不确定的字符串。你还写了%Panama%OR%PANAMA%,虽然可以改成LOWER(cinfo.COUNTRY) LIKE '%panama%'简化,但本质还是绕不开前缀模糊的性能问题。 - 多OR条件组合:查询里的
(CONTACT_EMAIL NOT LIKE ... OR CONTACT...)(虽然后面截断了)很容易让优化器放弃索引,尤其是当OR两边的字段不在同一个联合索引里时,直接触发全表扫描。 COUNT(DISTINCT cinfo.CONTACT_ID):JOIN之后如果存在重复的CONTACT_ID,DISTINCT会额外增加排序和去重的开销,数据量越大,这个开销越明显。
针对性优化方案
1. 替换前缀模糊匹配:用全文索引替代LIKE
如果你的业务是要匹配包含特定关键词的字符串,强烈建议用MySQL的全文索引,这比LIKE快几个数量级:
- 先给需要模糊匹配的字段创建全文索引:
CREATE FULLTEXT INDEX idx_cinfo_country_email ON cinfo(COUNTRY, CONTACT_EMAIL); - 然后修改查询条件,用
MATCH() AGAINST()替代LIKE:
全文索引会把字符串拆成关键词,快速定位包含目标词的记录,完全避免全表扫描。-- 替代原来的COUNTRY模糊匹配 MATCH(cinfo.COUNTRY) AGAINST('Panama' IN BOOLEAN MODE) -- 替代EMAIL的NOT LIKE条件(注意全文索引的NOT写法) AND NOT MATCH(cinfo.CONTACT_EMAIL) AGAINST('test engine' IN BOOLEAN MODE)
2. 拆分OR条件:用UNION ALL替代OR
OR条件是优化器的“天敌”之一,把OR拆成独立子查询再用UNION ALL合并,能让每个子query单独利用索引:
SELECT COUNT(DISTINCT combined.CONTACT_ID) FROM ( -- 第一个OR分支的查询 SELECT cinfo.CONTACT_ID FROM cinfo INNER JOIN LTocMapping ON cinfo.CONTACT_ID = LTocMapping.CONTACT_ID WHERE MATCH(cinfo.COUNTRY) AGAINST('Panama' IN BOOLEAN MODE) AND NOT MATCH(cinfo.CONTACT_EMAIL) AGAINST('test engine' IN BOOLEAN MODE) UNION ALL -- 第二个OR分支的查询(填你截断的部分) SELECT cinfo.CONTACT_ID FROM cinfo INNER JOIN LTocMapping ON cinfo.CONTACT_ID = LTocMapping.CONTACT_ID WHERE MATCH(cinfo.COUNTRY) AGAINST('Panama' IN BOOLEAN MODE) AND /* 这里补充你原来OR后面的条件 */ ) AS combined
如果两个子查询的结果有重复,可以把UNION ALL改成UNION(但UNION ALL更快,因为不需要去重),最后外层再做一次DISTINCT即可。
3. 优化COUNT(DISTINCT):先去重再计数
直接用COUNT(DISTINCT)有时候会让优化器先JOIN再去重,反而增加开销。可以先在子查询里筛选出唯一的CONTACT_ID,再计数:
SELECT COUNT(*) FROM ( SELECT DISTINCT cinfo.CONTACT_ID FROM cinfo INNER JOIN LTocMapping ON cinfo.CONTACT_ID = LTocMapping.CONTACT_ID WHERE /* 你的完整WHERE条件 */ ) AS unique_contacts
另外,先确认LTocMapping.CONTACT_ID是否有重复:如果LTocMapping里的CONTACT_ID是唯一的,那JOIN之后不会产生重复,直接去掉DISTINCT用COUNT(cinfo.CONTACT_ID)就行,能省掉大量去重开销。
4. 最后再检查一次索引
虽然你说索引都设置了,但还是要确认这两个关键点:
LTocMapping.CONTACT_ID必须有单独的索引,或者是联合索引的第一个字段,否则JOIN的时候会做全表扫描,这是很多人容易忽略的。- 如果用了全文索引,确保你的MySQL版本支持(MyISAM和InnoDB都支持,5.6+的InnoDB完全没问题)。
5. 进阶优化:临时表/分区表
如果上述方法还是达不到预期,可以试试:
- 临时表:先把符合
COUNTRY和CONTACT_EMAIL条件的cinfo数据导入临时表,再和LTocMappingJOIN——临时表数据量更小,处理速度更快。 - 分区表:如果你的数据是按
COUNTRY或者时间分区的,分区表能让MySQL只扫描目标分区的数据,减少扫描范围。
排查小技巧
用EXPLAIN ANALYZE(MySQL 8.0+支持)查看实际执行计划,重点看这几个字段:
type:如果是ALL就是全表扫描,说明索引没生效;Extra:如果有Using filesort或者Using temporary,说明有排序/临时表的开销,需要优化;rows:看看预估扫描的行数是不是和实际数据量匹配,判断优化器的统计信息是否准确。
内容的提问来源于stack exchange,提问作者Arunkumar Muthuvel
相关产品推荐
相关产品推荐

