MySQL: как индексировать предложение «ИЛИ» - PullRequest
17 голосов
/ 13 мая 2010

Я выполняю следующий запрос

SELECT COUNT(*)
FROM table
WHERE field1='value' AND (field2 >= 1000 OR field3 >= 2000)

Существует один индекс для field1 и другой, составленный для field2 и field3.

Я вижу, что MySQL всегда выбирает индекс field1, а затем делает соединение, используя два других поля, что очень плохо, поскольку ему нужно объединить 146 000 строк.

Предложения о том, как улучшить это? Спасибо

(РЕДАКТИРОВАТЬ ПОСЛЕ ПРЕДЛАГАЕМОГО РЕШЕНИЯ)

Основываясь на предложенном решении, я видел это на Mysql, когда играл с этим.

SELECT COUNT(*) FROM (SELECT * FROM table WHERE columnA = value1
UNION SELECT * FROM table WHERE columnB = value2) AS unionTable;

намного медленнее, чем выполнить:

SELECT COUNT(*)
FROM table
WHERE (columnA = value1 AND columnB = value2)
      OR (columnA = value1 AND columnC = value3)

Наличие двух составных индексов:

index1 (columnA,columnB)
index2 (columnA,columnC)

Достаточно интересно, что запрос Mysql «объяснить» запрос, который он принимает, всегда index1 в обоих случаях, а index2 не используется.

Если я изменю индексы на:

index1 (columnB,columnA)
index2 (columnC,columnA)

И запрос к:

SELECT COUNT(*)
FROM table
WHERE (columnB = value2 AND columnA = value1)
      OR (columnC = value3 AND columnA = value1)

Тогда я обнаружил, что Mysql работает быстрее всего.

Ответы [ 2 ]

24 голосов
/ 13 мая 2010

Типичный способ разбить предикаты OR - это UNION.

Обратите внимание, что ваш пример не вписывается в ваши индексы. Даже если вы исключите field1 из предиката, у вас будет field2 >= 1000 OR field3 >= 2000, который не может использовать индекс. Если бы у вас были индексы по (field1, field2) и (field1,field3) или field2 или field3 по отдельности, вы бы получили достаточно быстрый запрос.

SELECT COUNT(*) FROM
(SELECT * FROM table WHERE field1 = 'value' AND field2 >= 1000
UNION
SELECT * FROM table WHERE field1 = 'value' AND field3 >= 2000) T

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

Пример из реального мира. Имена столбцов и таблиц анонимны!

mysql> SELECT COUNT(*) FROM table;
+----------+
| COUNT(*) |
+----------+
|  3059139 |
+----------+
1 row in set (0.00 sec)

mysql> SELECT COUNT(*) FROM table WHERE columnA = value1;
+----------+
| COUNT(*) |
+----------+
|     1068 |
+----------+
1 row in set (0.00 sec)

mysql> SELECT COUNT(*) FROM table WHERE columnB = value2;
+----------+
| COUNT(*) |
+----------+
|      947 |
+----------+
1 row in set (0.00 sec)

mysql> SELECT COUNT(*) FROM table WHERE columnA = value1 OR columnB = value2;
+----------+
| COUNT(*) |
+----------+
|     1616 |
+----------+
1 row in set (9.92 sec)

mysql> SELECT COUNT(*) FROM (SELECT * FROM table WHERE columnA = value1
UNION SELECT * FROM table WHERE columnB = value2) T;
+----------+
| COUNT(*) |
+----------+
|     1616 |
+----------+
1 row in set (0.17 sec)

mysql> SELECT COUNT(*) FROM (SELECT * FROM table WHERE columnA = value1
UNION ALL SELECT * FROM table WHERE columnB = value2) T;
+----------+
| COUNT(*) |
+----------+
|     2015 |
+----------+
1 row in set (0.12 sec)
6 голосов
/ 14 мая 2010

Я новичок здесь, поэтому я не могу комментировать посты других людей, но это связано с постами Дэвида М. и soulmerge.

Временная таблица не нужна. UNION, предложенный Дэвидом М., не учитывает дважды, так как UNION подразумевает различное значение (то есть, если в одной половине объединения существует строка, игнорируйте ее в другой). Если бы вы использовали UNION ALL, вы бы получили две записи.

Поведение по умолчанию для UNION состоит в том, что повторяющиеся строки удаляются из результата. Необязательное ключевое слово DISTINCT не имеет никакого эффекта, кроме значения по умолчанию, поскольку оно также указывает удаление дублирующихся строк. При использовании необязательного ключевого слова ALL удаление повторяющихся строк не происходит, и результат включает все совпадающие строки из всех операторов SELECT.

http://dev.mysql.com/doc/refman/5.0/en/union.html

...