MySQL慢查询随时间变慢直至超时的优化咨询
环境信息
数据库服务器
- Server: 127.0.0.1 via TCP/IP
- 服务器类型:MariaDB
- 服务器版本:10.4.27-MariaDB
Web服务器
Apache/2.4.54 (Win64)
PHP版本:8.1.12
phpMyAdmin版本:5.2.0
CPU使用率:10-15%
内存使用率:30-40%
问题描述
为支持实时展示板,系统每10秒或30秒会执行十几个load data查询。其他SELECT查询运行正常,耗时均在2秒以内,但有一个查询初期正常,随后逐渐延迟,最终完全超时。执行SHOW FULL PROCESSLIST;时,可见该查询的多个实例处于“Sending data”状态,当Time达到20秒时会从进程列表消失。
待优化查询语句
SELECT * FROM `sfdc_chat_csat` LEFT JOIN `sfdc_chat_review` ON `sfdc_chat_csat`.`ChatKey__c` = `sfdc_chat_review`.`ChatKey` WHERE `sfdc_chat_csat`.`ChatRating__c` != '' AND `sfdc_chat_csat`.`ChatRating__c` <= 6 AND `sfdc_chat_review`.`manager_review` IS NULL AND ( `sfdc_chat_csat`.`Id` IN ( 'a0b4z00000W48UyAAJ', 'a0b4z00000W48V8AAJ', 'a0b4z00000W4CGvAAN', 'a0b4z00000W4CMAAA3', 'a0b4z00000W4CRjAAN', 'a0b4z00000W4CUTAA3', 'a0b4z00000W4CW5AAN', 'a0b4z00000W4DAoAAN', 'a0b4z00000W4CEpAAN', 'a0b4z00000W4CTaAAN' ) ) AND MONTH(`CreatedDate`) >= MONTH(now());
EXPLAIN 查询结果
+-----+-------------+-------------------+----------+-----------------------+---------+ | id | select_type | table | type | possible_keys | key | +-----+-------------+-------------------+----------+-----------------------+---------+ | 1 | SIMPLE | sfdc_chat_csat | range | PRIMARY,ChatRating__c | PRIMARY | | 1 | SIMPLE | sfdc_chat_review | eq_ref | PRIMARY | PRIMARY | +-----+-------------+-------------------+----------+-----------------------+---------+ continued +---------+--------------------------------------+------+-------------------------+ | key_len | ref | rows | Extra | +---------+--------------------------------------+------+-------------------------+ | 122 | NULL | 10 | Using where | | 122 | dashboard.sfdc_chat_csat.ChatKey__c | 1 | Using where; Not exists | +---------+--------------------------------------+------+-------------------------+
表结构
sfdc_chat_csat(含10k条数据)
+---------------------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------------------------+--------------+------+-----+---------+-------+ | Id | varchar(40) | NO | PRI | NULL | | | IsDeleted | tinyint(1) | NO | | NULL | | | ChatKey__c | varchar(40) | NO | UNI | NULL | | | ChatRating__c | varchar(40) | NO | MUL | NULL | | | Chat_ButtonId | varchar(40) | NO | MUL | NULL | | | Chat_Button__c | varchar(40) | NO | | NULL | | | Chat_Transcript_Number__c | varchar(40) | NO | | NULL | | | Chat_Transcript_Owner__c | varchar(40) | NO | | NULL | | | Comments_del__c | varchar(255) | NO | | NULL | | | CreatedDate | datetime | NO | MUL | NULL | | | Customer_Email_Address__c | varchar(40) | NO | | NULL | | | Customer_First_Name__c | varchar(40) | NO | | NULL | | | Customer_Last_Name__c | varchar(40) | NO | | NULL | | | NPS_Score__c | varchar(40) | NO | | NULL | | +---------------------------+--------------+------+-----+---------+-------+
sfdc_chat_review(含100条数据)
+-------------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------------+--------------+------+-----+---------+-------+ | ChatKey | varchar(40) | NO | PRI | NULL | | | chatTranscriptId | varchar(30) | NO | | NULL | | | manager | varchar(30) | NO | | NULL | | | manager_review | tinyint(1) | NO | MUL | NULL | | | manager_verdict | varchar(30) | NO | | NULL | | | false_reason_code | varchar(30) | NO | | NULL | | | manager_comments | varchar(500) | NO | | NULL | | +-------------------+--------------+------+-----+---------+-------+
优化建议
修复日期过滤的索引失效问题
原查询中MONTH(CreatedDate) >= MONTH(now())会导致CreatedDate的索引无法被使用,因为函数包裹了字段。改成范围查询:CreatedDate >= DATE_FORMAT(NOW(), '%Y-%m-01 00:00:00')这样可以直接利用
CreatedDate上的索引,快速筛选当月数据。优化字符串类型的数值比较
ChatRating__c是varchar类型,但查询中用了<=6的数值比较,会触发隐式类型转换,导致ChatRating__c的索引失效。建议:- 优先修改
ChatRating__c字段类型为INT(如果存储的确实是数值); - 若暂时无法改字段类型,查询中显式转换:
CAST(ChatRating__c AS UNSIGNED) <=6。
- 优先修改
创建覆盖复合索引
针对sfdc_chat_csat表,创建包含查询所有过滤条件和关联字段的复合索引,避免回表查询:CREATE INDEX idx_csat_rating_created_id_chatkey ON sfdc_chat_csat (ChatRating__c, CreatedDate, Id, ChatKey__c);这个索引可以覆盖
ChatRating__c过滤、CreatedDate范围、Id IN匹配,以及关联用的ChatKey__c,大幅提升查询效率。*替换SELECT 为具体字段
查询所有字段会增加数据传输量和内存消耗,尤其是在"Sending data"阶段。只列出实际需要的字段,减少不必要的数据加载。调整JOIN逻辑为NOT EXISTS
原查询的LEFT JOIN加sfdc_chat_review.manager_review IS NULL,等价于查找sfdc_chat_csat中没有对应sfdc_chat_review记录或manager_review为空的行,改用NOT EXISTS写法通常更高效:SELECT -- 仅列出需要的字段 sc.Id, sc.ChatKey__c, sc.ChatRating__c, sc.CreatedDate FROM `sfdc_chat_csat` sc WHERE sc.ChatRating__c != '' AND CAST(sc.ChatRating__c AS UNSIGNED) <= 6 AND sc.Id IN ('a0b4z00000W48UyAAJ', 'a0b4z00000W48V8AAJ', ...) AND sc.CreatedDate >= DATE_FORMAT(NOW(), '%Y-%m-01 00:00:00') AND NOT EXISTS ( SELECT 1 FROM `sfdc_chat_review` scr WHERE scr.ChatKey = sc.ChatKey__c AND scr.manager_review IS NOT NULL );优化load data的影响
频繁的load data可能占用数据库资源,导致查询延迟。可以尝试:- 使用
LOAD DATA INFILE ... LOW_PRIORITY,让导入操作等待查询完成后执行; - 调整导入批次,避免多个load data同时执行;
- 确保导入使用InnoDB引擎的批量插入优化(如关闭自动提交,导入后再提交)。
- 使用
内容的提问来源于stack exchange,提问作者user3436467

