慢 MySQL 查询,EXPLAIN 显示 Using temporary;使用文件排序

Slow MySQL query, EXPLAIN shows Using temporary; Using filesort

这个查询:

EXPLAIN SELECT ppi_loan.customerID,
               loan_number,
               CONCAT(forename, ' ', surname) AS agent,
               name,
               broker,
              (SELECT timestamp
               FROM ppi_sar_status
               WHERE history = 0
                   AND (status = 10 || status = 13)
                   AND ppi_sar_status.loanID = ppi_loan.loanID) AS ppi_unsure_date,
              fosSent,
              letterSent,
              (SELECT timestamp
               FROM ppi_ques_status
               WHERE status = 1 
                   AND ppi_ques_status.loanID = ppi_loan.loanID
               ORDER BY timestamp DESC LIMIT 1) AS sent_date,
               ppi_ques_status.timestamp
FROM ppi_loan
LEFT JOIN ppi_assignments ON ppi_assignments.customerID = ppi_loan.customerID
LEFT JOIN italk.users ON italk.users.id = agentID
LEFT JOIN ppi_ques_status ON ppi_ques_status.loanID = ppi_loan.loanID
JOIN ppi_lenders ON ppi_lenders.id = ppi_loan.lender
JOIN ppi_status ON ppi_status.customerID = ppi_loan.customerID
JOIN ppi_statuses ON ppi_statuses.status = ppi_status.status
   AND ppi_ques_status.status = 1
   AND ppi_ques_status.history = 0
   AND (cc_type = '' || (cc_type != '' AND cc_accepted = 'no'))
   AND ppi_loan.deleted = 'no'
   AND ppi_loan.customerID != 10
GROUP BY ppi_loan.customerID, loan_number

非常慢,这里是 EXPLAIN 查询的所有结果

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY ppi_ques_status ref loanID,status,history   status  3   const   91086   Using where; Using temporary; Using filesort
1   PRIMARY ppi_loan    eq_ref  PRIMARY,customerID  PRIMARY 8   ppimm.ppi_ques_status.loanID    1   Using where
1   PRIMARY ppi_lenders eq_ref  PRIMARY PRIMARY 4   ppimm.ppi_loan.lender   1   Using where
1   PRIMARY ppi_assignments eq_ref  customerID  customerID  8   ppimm.ppi_loan.customerID   1   
1   PRIMARY users   eq_ref  PRIMARY PRIMARY 8   ppimm.ppi_assignments.agentID   1   
1   PRIMARY ppi_status  ref status,customerID   customerID  8   ppimm.ppi_loan.customerID   6   
1   PRIMARY ppi_statuses    eq_ref  PRIMARY PRIMARY 4   ppimm.ppi_status.status 1   Using where; Using index
3   DEPENDENT SUBQUERY  ppi_ques_status ref loanID,status   loanID  8   func    1   Using where; Using filesort
2   DEPENDENT SUBQUERY  ppi_sar_status  ref loanID,status,history   loanID  8   func    2   Using where

为什么要扫描这么多行,为什么 "Using temporary; Using filesort"? 我无法删除任何子查询,因为我需要它们生成的所有结果

正如评论中已经提到的,查询缓慢的主要原因是您似乎只有单列索引,而您需要 multi-column 个索引来覆盖连接、过滤器和分组依据。

此外,您的查询还有另外 2 个问题:

  1. 即使您仅在 2 个字段上 group by,但 select 列表中列出了其他几个字段,而不受聚合函数的约束,例如 min(). MySQL 确实允许此类查询在某些 sql 模式设置下为 运行,但它们仍然违反 sql 标准并且可能会产生意想不到的副作用,除非你真的知道什么你在做什么。

  2. 您在 left join 中左侧 table 的联接条件中对 ppi_loan table 进行了筛选。由于左连接的性质,这些记录不会从结果集中消除,但 MySQL 不会在它们上连接任何值。这些条件应移至 where 子句。

我要创建的索引:

  • ppi_sar_status:loanID、状态、历史字段上的 multi-column 索引 - 我会考虑将其移至连接部分,因为 table 不存在

  • ppi_ques_status: multi-column loanID、status、timestamp 字段上的索引 - 这将同时支持子查询和连接。请记住,子查询在解释中也有文件排序。

  • ppi_loan:作为 customerID 上的一个 multi-column 索引,loan_number 字段支持 group by 子句,因此避免文件排序为最低限度。您可以考虑根据它们对该索引的选择性将其他字段添加到连接条件中。

我也不确定为什么您在联接中有最后 2 个状态 table,因为您没有从它们中检索任何值。如果您使用这些 table 来消除某些记录,请考虑使用 exists() 子查询而不是连接。在联接中 MySQL 需要从所有联接的 table 中获取数据,而在 exists() 子查询中它只会检查结果集中是否至少存在 1 条记录,而不会从中检索任何实际数据底层 tables.