Mengapa kueri sqlite ini jauh lebih lambat ketika saya mengindeks kolom?

14

Saya memiliki database sqlite dengan dua tabel, masing-masing dengan 50.000 baris, berisi nama-nama orang (palsu). Saya telah membuat kueri sederhana untuk mengetahui berapa banyak nama yang ada (nama yang diberikan, nama tengah, nama keluarga) yang umum untuk kedua tabel:

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;

Ketika tidak ada indeks kecuali pada kunci utama (tidak relevan dengan permintaan ini), itu berjalan dengan cepat:

[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

Tetapi jika saya menambahkan indeks ke tiga kolom pada setiap tabel (enam indeks semuanya):

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

lalu berjalan dengan sangat lambat:

[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

Apakah ada sajak atau alasan untuk ini?

Inilah hasil EXPLAIN QUERY PLANuntuk versi tanpa indeks:

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

Ini dengan indeks:

0|0|0|SCAN TABLE fakenames_uk
0|1|1|SEARCH TABLE fakenames_usa USING INDEX idx_us_middleinitial (middleinitial=?)
keamanan chiastic
sumber
1
Indeks Anda tidak mencakup. Tampaknya Anda mengindeks setiap kolom satu per satu. Apa yang terjadi ketika Anda membuat indeks penutup yang berisi ketiga kolom dalam indeks ( middleinitial, surnamedan givenname)?
Randolph West
@ Randoph West Saya mengerti apa yang Anda maksud, tetapi Anda tidak menggunakan terminologi yang tepat: "indeks penutup" adalah salah satu yang menyertakan kolom yang dipilih juga. Misalnya, untuk kueri SELECT c FROM t WHERE a=1 AND b=2, indeks t(a,b,c)mencakup tetapi t(a,b)tidak. Manfaat dari indeks yang dicakup adalah bahwa seluruh hasil kueri dapat ditarik langsung dari indeks, sedangkan indeks yang tidak mencakup dengan cepat menemukan baris yang relevan tetapi masih perlu merujuk ke data tabel utama untuk memilih nilai-nilai.
Arthur Tacca

Jawaban:

15

Dalam SQLite, gabungan dieksekusi sebagai gabungan simpul bersarang, yaitu, basis data melewati satu tabel, dan untuk setiap baris, mencari baris yang cocok dari tabel lainnya.

Jika ada indeks, database dapat mencari setiap kecocokan dalam indeks dengan cepat, dan kemudian pergi ke baris tabel yang sesuai untuk mendapatkan nilai-nilai kolom lain yang diperlukan.

Dalam hal ini, ada tiga kemungkinan indeks. Tanpa informasi statistik (yang akan dibuat dengan menjalankan ANALYZE ), database memilih yang terkecil, untuk mengurangi I / O. Namun, middleinitialindeks tidak berguna karena tidak mengurangi jumlah baris tabel yang perlu diambil; dan langkah tambahan melalui indeks sebenarnya meningkatkan I / O yang dibutuhkan karena baris tabel tidak lagi dibaca secara berurutan, tetapi secara acak.

Jika tidak ada indeks, pencarian baris yang cocok akan membutuhkan pemindaian tabel lengkap dari tabel kedua untuk setiap baris dari tabel pertama. Ini akan sangat buruk sehingga database memperkirakan bahwa itu bermanfaat untuk membuat dan kemudian menjatuhkan indeks sementara hanya untuk permintaan ini. Indeks sementara ("OTOMATIS") ini dibuat pada semua kolom yang digunakan untuk pencarian. Operasi COUNT (*) tidak memerlukan nilai dari kolom lain, jadi indeks ini merupakan indeks penutup , yang berarti tidak perlu mencari baris tabel yang terkait dengan entri indeks, yang bahkan lebih menghemat saya. /HAI.

Untuk mempercepat kueri ini, buat indeks ini secara permanen, sehingga tidak perlu lagi membuat yang sementara:

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=?)

Indeks aktif surnametidak lagi diperlukan karena indeks tiga kolom dapat digunakan untuk pencarian apa pun di kolom ini.
Indeks aktif givennamemungkin berguna jika Anda akan melakukan pencarian pada kolom ini saja.
Indeks aktif middleinitialselalu tidak berharga: kueri yang mencari salah satu dari 26 nilai yang mungkin lebih cepat jika hanya memindai seluruh tabel.

CL.
sumber