Почему MySQL не всегда использует индекс для запроса выбора? - PullRequest
0 голосов
/ 27 мая 2020

У меня есть две таблицы пользователей и статей в базе данных.

Записи в моей таблице пользователей и статей приведены ниже:

+----+--------+
| id | name   |
+----+--------+
|  1 | user1  |
|  2 | user2  |
|  3 | user3  |
+----+--------+


+----+---------+----------+
| id | user_id | article  |
+----+---------+----------+
|  1 |       1 | article1 |
|  2 |       1 | article2 |
|  3 |       1 | article3 |
|  4 |       2 | article4 |
|  5 |       2 | article5 |
|  6 |       3 | article6 |
+----+---------+----------+

Указаны ниже запросы и уважаемые EXPLAIN output.

EXPLAIN SELECT * FROM articles WHERE user_id = 1;

+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | articles | NULL       | ALL  | user_id       | NULL | NULL    | NULL |    6 |    50.00 | Using where |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+



EXPLAIN SELECT * FROM articles WHERE user_id = 2;
+----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | articles | NULL       | ref  | user_id       | user_id | 5       | const |    2 |   100.00 | NULL  |
+----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------+


EXPLAIN SELECT * FROM articles WHERE user_id = 3;
+----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | articles | NULL       | ref  | user_id       | user_id | 5       | const |    1 |   100.00 | NULL  |
+----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------+

Глядя на планы EXPLAIN для моих запросов select, кажется, что запросы не всегда используют индексы.

В случае

, когда user_id равно 1, он не использует ключ и сканирует всю таблицу.

в противном случае он использует ключ user_id и сканирует только несколько строк.

Не могли бы вы объяснить, почему запросы не всегда используют здесь индекс?

1 Ответ

1 голос
/ 30 мая 2020

В показываемых вами запросах (вероятно) задействовано два дерева BT. Одно дерево BTree для данных, отсортированное по PRIMARY KEY, которое, как я полагаю, равно id. Другой для INDEX на user_id (опять же, я предполагаю). Когда InnoDB (который, как я полагаю, вы используете) создает «вторичный индекс», такой как INDEX(user_id), он молча прикрепляет PK таблицы. Таким образом, фактически он становится BTree, содержащим два столбца: (user_id, id) и отсортированный по этой паре.

Когда оптимизатор просматривает SELECT * FROM t WHERE user_id=?, он исследует таблицу и обнаруживает, что "много" строк было user_id = 1, и не во многих строках были другие значения, которые вы пробовали.

Оптимизатор имеет два (или более) способа оценки подобных запросов -

План A (используйте индекс ): Вот что он делает:

  1. Просмотрите BTree индекса, чтобы найти первое вхождение user_id=2.
  2. Там он найдет id.
  3. Используйте этот id, чтобы развернуть BTree данных и найти * (как в SELECT *).
  4. Перейдите к следующей записи в индексном BTree. (На самом деле это довольно эффективно, так как это действительно «дерево B +»; см. Википедию.)
  5. Если найдено, l oop возвращается к шагу 2. Если не найден (больше нет записей индекса с user_id=2), выход.

План B (не используйте индекс - полезно для user_id=1):

  1. Просто пройдитесь по данным BTree в любом порядок.
  2. Пропустить любую строку, в которой нет user_id=1.

Перепрыгивание назад и вперед между двумя BT-деревьями стоит чего-то. Оптимизатор решил, что в вашем случае =1 необходимо просмотреть более 20% таблицы, и решил, что план Б будет быстрее. То есть он намеренно игнорировал ИНДЕКС.

Есть много факторов, которые Оптимизатор не может или неправильно оценивает, но обычно выбор между этими двумя планами приводит к более быстрому исполнение. (Ваша таблица слишком мала, чтобы надежно измерить разницу.)

Другие «планы» - если индекс «покрывает», нет необходимости использовать данные BTree. Если существует ORDER BY, который можно использовать, тогда Оптимизатор вероятно будет использовать план A, чтобы избежать «сортировки файлов». (См. EXPLAIN SELECT ...) Et c.

...