Как индексировать таблицу MySQL InnoDB для запроса с помощью ключа select where (здесь некоторые значения)?

У меня есть сервер mariadb 10.3 и следующая таблица (с использованием механизма хранения InnoDB):

create table if not exists token (
   `token` bigint unsigned not null,
    `uid` smallint unsigned not null default 0,
    `nham` int default 0,
    `nspam` int default 0,
    `timestamp` int unsigned default 0
) Engine=InnoDB;

create index token_idx1 on token(token);
create index token_idx2 on token(uid);

В таблице токенов ~ 900 тыс. строк, и я хотел бы выполнить следующий запрос с 2-300 числами в предложении IN ( ):

select token, nham, nspam from token where token in (1,2,3,4,...);

Теперь проблема: запрос выполняется очень медленно, и он просто не будет использовать token_idx1:

+------+-------------+-------+------+---------------+------------+---------+-------+--------+-------------+
|  id  | select_type | table | type | possible_keys |     key    | key_len |  ref  |  rows  |    Extra    |
+------+-------------+-------+------+---------------+------------+---------+-------+--------+-------------+
|   1  |    SIMPLE   | token |  ref |   token_idx1  | token_idx1 |    2    | const | 837534 | Using where |
+------+-------------+-------+------+---------------+------------+---------+-------+--------+-------------+

Поскольку столбец токена проиндексирован, я удивлен, что объяснение выбора говорит, что оптимизатор не интересуется token_idx1 (и запрос занимает много времени, ~ 30 секунд из-за полного сканирования таблицы).

Как решить проблему? Я знаю, что я мог бы использовать USE INDEX(token_idx1) в запросе, но я бы решил это без такого взлома.


person Zsolti Vagyok    schedule 20.07.2018    source источник
comment
Я не могу себе представить, что оператор in, состоящий из 300 элементов, будет быстрым. Можете ли вы поместить их в таблицу и вместо этого использовать соединение?   -  person P.Salmon    schedule 20.07.2018
comment
Не уверен, что это поможет, но я провел некоторое тестирование на своей таблице с аналогичным индексом в столбце. Я тестировал только с предложением IN, но получил интересные результаты. Когда я использовал список только с действительными совпадениями, он шел очень быстро, но когда я добавлял недействительные, он, кажется, сканировал всю таблицу. EXPLAIN проверил это поведение.   -  person UncleCarl    schedule 20.07.2018
comment
Что-то не так — key_len = 2, а token — это BIGINT UNSIGNED (8 байт). Пожалуйста, проверьте CREATE TABLE, индексы и объяснение.   -  person Rick James    schedule 20.07.2018
comment
И вам действительно нужен PRIMARY KEY на столе.   -  person Rick James    schedule 20.07.2018
comment
К сожалению, я не могу поместить эти 300 элементов в отдельную таблицу. Если столбец проиндексирован (как есть), то я ожидаю, что запрос даже с 300 элементами будет довольно быстрым. Смотрите мое решение ниже.   -  person Zsolti Vagyok    schedule 23.07.2018


Ответы (2)


Решение состоит в том, чтобы переписать запрос. Таким образом, хотя такой запрос отстой в производительности:

выберите токен, nham, nspam из токена, где токен в (1,2,3,4,...);

Следующий запрос выполняется быстро, как и должно быть (даже если некоторые значения токенов не существуют в таблице):

выберите токен, nham, nspam из токена, где токен=1 или токен=2 или токен=3 или ...;

Итак, проблема решена, хотя я до сих пор не понимаю, почему у оптимизатора проблемы с первым запросом.

В любом случае, спасибо за все ваши мысли, идеи и вклад, которые привели меня к обходному пути.

person Zsolti Vagyok    schedule 23.07.2018
comment
В таблице ~8-900 тыс. записей. Обычно я ищу несколько сотен токенов за раз и, возможно, возвращаю половину из них (т.е. других токенов в таблице не существует). - person Zsolti Vagyok; 24.07.2018
comment
Я спросил, потому что видел довольно много вопросов о медленных запросах, но авторы забыли, что возврат нескольких сотен тысяч записей медленный по нескольким причинам :) Просто подсказка, я понятия не имею, знаете ли вы об этом или нет: с EXPLAIN есть также EXPLAIN EXTENDED и profiling. Для профилирования: SET PROFILING = 1; SELECT ... (your query here); SHOW PROFILE FOR QUERY 1; SET PROFILING = 0;. Обычно это помогает отследить, какая часть сервера может быть узким местом. - person N.B.; 24.07.2018

удалите существующий индекс token_idx1 и создайте заново с помощью

CREATE INDEX token_idx1 ON token(token) USING BTREE;
CREATE INDEX token_idx2 ON token(uid) USING BTREE;
person D P    schedule 20.07.2018
comment
Можете ли вы объяснить, почему это может помочь. - person P.Salmon; 20.07.2018
comment
К сожалению, это не поможет, потому что тип индекса уже BTREE. - person Zsolti Vagyok; 23.07.2018