Perché questa query sqlite è molto più lenta quando indicizzo le colonne?


14

Ho un database sqlite con due tabelle, ognuna con 50.000 righe, contenente nomi di persone (false). Ho creato una semplice query per scoprire quanti nomi ci sono (nome, medio iniziale, cognome) comuni a entrambe le tabelle:

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;

Quando non ci sono indici ad eccezione delle chiavi primarie (irrilevanti per questa query), viene eseguito rapidamente:

[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

Ma se aggiungo indici alle tre colonne su ogni tabella (sei indici in tutto):

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

quindi corre dolorosamente lentamente:

[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

C'è qualche rima o ragione per questo?

Ecco il risultato di EXPLAIN QUERY PLANper la versione senza indici:

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

Questo è con gli indici:

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

1
I tuoi indici non sono coperti. Sembra che stai indicizzando ciascuna colonna singolarmente. Cosa succede quando si crea un indice di copertura contenente tutte e tre le colonne in un indice ( middleinitial, surnamee givenname)?
Randolph West,

@Randoph West Capisco cosa volevi dire, ma non stai usando la giusta terminologia: un "indice di copertura" è uno che include anche le colonne selezionate. Ad esempio, per la query SELECT c FROM t WHERE a=1 AND b=2, l'indice t(a,b,c)copre ma t(a,b)non lo è. Il vantaggio degli indici di copertura è che l'intero risultato della query può essere estratto direttamente dall'indice, mentre gli indici non di copertura trovano rapidamente le righe pertinenti ma devono comunque fare riferimento ai dati della tabella principale per selezionare i valori.
Arthur Tacca

Risposte:


15

In SQLite, i join vengono eseguiti come join di loop nidificati, ovvero il database passa attraverso una tabella e, per ogni riga, cerca le righe corrispondenti dall'altra tabella.

Se esiste un indice, il database può cercare rapidamente eventuali corrispondenze nell'indice e quindi passare alla riga della tabella corrispondente per ottenere i valori di tutte le altre colonne necessarie.

In questo caso, ci sono tre possibili indici. Senza alcuna informazione statistica (che verrebbe creata eseguendo ANALYZE ), il database sceglie quello più piccolo, per ridurre l'I / O. Tuttavia, l' middleinitialindice è inutile perché non riduce notevolmente il numero di righe della tabella che devono essere recuperate; e il passaggio aggiuntivo attraverso l'indice aumenta effettivamente l'I / O necessario perché le righe della tabella non vengono più lette in ordine, ma in modo casuale.

Se non è presente alcun indice, la ricerca di righe corrispondenti richiederebbe una scansione completa della tabella della seconda tabella per ogni riga della prima tabella. Ciò sarebbe così negativo che il database stima che valga la pena creare e quindi eliminare un indice temporaneo solo per questa query. Questo indice temporaneo ("AUTOMATICO") viene creato su tutte le colonne utilizzate per la ricerca. L'operazione COUNT (*) non richiede valori da altre colonne, quindi questo indice sembra essere un indice di copertura , il che significa che non è necessario cercare effettivamente la riga della tabella corrispondente a una voce di indice, che consente di risparmiare ancora di più I / O.

Per accelerare questa query, crea questo indice in modo permanente, in modo che non sia più necessario costruirne uno temporaneo:

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

L'indice attivo surnamenon è più necessario perché l'indice a tre colonne può essere utilizzato per qualsiasi ricerca su questa colonna.
L'indice attivo givennamepotrebbe essere utile se effettuerai ricerche solo su questa colonna.
L'indice middleinitialattivo è sempre senza valore: una query che cerca uno dei 26 valori possibili è più veloce se esegue la scansione dell'intera tabella.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.