Синтаксис MySQL и производительность «ИЛИ» - PullRequest
2 голосов
/ 21 марта 2009

Этот запрос MySQL работает просто отлично

SELECT o.id 
FROM descriptions_programs d, titles_programs t, programs o
WHERE (d.object_id=o.id 
        AND MATCH (d.text) AGAINST ('+china' IN BOOLEAN MODE) 
        AND d.current=1)
AND   (t.object_id=o.id 
        AND MATCH (t.text) AGAINST ('+china' IN BOOLEAN MODE) 
        AND t.current=1)

Но если я заменю одно И на ИЛИ, запрос будет выполняться очень долго. (Я должен убить его.):

SELECT o.id 
FROM descriptions_programs d, titles_programs t, programs o
WHERE (d.object_id=o.id 
        AND MATCH (d.text) AGAINST ('+china' IN BOOLEAN MODE) 
        AND d.current=1)
OR    (t.object_id=o.id 
        AND MATCH (t.text) AGAINST ('+china' IN BOOLEAN MODE) 
        AND t.current=1)

Почему это? Не зацикливайтесь на простоте Китая. Я просто упростил это ради отладки. Кроме того, если я запускаю только один из тестов MATCH AGAINST, он работает нормально, так что оба в порядке сами по себе. Мне кажется, что я случайно вызвал огромное объединение, ИСПОЛЬЗУЯ ИЛИ, но я просто не понимаю. Ранее я использовал тест n IN на UNION из двух подвыборов, который работал, но это также должно работать. Верно?

Обновление: по запросу Бобинса. Это не супер медленно, но на ~ 500 мс, это не так быстро, как при использовании UNION, как обсуждается здесь .

mysql> explain SELECT o.id
    -> FROM programs o
    -> JOIN titles_programs t ON t.object_id=o.id
    -> JOIN descriptions_programs d ON d.object_id=o.id
    -> WHERE MATCH (d.text) AGAINST ('+china' IN BOOLEAN MODE) AND d.current=1
    -> OR MATCH (t.text) AGAINST ('+china' IN BOOLEAN MODE) AND t.current=1
    -> ;
+----+-------------+-------+-------+

----------------+----------------+---------+----------------------+--------+-------------+
| id | select_type | table | type  | possible_keys  | key            | key_len | ref                  | rows   | Extra       |
+----+-------------+-------+-------+----------------+----------------+---------+----------------------+--------+-------------+
|  1 | SIMPLE      | o     | index | PRIMARY        | PRIMARY        | 4       | NULL                 | 148666 | Using index | 
|  1 | SIMPLE      | d     | ref   | object_current | object_current | 4       | haystack.o.id        |      1 |             | 
|  1 | SIMPLE      | t     | ref   | object_current | object_current | 4       | haystack.d.object_id |      1 | Using where | 
+----+-------------+-------+-------+----------------+----------------+---------+----------------------+--------+-------------+

Ответы [ 2 ]

2 голосов
/ 21 марта 2009

Ответ Джейсона точен. Кроме того, я бы попытался использовать более современный синтаксис объединения ANSI, чтобы снять нагрузку с предложения WHERE, чтобы избежать путаницы:

SELECT o.id
FROM programs o
JOIN titles_programs t ON t.object_id=o.id
JOIN descriptions_programs d ON d.object_id=o.id
WHERE MATCH (d.text) AGAINST ('+china' IN BOOLEAN MODE) AND d.current=1
OR MATCH (t.text) AGAINST ('+china' IN BOOLEAN MODE) AND t.current=1

Это остановит случайное перекрестное соединение, вызывающее комбинаторный взрыв; Я ожидаю, что он будет работать в разумные сроки, если база данных не будет действительно огромной.

Если нет, можете ли вы опубликовать результаты EXPLAIN SELECT из вышеперечисленного? Предположительно, один или оба полнотекстовых индекса не используются. Я, конечно, мог бы вообразить, что оптимизатор запросов не может использовать второй полнотекстовый индекс, делая что-то вроде попытки «заполнить» строки, которые не соответствуют первому полнотекстовому запросу, вместо перехода прямо к индексу или что-то в этом роде.

Обычно, когда вы хотите использовать полнотекстовый индекс для двух столбцов в комбинации, вы создаете один индекс для обоих столбцов. В любом случае это будет намного быстрее. Однако это будет означать, что вы должны поместить названия и описания в одну таблицу. Это может быть не так сложно: так как полный текст работает только для таблиц MyISAM (и вы обычно не хотите, чтобы ваши канонические данные в таблицах MyISAM), вы можете хранить точную копию ваших данных в должным образом нормализованных таблицах InnoDB с дополнительной таблицей MyISAM содержит только раздетую и остановленную поисковую приманку.

Если ничего из этого не принесет пользы ... ну, я думаю, я бы вернулся к упомянутому вами UNIONing в сочетании с фильтром уровня приложения для удаления дублирующихся идентификаторов.

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

Ваша проблема в том, что соединения между o и d и t должны происходить во ВСЕХ случаях. То есть вам нужно:

SELECT o.id 
FROM descriptions_programs d, titles_programs t, programs o
WHERE d.object_id=o.id AND t.object_id=o.id AND
(
        MATCH (d.text) AGAINST ('+china' IN BOOLEAN MODE) 
        AND d.current=1
) OR (  MATCH (t.text) AGAINST ('+china' IN BOOLEAN MODE) 
        AND t.current=1
)

Почему? Потому что в вашем первом запросе вы можете игнорировать эти круглые скобки - все объединено и таблицы объединены в порядке. В вашем втором запросе это не так.

Рассмотрим, что на самом деле делает база данных: она берет «все строки в t» и пересекает их со «всеми строками в d», поэтому t*d строк. Обычно вы используете объединения (как я), чтобы ограничить это линейным списком допустимых строк.

Но в вашем запросе OR вы разрешаете любой строке соответствовать o вместо обоим соответствию o, поэтому для каждой строки в одной таблице, которая соответствует вам ' также выберите все строки в другой таблице .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...