Как может 'WHERE column LIKE "% expression%"' работать лучше, чем MATCH (column) AGAINST ("expression") в MySQL? - PullRequest
3 голосов
/ 05 июля 2011

Я столкнулся с серьезным узким местом производительности MySQL, которое не могу понять и устранить. Вот структуры таблиц, индексы и количество записей (потерпите меня, это только две таблицы):

mysql> desc elggobjects_entity;
+-------------+---------------------+------+-----+---------+-------+
| Field       | Type                | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+-------+
| guid        | bigint(20) unsigned | NO   | PRI | NULL    |       |
| title       | text                | NO   | MUL | NULL    |       |
| description | text                | NO   |     | NULL    |       |
+-------------+---------------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> show index from elggobjects_entity;
+--------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table              | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| elggobjects_entity |          0 | PRIMARY  |            1 | guid        | A         |      613637 |     NULL | NULL   |      | BTREE      |         |
| elggobjects_entity |          1 | title    |            1 | title       | NULL      |         131 |     NULL | NULL   |      | FULLTEXT   |         |
| elggobjects_entity |          1 | title    |            2 | description | NULL      |         131 |     NULL | NULL   |      | FULLTEXT   |         |
+--------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
3 rows in set (0.00 sec)

mysql> select count(*) from elggobjects_entity;
+----------+
| count(*) |
+----------+
|   613637 |
+----------+
1 row in set (0.00 sec)

mysql> desc elggentity_relationships;
+--------------+---------------------+------+-----+---------+----------------+
| Field        | Type                | Null | Key | Default | Extra          |
+--------------+---------------------+------+-----+---------+----------------+
| id           | int(11)             | NO   | PRI | NULL    | auto_increment |
| guid_one     | bigint(20) unsigned | NO   | MUL | NULL    |                |
| relationship | varchar(50)         | NO   | MUL | NULL    |                |
| guid_two     | bigint(20) unsigned | NO   | MUL | NULL    |                |
| time_created | int(11)             | NO   |     | NULL    |                |
+--------------+---------------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
mysql> show index from elggentity_relationships;
+--------------------------+------------+--------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+
| Table                    | Non_unique | Key_name     | Seq_in_index | Column_name  | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------------------------+------------+--------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+
| elggentity_relationships |          0 | PRIMARY      |            1 | id           | A         |    11408236 |     NULL | NULL   |      | BTREE      |         |
| elggentity_relationships |          0 | guid_one     |            1 | guid_one     | A         |        NULL |     NULL | NULL   |      | BTREE      |         |
| elggentity_relationships |          0 | guid_one     |            2 | relationship | A         |        NULL |     NULL | NULL   |      | BTREE      |         |
| elggentity_relationships |          0 | guid_one     |            3 | guid_two     | A         |    11408236 |     NULL | NULL   |      | BTREE      |         |
| elggentity_relationships |          1 | relationship |            1 | relationship | A         |    11408236 |     NULL | NULL   |      | BTREE      |         |
| elggentity_relationships |          1 | guid_two     |            1 | guid_two     | A         |    11408236 |     NULL | NULL   |      | BTREE      |         |
+--------------------------+------------+--------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+
6 rows in set (0.00 sec)

mysql> select count(*) from elggentity_relationships;
+----------+
| count(*) |
+----------+
| 11408236 |
+----------+
1 row in set (0.00 sec)

Теперь я хотел бы использовать INNER JOIN для этих двух таблиц и выполнить полнотекстовый поиск.

Запрос:

SELECT
        count(DISTINCT o.guid) as total
FROM
        elggobjects_entity o
INNER JOIN
        elggentity_relationships r on (r.relationship="image" AND r.guid_one = o.guid)
WHERE
        ((MATCH (o.title, o.description) AGAINST ('scelerisque' )))

Это дало мне 6 минут (!) Времени отклика.

С другой стороны, этот

SELECT
        count(DISTINCT o.guid) as total
FROM
        elggobjects_entity o
INNER JOIN
        elggentity_relationships r on (r.relationship="image" AND r.guid_one = o.guid)
WHERE
        ((o.title like "%scelerisque%") OR (o.description like "%scelerisque%"))

вернул то же значение счетчика за 0,02 секунды.

Как это возможно? Что мне здесь не хватает? (Информация MySQL: mysql Ver 14.14 Distrib 5.1.49, для debian-linux-gnu (x86_64) с использованием readline 6.1)

