如何提高SQL Count性能?

时间:2019-09-22 08:23:40

标签: mysql sql performance database-performance sqlperformance

我的一个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秒

2 个答案:

答案 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);
  • 第四,在多表查询的情况下,建议使用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;

答案 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。