Чому цей запит sqlite набагато повільніше, коли я індексую стовпці?


14

У мене база даних sqlite з двома таблицями, кожна з яких містить 50 000 рядків, що містять імена (підроблених) людей. Я сконструював простий запит, щоб дізнатися, скільки імен (ім'я, середня ініціатива, прізвище) є спільними для обох таблиць:

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 (*) не потребує значень жодних інших стовпців, тому цей індекс, як буває, є індексом покриття , а це означає, що не потрібно насправді шукати рядок таблиці, відповідний запису індексу, що економить ще більше / О.

Щоб пришвидшити цей запит, створіть цей індекс постійно, так що більше не потрібно будувати тимчасовий:

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.