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