我的一个SQL查询非常慢。我需要在总共近300,000条记录的表上进行COUNT,但是查询要花8秒才能返回结果。
SELECT oc_subject.*,
(SELECT COUNT(sid) FROM oc_details
WHERE DATE(oc_details.created) > DATE(NOW() - INTERVAL 1 DAY)
AND oc_details.sid = oc_subject.id) as totalDetails
FROM oc_subject
WHERE oc_subject.status='1'
ORDER BY created DESC LIMIT " . (int)$start . ", " . (int)$limit;
以这种方式:总共50次,查询8.5837秒
SELECT oc_subject.*
FROM oc_subject
WHERE oc_subject.status='1'
ORDER BY created DESC LIMIT 0, 50
无计数:总计50,查询0.0457秒
答案 0 :(得分:3)
可能有很多改进之处
oc_subject
表上的外部查询(主SELECT查询)。通过使用复合索引(status, created)
,此查询可以利用ORDER BY
Optimization的优势。因此,定义以下索引(如果尚未定义):ALTER TABLE oc_subject ADD INDEX (status, created);
Date()
子句中的列上使用WHERE
函数,因此获取Count的子查询不是Sargeable。因此,它无法正确使用索引。此外,DATE(oc_details.created) > DATE(NOW() - INTERVAL 1 DAY)
只是意味着您正在尝试考虑那些在当前日期(今天)创建的详细信息。可以简单地写成:oc_details.created >= CURRENT_DATE
。这里的技巧是,即使created
列为日期时间类型,MySQL也会隐式将CURRENT_DATE
的值转换为CURRENT_DATE 00:00:00
。
因此将内部子查询更改为以下内容:
SELECT COUNT(sid)
FROM oc_details
WHERE oc_details.created >= CURRENT_DATE
AND oc_details.sid = oc_subject.id
oc_details
表上定义了适当的索引时,内部子查询的所有改进才有用。因此,在oc_details
表上定义以下综合(和覆盖)索引:(sid, created)
。请注意,这里的列顺序很重要,因为created
是一个Range条件,因此它应该出现在末尾。因此,定义以下索引(如果尚未定义):ALTER TABLE oc_details ADD INDEX (sid, created);
因此,一旦定义了所有索引(如上所述),就可以使用以下查询:
SELECT s.*,
(SELECT COUNT(d.sid)
FROM oc_details AS d
WHERE d.created >= CURRENT_DATE
AND d.sid = s.id) as totalDetails
FROM oc_subject AS s
WHERE s.status='1'
ORDER BY s.created DESC LIMIT " . (int)$start . ", " . (int)$limit;
答案 1 :(得分:2)
您可以尝试对由sid和date()分组的单个子查询使用联接,而不是几个子查询(oc_subject表中的每一行之一)
SELECT oc_subject.*, b.count_sid
FROM oc_subject
INNER JOIN (
SELECT sid, DATE(oc_details.created) date_created, COUNT(sid) count_sid
FROM oc_details
GROUP BY sid, DATE(oc_details.created)
) b on b.sid = oc_subject.id
AND b.date_created > DATE(NOW() - INTERVAL 1 DAY)
WHERE oc_subject.status='1'
ORDER BY created DESC LIMIT " . (int)$start . ", " . (int)$limit;
无论如何都要小心使用php var来限制..确保使用清理过的值以避免sqlinjection。