Por que essa consulta sqlite é muito mais lenta quando eu indexo as colunas?

14

Eu tenho um banco de dados sqlite com duas tabelas, cada uma com 50.000 linhas, contendo nomes de pessoas (falsas). Eu construí uma consulta simples para descobrir quantos nomes existem (nome, inicial do meio, sobrenome) comuns a ambas as tabelas:

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 não há índices, exceto nas chaves primárias (irrelevantes para esta consulta), ela é executada 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

Mas se eu adicionar índices às três colunas em cada tabela (seis índices no total):

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

depois corre dolorosamente devagar:

[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

Existe alguma rima ou razão para isso?

Aqui está o resultado da EXPLAIN QUERY PLANversão sem índices:

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

Isto é com índices:

0|0|0|SCAN TABLE fakenames_uk
0|1|1|SEARCH TABLE fakenames_usa USING INDEX idx_us_middleinitial (middleinitial=?)
segurança quiastica
fonte
1
Seus índices não estão cobrindo. Parece que você está indexando cada coluna individualmente. O que acontece quando você cria um índice de cobertura contendo todas as três colunas em um índice ( middleinitial, surnamee givenname)?
Randolph West

Respostas:

15

No SQLite, as junções são executadas como junções de loop aninhadas, ou seja, o banco de dados passa por uma tabela e, para cada linha, pesquisa as linhas correspondentes da outra tabela.

Se houver um índice, o banco de dados poderá procurar rapidamente quaisquer correspondências no índice e, em seguida, ir para a linha da tabela correspondente para obter os valores de quaisquer outras colunas necessárias.

Nesse caso, existem três índices possíveis. Sem nenhuma informação estatística (que seria criada com a execução de ANALYZE ), o banco de dados escolhe o menor, para reduzir a E / S. No entanto, o middleinitialíndice é inútil porque não reduz muito o número de linhas da tabela que precisam ser buscadas; e a etapa adicional no índice realmente aumenta a E / S necessária porque as linhas da tabela não são mais lidas em ordem, mas aleatoriamente.

Se não houver índice, a pesquisa de linhas correspondentes exigiria uma varredura completa da segunda tabela para cada linha da primeira tabela. Isso seria tão ruim que o banco de dados estima que vale a pena criar e soltar um índice temporário apenas para esta consulta. Este índice temporário ("AUTOMATIC") é criado em todos os colunms usados ​​para a pesquisa. A operação COUNT (*) não precisa de valores de nenhuma outra coluna; portanto, esse índice é um índice de cobertura , o que significa que não é necessário procurar a linha da tabela correspondente a uma entrada de índice, o que economiza ainda mais I / O.

Para acelerar essa consulta, crie esse índice permanentemente, para que não seja mais necessário criar um temporário:

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

O índice ativado surnamenão é mais necessário porque o índice de três colunas pode ser usado para qualquer pesquisa nessa coluna.
O índice ativado givennamepode ser útil se você fizer pesquisas apenas nesta coluna.
O índice middleinitialativado é sempre inútil: uma consulta que pesquisa um dos 26 valores possíveis é mais rápida se apenas analisar a tabela inteira.

CL.
fonte