Как я могу улучшить производительность SQL Count? - PullRequest
2 голосов
/ 22 сентября 2019

Один из моих SQL-запросов очень медленный.Мне нужно СЧИТАТЬ на таблицу с общим количеством записей около 300 000, но запрос возвращает 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 ]

3 голосов
/ 22 сентября 2019

Возможны многие улучшения:

  • Во-первых, давайте поговорим о внешнем запросе (основной запрос SELECT) к таблице oc_subject.Этот запрос может использовать ORDER BY Оптимизация с использованием составного индекса: (status, created).Итак, определите следующий индекс (если он еще не определен):
ALTER TABLE oc_subject ADD INDEX (status, created);
  • Во-вторых, ваш подзапрос для получения Count не является Sargeable из-за использования Date() функция в столбце внутри WHERE предложения.Из-за этого он не может правильно использовать индексы.

Кроме того, 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 Таблица.Итак, определите следующий составной (и охватывающий) индекс в таблице 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;
2 голосов
/ 22 сентября 2019

Вместо нескольких подзапросов (по одной на каждую строку в вашей таблице oc_subject) вы можете попробовать использовать объединение в одном подзапросе, сгруппированном по 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.

...