Медленный запрос MYSQL с вложенными запросами с использованием COUNT

#php #mysql #performance

#php #mysql #Производительность

Вопрос:

Верно, я понятия не имею, почему, но выполнение этого запроса занимает более 6 секунд, все индексы настроены правильно, и если я запускаю каждый запрос отдельно, он отлично работает менее чем за 0,5 секунды для выполнения.

Вот запрос

 SELECT c.supplier_id, supplier_name, address1, address2, address3, address4, suppliertype, postcode, contact_name,
(SELECT COUNT(*)
    FROM supplier_questions q1
    WHERE c.supplier_id = q1.supplier_id AND q1.incomplete = '0') AS questions, 
IF (active=1,'Yes', IF (active=2, 'NCR Only','Inactive')) AS rated, 
(SELECT COUNT(*)
    FROM supplier_questions q2
    WHERE c.supplier_id = q2.supplier_id AND q2.reviewed = '1') AS reviewed, 
questapproved, 
ss.supplier_no AS supplier_no
FROM suppliers c
INNER JOIN supplier_site ss ON c.supplier_id = ss.supplier_id
WHERE c.supplier_id != '0' AND ss.site_id = '2'
GROUP BY c.supplier_id
ORDER BY c.supplier_name ASC
LIMIT 0, 20
  

Результаты запроса Explain следующие

 id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY ss  ref site_id,supplier_id site_id 4   const   1287    Using where; Using temporary; Using filesort
1   PRIMARY c   eq_ref  PRIMARY PRIMARY 4   ss.supplier_id  1   
3   DEPENDENT SUBQUERY  q2  ref supplier_id,reviewed    reviewed    4   const   263 Using where
2   DEPENDENT SUBQUERY  q1  ref supplier_id,incomplete  incomplete  4   const   254 Using where
  

