如何提高 SQL 计数性能?
How can I improve SQL Count performance?
我的一个 SQL 查询非常慢。我需要计算 table 总共有近 300,000 条记录,但查询 return 结果需要 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秒
您可以尝试在按 sid 和 date()
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。
许多可能的改进:
- 首先,让我们谈谈
oc_subject
table 上的外部查询(主要SELECT 查询)。此查询可以通过使用复合索引来利用 ORDER BY
Optimization 的优势:(status, created)
。因此,定义以下索引(如果尚未定义):
ALTER TABLE oc_subject ADD INDEX (status, created);
- 其次,您获取 Count 的子查询不是 Sargeable,因为在
WHERE
子句内的列上使用了 Date()
函数。因此,它无法正确使用索引。
此外,DATE(oc_details.created) > DATE(NOW() - INTERVAL 1 DAY)
仅表示您正在尝试考虑在当前日期(今天)创建的那些详细信息。这可以简单地写成: oc_details.created >= CURRENT_DATE
。这里的技巧是,即使 created
列是 datetime 类型,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
table 上定义了适当的索引时,内部子查询的所有改进才会有用。因此,在 oc_details
table 上定义以下复合(和覆盖)索引:(sid, created)
。请注意,列的顺序在这里很重要,因为 created
是一个范围条件,因此它应该出现在最后。因此,定义以下索引(如果尚未定义):
ALTER TABLE oc_details ADD INDEX (sid, created);
- 第四,在多table查询的情况下,建议使用Aliasing,为了代码清晰(增强可读性),并避免明确的行为。
因此,一旦定义了所有索引(如上所述),就可以使用以下查询:
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;
我的一个 SQL 查询非常慢。我需要计算 table 总共有近 300,000 条记录,但查询 return 结果需要 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秒
您可以尝试在按 sid 和 date()
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。
许多可能的改进:
- 首先,让我们谈谈
oc_subject
table 上的外部查询(主要SELECT 查询)。此查询可以通过使用复合索引来利用ORDER BY
Optimization 的优势:(status, created)
。因此,定义以下索引(如果尚未定义):
ALTER TABLE oc_subject ADD INDEX (status, created);
- 其次,您获取 Count 的子查询不是 Sargeable,因为在
WHERE
子句内的列上使用了Date()
函数。因此,它无法正确使用索引。
此外,DATE(oc_details.created) > DATE(NOW() - INTERVAL 1 DAY)
仅表示您正在尝试考虑在当前日期(今天)创建的那些详细信息。这可以简单地写成: oc_details.created >= CURRENT_DATE
。这里的技巧是,即使 created
列是 datetime 类型,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
table 上定义了适当的索引时,内部子查询的所有改进才会有用。因此,在oc_details
table 上定义以下复合(和覆盖)索引:(sid, created)
。请注意,列的顺序在这里很重要,因为created
是一个范围条件,因此它应该出现在最后。因此,定义以下索引(如果尚未定义):
ALTER TABLE oc_details ADD INDEX (sid, created);
- 第四,在多table查询的情况下,建议使用Aliasing,为了代码清晰(增强可读性),并避免明确的行为。
因此,一旦定义了所有索引(如上所述),就可以使用以下查询:
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;