You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:37:46