Причина, по которой есть запросы count, заключается в том, что мне нужно знать количество строк из этих таблиц, это невозможно сделать в другом запросе, поскольку результаты также должны быть отсортированы по этим значениям : (

Комментарии:

1. Можете ли вы подтвердить, что на c.supplier_name есть индекс? Каково объяснение, когда вы удаляете два зависимых подзапроса?

2. У supplier_name нет индекса, но при добавлении он не изменяется.

3. Какие индексы у вас есть в таблице supplier_questions ?

4. «это невозможно сделать в другом запросе, поскольку результаты также должны быть отсортированы по этим значениям» — но запрос, который вы опубликовали, не является.

5. Денис сортировка по генерируется php-скриптом, сортировка по может не отображаться в запросе, но это можно изменить

Ответ №1:

Как удар в темноте, это выполняется быстрее? (У меня нет mysql для проверки синтаксиса, поэтому простите любые незначительные ошибки, но вы могли бы уловить идею)

 SELECT c.supplier_id, supplier_name, address1, address2, address3, address4, suppliertype, postcode, contact_name, questions, reviewed 
IF (active=1,'Yes', IF (active=2, 'NCR Only','Inactive')) AS rated,  
questapproved,  ss.supplier_no AS supplier_no 
FROM suppliers c 
INNER JOIN supplier_site ss ON c.supplier_id = ss.supplier_id 
inner join 
(SELECT supplier_id, sum(if(incomplete='0',1,0)) as questions,  sum(if(incomplete='1',1,0)) as reviewed FROM supplier_questions q1 group by supplier_id) as tmp
on c.supplier_id = tmp.supplier_id
WHERE c.supplier_id != '0' AND ss.site_id = '2' 
GROUP BY c.supplier_id 
ORDER BY c.supplier_name ASC LIMIT 0, 20 
  

Комментарии:

1. Этого не произойдет, потому что в конечном итоге он объединит все таблицы вместе. Странная вещь в его плане запроса заключается в том, что он не извлекает 20 лучших строк, используя c.supplier_name и применяя к этому join / подзапросы.

2. Выполняется быстрее, но возвращает только 3 строки вместо возможных 1000 строк

3. Возможно, вам не нужно внутреннее объединение, вам может понадобиться просто объединение, поскольку очевидно, что тогда включаются только элементы в обеих таблицах в их нынешнем виде.. @Denis, это не помогает, мне просто интересно, будет ли иметь какое-либо существенное значение подсчет вопросов и отзывов один раз.

4. Поиск ошибок, возможно, вы в чем-то правы, просто проверяю результаты вашего запроса, слегка измененные с помощью соединения по левому краю, и он пока кажется правильным, 0,124 секунды для получения 925 строк и 0,156 для извлечения 1257 строк

5. Поиск ошибок, возможно, вы к чему-то пришли, запрос возвращается с нулевыми вопросами и проверяется, попробуйте добавить IFNULL () к запросу, но все равно получите нулевые значения, есть идеи?

Ответ №2:

 FROM suppliers c
INNER JOIN supplier_site ss ON c.supplier_id = ss.supplier_id
WHERE c.supplier_id != '0' AND ss.site_id = '2'
GROUP BY c.supplier_id 
ORDER BY c.supplier_name ASC
  

Поскольку автоматически сгенерированные первичные ключи никогда не равны 0 (если не допущена ошибка при проектировании большой базы данных), вы можете удалить предложение c.supplier_id != ‘0’.

ss.site_id = ‘2’ должно быть в условии объединения для удобства чтения.

Похоже, что это должно соответствовать только одной строке в таблице supplier_site для каждого поставщика (если это ваше обычное отношение 1-N адресов объектов, т. Е. вы выбираете второй адрес каждого поставщика, возможно, ‘2’ соответствует ‘адресу выставления счетов’ или чему-то еще), поэтому ГРУППА ПО c.supplier_id бесполезна. Если GROUP BY действительно что-то делает, то запрос неверен, поскольку столбцы «address», которые предположительно поступают из таблицы supplier_site, будут получены из случайной строки.

Итак, вот упрощенный FROM (КУДА ушло) :

 FROM suppliers c
INNER JOIN supplier_site ss ON 
    (c.supplier_id = ss.supplier_id AND ss.site_id = '2')
ORDER BY c.supplier_name ASC
  

Я полагаю, у вас есть индекс на c.supplier_name, поэтому эта часть запроса должна быть очень быстрой.

Теперь попробуйте этот запрос :

 SELECT a.*,
    questapproved, 
    ss.supplier_no AS supplier_no,
    IF (active=1,'Yes', IF (active=2, 'NCR Only','Inactive')) AS rated, 
    sum( q.incomplete = '0') AS questions,
    sum( q.reviewed = '1' ) AS reviewed
FROM
(
    SELECT c.supplier_id, supplier_name, address1, address2, address3, address4, suppliertype, postcode, contact_name
    FROM suppliers c
    INNER JOIN supplier_site ss ON 
        (c.supplier_id = ss.supplier_id AND ss.site_id = '2')
    ORDER BY c.supplier_name ASC
    LIMIT 0, 20
) a
LEFT JOIN supplier_questions q ON (q.supplier_id = c.supplier_id)
GROUP BY c.supplier_id
ORDER BY c.supplier_name;
  

Комментарии:

1. Сгенерировал ряд ошибок SQL, после перемещения questapprove, supplier_no и rated в запрос FROM, на который он жалуется на c.supplier_id — неизвестный столбец в предложении ON

2. Ваш запрос работает, и он намного быстрее, однако не может упорядочиваться по вопросам, поскольку по какой-то причине он не упорядочивается

Ответ №3:

Если вы удалите вложенные запросы, вы получите что-то вроде этого:

 SELECT c.supplier_id, supplier_name, address1, address2, address3, address4, suppliertype, postcode, contact_name,
COUNT(IF (q1.incomplete = '0', '0', null)) AS questions, 
IF (active=1,'Yes', IF (active=2, 'NCR Only','Inactive')) AS rated, 
COUNT(IF (q1.reviewed = '1', '1', null)) AS reviewed,
questapproved, 
ss.supplier_no AS supplier_no
FROM suppliers c
INNER JOIN supplier_site ss ON c.supplier_id = ss.supplier_id
LEFT OUTER JOIN supplier_questions q1 ON c.supplier_id = q1.supplier_id
WHERE c.supplier_id != '0' AND ss.site_id = '2'
GROUP BY c.supplier_id
ORDER BY c.supplier_name ASC
LIMIT 0, 20
  

У меня нет доступной базы данных MySQL, поэтому в моем SQL могут быть ошибки.
Идея состоит в том, чтобы удалить вложенные запросы и заменить их внешним соединением
и используйте IF только для подсчета соответствующих строк.

Ответ №4:

Я бы сначала попытался реструктурировать, предварительно запросив агрегированные данные по количеству вопросов поставщика и проверив ИХ ОДИН РАЗ. Затем присоединитесь к остальным деталям. Используя ключевое слово STRAIGHT_JOIN, он должен обрабатываться в отображаемом порядке. Сначала это будет предварительная агрегация и использование этого в качестве основы для обратного соединения с поставщиками, а затем с сайтами поставщиков. Внешняя группа by не требуется, поскольку она все равно основана на идентификаторе поставщика. Однако присоединение к supplier_sites (ваш ss.supplier_no) будет означать, что у поставщика более одного местоположения. Означает ли это, что столбцы адреса и активного статуса происходят из этой таблицы?

Должно ли объединение вопросов быть связано с конкретным поставщиком и соответствующим местоположением сайта или нет?

Кроме того, поскольку предварительный запрос содержит предложение WHERE для supplier_id != ‘0’, в дальнейшем это не требуется, поскольку это будет основой обычного соединения с другими таблицами, что исключает их из результирующего набора.

 SELECT STRAIGHT_JOIN
      PreAggregate.supplier_id, 
      PreAggregate.supplier_name, 
      address1, 
      address2, 
      address3, 
      address4, 
      suppliertype, 
      postcode, 
      contact_name,
      PreAggregate.Questions,
      IF (active=1,'Yes', IF (active=2, 'NCR Only','Inactive')) AS rated, 
      PreAggregate.Reviewed,
      questapproved, 
      ss.supplier_no AS supplier_no
   FROM 
      (select 
             s1.Supplier_ID,
             s1.Supplier_Name,
             SUM( IF( q1.Incomplete = '0', 1, 0 )) Questions,
             SUM( IF( q1.Reviewed = '1', 1, 0 )) Reviewed
          from 
             suppliers s1
                join supplier_questions q1
                   ON s1.supplier_id = q1.supplier_id
          where
             s1.supplier_id != '0'
          group by
             s1.Supplier_ID 
          ORDER BY 
             s1.supplier_name ASC ) PreAggregate

       JOIN suppliers c
          ON PreAggregate.Supplier_ID = c.Supplier_ID

       JOIN supplier_site ss 
          ON PreAggregate.Supplier_ID = ss.supplier_id
          AND ss.Site_ID = '2'
  LIMIT 0, 20
  

Комментарии:

1. Один поставщик может предоставить нескольким сайтам пользователям, разрешающим доступ только к определенным сайтам, следовательно, supplier_sites, что касается запроса, возвращает 0 строк

2. @Neo, я переместил ОГРАНИЧЕНИЕ 0,20 во ВНЕШНЮЮ часть запроса… Я предполагаю, что внутренний запрос ограничивался 20, из которых ни один из сайтов поставщиков, прошедших квалификацию, не имел site_id = ‘2’…