EDIT

ОБЪЯСНЕНИЕ первого запроса (с использованием сопоставления .. против) дает:

+----+-------------+-------+----------+-----------------------+--------------+---------+-------+------+-------------+
| id | select_type | table | type     | possible_keys         | key          | key_len | ref   | rows | Extra       |
+----+-------------+-------+----------+-----------------------+--------------+---------+-------+------+-------------+
|  1 | SIMPLE      | r     | ref      | guid_one,relationship | relationship | 152     | const | 6145 | Using where |
|  1 | SIMPLE      | o     | fulltext | PRIMARY,title         | title        | 0       |       |    1 | Using where |
+----+-------------+-------+----------+-----------------------+--------------+---------+-------+------+-------------+

во время второго запроса (используя LIKE "% ..%"):

+----+-------------+-------+--------+-----------------------+--------------+---------+---------------------+------+-------------+
| id | select_type | table | type   | possible_keys         | key          | key_len | ref                 | rows | Extra       |
+----+-------------+-------+--------+-----------------------+--------------+---------+---------------------+------+-------------+
|  1 | SIMPLE      | r     | ref    | guid_one,relationship | relationship | 152     | const               | 6145 | Using where |
|  1 | SIMPLE      | o     | eq_ref | PRIMARY               | PRIMARY      | 8       | elgg1710.r.guid_one |    1 | Using where |
+----+-------------+-------+--------+-----------------------+--------------+---------+---------------------+------+-------------+

1 Ответ

2 голосов
/ 05 июля 2011

Объединив свой опыт и результаты EXPLAIN, кажется, что полнотекстовый индекс не так полезен, как вы ожидаете в данном конкретном случае. Это зависит от конкретных данных в вашей базе данных, от структуры базы данных и / или конкретного запроса.

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

Вывод EXPLAIN показывает, что оптимизатор запросов к базе данных решил использовать индексы для relationship и title. Фильтр отношений уменьшает таблицу elggentity_relationships до 6145 строк. И фильтр заголовков уменьшает таблицу elggobjects_entity до 72697 строк. Затем MySQL необходимо объединить эти таблицы (6145 x 72697 = 446723065 операций фильтрации) без использования какого-либо индекса, поскольку индексы уже использовались для фильтрации. В этом случае это может быть слишком много. MySQL может даже принять решение сохранить промежуточные вычисления на жестком диске, пытаясь сохранить достаточно свободного места в памяти.

Теперь давайте посмотрим на другой запрос. Он использует relationship и PRIMARY KEY (из таблицы elggobjects_entity) в качестве своих индексов. Фильтр отношений уменьшает таблицу elggentity_relationships до 6145 строк. При объединении этих таблиц по индексу PRIMARY KEY в результате получается только 3957 строк. Это не так много для последнего фильтра (т.е. LIKE "%scelerisque%"), даже если индекс НЕ используется для этой цели вообще.

Как видите, скорость во многом зависит от индексов, выбранных для запроса. Таким образом, в данном конкретном случае индекс PRIMARY KEY гораздо полезнее, чем индекс title с полным текстом, поскольку PRIMARY KEY оказывает большее влияние на уменьшение результата, чем title.

MySQL не всегда умен, чтобы установить правильные индексы. Мы можем сделать это вручную, используя такие пункты, как IGNORE INDEX (index_name), FORCE INDEX (index_name) и т. Д.

Но в вашем случае проблема заключается в том, что если мы используем MATCH() AGAINST() в запросе, тогда требуется полнотекстовый индекс, потому что MATCH() AGAINST() вообще не работает без полнотекстового индекса. Так что это основная причина, по которой MySQL выбрал неверные индексы для запроса.

UPDATE

ОК, я провел некоторое расследование.

Во-первых, вы можете попытаться заставить MySQL использовать индекс guid_one вместо relationship в таблице elggentity_relationships: USE INDEX (guid_one).

Но для еще большей производительности, я думаю, вы можете попытаться создать один индекс для композиции из двух столбцов (guid_one, membership). Текущий индекс guid_one очень похож, но для 3 столбцов, а не для 2. В этом запросе используются только 2 столбца. На мой взгляд, после создания индекса MySQL должен автоматически использовать правильный индекс. Если нет, заставьте MySQL использовать его.

Примечание. После создания индекса не забудьте удалить старую инструкцию USE INDEX из запроса, поскольку это может помешать запросу использовать вновь созданный индекс. :)

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