Почему тип сканирования изменяется с ALL на RANGE при использовании LIMIT для запросов SQL + Оптимизировать запрос - PullRequest
1 голос
/ 21 марта 2011

У меня есть этот запрос

SELECT l.licitatii_id, 
       l.nume, 
       l.data_publicarii, 
       l.data_limita 
FROM   licitatii_ue l 
       INNER JOIN domenii_licitatii dl 
         ON l.licitatii_id = dl.licitatii_id 
            AND dl.tip_licitatie = '2' 
       INNER JOIN domenii d 
         ON dl.domenii_id = d.domenii_id 
            AND d.status = 1 
            AND d.tip_domeniu = '1' 
WHERE  l.status = 1 
       AND Unix_timestamp(TIMESTAMPADD(DAY, 1, CAST(From_unixtime(l.data_limita) 
                                               AS DATE))) 
           < '1300683793' 
GROUP  BY l.licitatii_id 
ORDER  BY data_publicarii DESC 

Объяснить вывод:

+-----+--------------+--------+---------+-------------------------------------+----------+----------+---------------------------+-------+-----------+----------------------------------------------+
| id  | select_type  | table  | type    | possible_keys                       | key      | key_len  | ref                       | rows  | filtered  | Extra                                        |
| 1   | SIMPLE       | d      | ALL     | PRIMARY,key_status_tip_domeniu      | NULL     | NULL     | NULL                      | 120   | 85.83     | Using where; Using temporary; Using filesort |
| 1   | SIMPLE       | dl     | ref     | PRIMARY,tip_licitatie,licitatii_id  | PRIMARY  | 4        | web61db1.d.domenii_id     | 6180  | 100.00    | Using where; Using index                     |
| 1   | SIMPLE       | l      | eq_ref  | PRIMARY                             | PRIMARY  | 4        | web61db1.dl.licitatii_id  | 1     | 100.00    | Using where                                  |
+-----+--------------+--------+---------+-------------------------------------+----------+----------+---------------------------+-------+-----------+----------------------------------------------+

Как вы видите type = ALL для таблицы d

сейчас, если я добавлю LIMIT 100 к запросу

план меняется на range:

+-----+--------------+--------+---------+-------------------------------------+-------------------------+----------+---------------------------+-------+-----------+----------------------------------------------+
| id  | select_type  | table  | type    | possible_keys                       | key                     | key_len  | ref                       | rows  | filtered  | Extra                                        |
| 1   | SIMPLE       | d      | range   | PRIMARY,key_status_tip_domeniu      | key_status_tip_domeniu  | 9        | NULL                      | 103   | 100.00    | Using where; Using temporary; Using filesort |
| 1   | SIMPLE       | dl     | ref     | PRIMARY,tip_licitatie,licitatii_id  | PRIMARY                 | 4        | web61db1.d.domenii_id     | 6180  | 100.00    | Using where; Using index                     |
| 1   | SIMPLE       | l      | eq_ref  | PRIMARY                             | PRIMARY                 | 4        | web61db1.dl.licitatii_id  | 1     | 100.00    | Using where                                  |
+-----+--------------+--------+---------+-------------------------------------+-------------------------+----------+---------------------------+-------+-----------+----------------------------------------------+

Почему это происходит?
Можно ли оптимизировать этот запрос больше, оба запроса занимают 13 секунд.

Схема таблицы видна на gist github

Ответы [ 3 ]

1 голос
/ 21 марта 2011

Ах, загадки оптимизатора запросов многочисленны и непостижимы ...

На первый взгляд, наиболее очевидная вещь для оптимизации - это предложение

AND Unix_timestamp(TIMESTAMPADD(DAY, 1, CAST(From_unixtime(l.data_limita) 
                                               AS DATE))) 

.

, в зависимости от количества записей в таблице licitatii_ue, это выглядит как дорогостоящая операция и обходит любые доступные индексы.

1 голос
/ 23 марта 2011

MySQL выбирает domenii в качестве ведущей таблицы для объединения.

Эта таблица фильтруется по (status, tip_domeniu) = (1, 1).

Это не очень избирательное условие,поэтому обычно сканирование полной таблицы с фильтрацией предпочтительнее сканирования индекса.

Мы можем видеть, что MySQL ожидает, что 120 записей будет возвращено из domanii, для которых это условие будет выполнено.

Когда вы добавляете LIMIT, число ожидаемых записей должно быть уменьшено, и MySQL считает сканирование индекса более эффективным для этого.

Обратите внимание, что это условие:

Unix_timestamp(TIMESTAMPADD(DAY, 1, CAST(From_unixtime(l.data_limita) AS DATE))) < '1300683793'

не может быть sargable, поэтому вы лишаете оптимизатор использовать индекс для data_limita.

Создайте следующие индексы:

licitatii_ue (status, data_limita)
licitatii_ue (status, data_publicarii)

и перепишите запрос следующим образом:

SELECT l.licitatii_id, 
       l.nume, 
       l.data_publicarii, 
       l.data_limita 
FROM   licitatii_ue l 
JOIN   domenii_licitatii dl 
ON     l.licitatii_id = dl.licitatii_id 
       AND dl.tip_licitatie = '2' 
JOIN   domenii d 
ON     dl.domenii_id = d.domenii_id 
       AND d.status = 1 
       AND d.tip_domeniu = '1' 
WHERE  l.status = 1
       AND l.data_limita < FROM_UNIXTIME(((1300683793 - 86400) div 86400) * 86400)
GROUP BY
       l.licitatii_id 
ORDER BY
       data_publicarii DESC 
0 голосов
/ 21 марта 2011

ALL - сканирование таблицы, range - сканирование диапазона (из-за LIMIT).Ничего плохого в этом нет, на самом деле это также вызывает использование ключа (key_status_tip_domeniu).

Причина, по которой вы работаете медленно, заключается, скорее всего, в том, что вы используете ORDER BY data_publicarii DESC (это легко проверить,просто отбросьте ORDER BY и сравните тест с запросом; ожидайте несколько порядков).

Mysql признает (в столбце «Дополнительные» объяснения), что использует файловую сортировку (необходимо для упорядочения, потому что он не может или делаетне знаю, как использовать индекс).Добавление еще одного индекса в микс может помочь, особенно если вы подтвердите, что ORDER BY делает его медленным.

EDIT На самом деле, в вашем запросе есть кардинальный грех:

Unix_timestamp(TIMESTAMPADD(DAY, 1, CAST(From_unixtime(l.data_limita) AS DATE))) < '1300683793'

Избегайте применения каких-либо функций к значениям поля, если вы можете применить их к константе.Так что поменяйте его и переписайте как

l.data_limita < some_function('1300683793')

Как бы ни было завершено some_function, оно будет вычислено только один раз.Mysql планировщик будет знать, что это постоянная.То, как вы это написали, заставит mysql применить unix_timestamp, timestampadd, cast и from_unixtime к значению data_limita из каждой строки.Теперь в системах, связанных с вводом / выводом, это обычно просто сжигает некоторые дополнительные циклы ЦП, ожидая вращения дисков (однако, это может стать значительным, ваша система может быть привязана к ЦП, и это просто плохо).Самое большое отличие состоит в том, что вы теряете возможность использовать индекс для data_limita.

Наконец, все ваши индексы являются индексами одиночного поля, а mysql выполняет некоторое объединение индексов , но не является звездным в нем,Возможно, вы захотите попробовать создать индексы, которые охватывают все ваши условия и порядок сортировки (в порядке селективности для целевого запроса).

...