Почему этот SQL-запрос намного медленнее, когда я индексирую столбцы?


14

У меня есть база данных sqlite с двумя таблицами, каждая из которых содержит 50000 строк, содержащих имена (фальшивых) людей. Я построил простой запрос, чтобы узнать, сколько существует имен (имя, отчество, фамилия), которые являются общими для обеих таблиц:

select count(*) from fakenames_uk inner join fakenames_usa on fakenames_uk.givenname=fakenames_usa.givenname and fakenames_uk.surname=fakenames_usa.surname and fakenames_uk.middleinitial=fakenames_usa.middleinitial;

Когда нет индексов, кроме первичных ключей (не относящихся к этому запросу), он выполняется быстро:

[james@marlon Downloads] $ time sqlite3 generic_data_no_indexes.sqlite "select count(*) from fakenames_uk inner join fakenames_usa on fakenames_uk.givenname=fakenames_usa.givenname and fakenames_uk.surname=fakenames_usa.surname and fakenames_uk.middleinitial=fakenames_usa.middleinitial;"
131

real    0m0.115s
user    0m0.111s
sys     0m0.004s

Но если я добавлю индексы к трем столбцам в каждой таблице (всего шесть индексов):

CREATE INDEX `idx_uk_givenname` ON `fakenames_uk` (`givenname` )
//etc.

потом он работает мучительно медленно

[james@marlon Downloads] $ time sqlite3 generic_data.sqlite "select count(*) from fakenames_uk inner join fakenames_usa on fakenames_uk.givenname=fakenames_usa.givenname and fakenames_uk.surname=fakenames_usa.surname and fakenames_uk.middleinitial=fakenames_usa.middleinitial;"
131

real    1m43.102s
user    0m52.397s
sys     0m50.696s

Есть ли какая-то рифма или причина для этого?

Вот результат EXPLAIN QUERY PLANдля версии без индексов:

0|0|0|SCAN TABLE fakenames_uk
0|1|1|SEARCH TABLE fakenames_usa USING AUTOMATIC COVERING INDEX (middleinitial=? AND surname=? AND givenname=?)

Это с индексами:

0|0|0|SCAN TABLE fakenames_uk
0|1|1|SEARCH TABLE fakenames_usa USING INDEX idx_us_middleinitial (middleinitial=?)

1
Ваши индексы не покрывают. Похоже, вы индексируете каждый столбец отдельно. Что происходит, когда вы создаете индекс покрытия, содержащий все три столбца в индексе ( middleinitial, surnameи givenname)?
Рэндольф Вест

@Randoph West Я понимаю, что вы имели в виду, но вы не используете правильную терминологию: «индекс покрытия» - это индекс, который включает в себя также выбранные столбцы. Например, для запроса SELECT c FROM t WHERE a=1 AND b=2индекс t(a,b,c)покрывает, но t(a,b)нет. Преимущество покрытия индексов состоит в том, что весь результат запроса может быть извлечен непосредственно из индекса, тогда как непокрытые индексы быстро находят соответствующие строки, но для выбора значений все равно необходимо обращаться к данным основной таблицы.
Артур Такка

Ответы:


15

В SQLite объединения выполняются как объединения вложенных циклов, т. Е. База данных проходит через одну таблицу, и для каждой строки выполняется поиск соответствующих строк из другой таблицы.

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

В этом случае есть три возможных индекса. Без какой-либо статистической информации (которая была бы создана при запуске ANALYZE ), база данных выбирает наименьшую, чтобы уменьшить количество операций ввода-вывода. Однако middleinitialиндекс бесполезен, потому что он не сильно уменьшает количество строк таблицы, которые необходимо извлечь; и дополнительный шаг по индексу фактически увеличивает необходимый ввод-вывод, потому что строки таблицы больше не читаются по порядку, а случайным образом.

Если индекс отсутствует, для поиска совпадающих строк потребуется полное сканирование второй таблицы для каждой строки первой таблицы. Это было бы так плохо, что, по оценкам базы данных, стоит создать, а затем удалить временный индекс только для этого запроса. Этот временный («АВТОМАТИЧЕСКИЙ») индекс создается для всех полей, используемых для поиска. Операция COUNT (*) не нуждается в значениях из каких-либо других столбцов, поэтому этот индекс является индексом покрытия , что означает, что нет необходимости на самом деле искать строку таблицы, соответствующую записи индекса, что экономит еще больше I / вывода.

Чтобы ускорить этот запрос, создайте этот индекс постоянно, чтобы больше не нужно было создавать временный:

CREATE INDEX uk_all_names ON fakenames_uk(surname, givenname, middleinitial);

EXPLAIN QUERY PLAN
SELECT count(*)
FROM fakenames_uk
JOIN fakenames_usa USING (givenname, middleinitial, surname);

0|0|1|SCAN TABLE fakenames_usa
0|1|0|SEARCH TABLE fakenames_uk USING COVERING INDEX uk_all_names (surname=? AND givenname=? AND middleinitial=?)

Индекс на surnameбольше не нужен, так как трехколонный индекс можно использовать для любых поисков в этом столбце.
Индекс givennameможет быть полезен, если вы будете выполнять поиск только по этому столбцу.
Индекс на middleinitialвсегда бесполезен: запрос, который ищет одно из 26 возможных значений, выполняется быстрее, если он просто просматривает всю таблицу.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.