如何提高 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;