Warum ist diese SQLite-Abfrage viel langsamer, wenn ich die Spalten indiziere?

14

Ich habe eine SQLite-Datenbank mit zwei Tabellen mit jeweils 50.000 Zeilen, die Namen von (falschen) Personen enthalten. Ich habe eine einfache Abfrage erstellt, um herauszufinden, wie viele Namen (Vorname, zweiter Vorname, Nachname) in beiden Tabellen vorkommen:

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;

Wenn außer den Primärschlüsseln keine Indizes vorhanden sind (für diese Abfrage irrelevant), wird dies schnell ausgeführt:

[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

Wenn ich jedoch den drei Spalten jeder Tabelle Indizes hinzufüge (insgesamt sechs Indizes):

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

dann läuft es schmerzhaft langsam:

[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

Gibt es einen Reim oder Grund dafür?

Hier ist das Ergebnis EXPLAIN QUERY PLANfür die Version ohne Indizes:

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

Dies ist mit Indizes:

0|0|0|SCAN TABLE fakenames_uk
0|1|1|SEARCH TABLE fakenames_usa USING INDEX idx_us_middleinitial (middleinitial=?)
chiastic-security
quelle
1
Ihre Indizes decken nicht ab. Anscheinend indizieren Sie jede Spalte einzeln. Was passiert, wenn Sie einen abdeckenden Index erstellen, der alle drei Spalten in einem Index ( middleinitial, surnameund givenname) enthält?
Randolph West

Antworten:

14

In SQLite werden Joins als Joins mit verschachtelten Schleifen ausgeführt, dh, die Datenbank durchsucht eine Tabelle und sucht für jede Zeile nach übereinstimmenden Zeilen aus der anderen Tabelle.

Wenn ein Index vorhanden ist, kann die Datenbank Übereinstimmungen im Index schnell nachschlagen und dann in die entsprechende Tabellenzeile wechseln, um die Werte aller anderen erforderlichen Spalten abzurufen.

In diesem Fall gibt es drei mögliche Indizes. Ohne statistische Informationen (die durch Ausführen von ANALYZE erstellt würden ) wählt die Datenbank die kleinste aus, um die E / A zu reduzieren. Der middleinitialIndex ist jedoch nutzlos, da er die Anzahl der abzurufenden Tabellenzeilen nicht wesentlich verringert. und der zusätzliche Schritt durch den Index erhöht tatsächlich die benötigte E / A, da die Tabellenzeilen nicht mehr in der richtigen Reihenfolge, sondern zufällig gelesen werden.

Wenn es keinen Index gibt, erfordert die Suche nach übereinstimmenden Zeilen einen vollständigen Tabellenscan der zweiten Tabelle für jede Zeile der ersten Tabelle. Dies wäre so schlimm, dass die Datenbank einschätzt, dass es sich lohnt, nur für diese Abfrage einen temporären Index zu erstellen und dann zu löschen. Dieser temporäre Index ("AUTOMATIC") wird für alle für die Suche verwendeten Spalten erstellt. Die COUNT (*) -Operation benötigt keine Werte aus anderen Spalten, daher handelt es sich bei diesem Index zufällig um einen abdeckenden Index. Dies bedeutet, dass die Tabellenzeile, die einem Indexeintrag entspricht, nicht nachgeschlagen werden muss, wodurch noch mehr I gespart wird /Ö.

Um diese Abfrage zu beschleunigen, erstellen Sie diesen Index dauerhaft, sodass kein temporärer Index mehr erstellt werden muss:

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

Der Index für surnamewird nicht mehr benötigt, da der dreispaltige Index für Suchvorgänge in dieser Spalte verwendet werden kann.
Der Index für givennamekann hilfreich sein, wenn Sie nur in dieser Spalte nachschlagen.
Der Index für middleinitialist immer wertlos: Eine Abfrage, die nach einem der 26 möglichen Werte sucht, ist schneller, wenn nur die gesamte Tabelle durchsucht wird.

CL.
quelle