¿Por qué esta consulta sqlite es mucho más lenta cuando indexo las columnas?

14

Tengo una base de datos sqlite con dos tablas, cada una con 50,000 filas, que contiene nombres de personas (falsas). He construido una consulta simple para averiguar cuántos nombres hay (nombre de pila, inicial del segundo nombre, apellido) que son comunes a ambas tablas:

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;

Cuando no hay índices excepto en las claves primarias (irrelevantes para esta consulta), se ejecuta rápidamente:

[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

Pero si agrego índices a las tres columnas en cada tabla (seis índices en total):

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

entonces corre dolorosamente lento:

[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

¿Hay alguna rima o razón para esto?

Aquí está el resultado de EXPLAIN QUERY PLANla versión sin índices:

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

Esto es con índices:

0|0|0|SCAN TABLE fakenames_uk
0|1|1|SEARCH TABLE fakenames_usa USING INDEX idx_us_middleinitial (middleinitial=?)
seguridad quiástica
fuente
1
Sus índices no están cubriendo. Parece que estás indexando cada columna individualmente. ¿Qué ocurre cuando se crea un índice de cobertura que contiene las tres columnas en un índice ( middleinitial, surnamey givenname)?
Randolph West
@Randoph West Entiendo lo que quisiste decir, pero no estás usando la terminología correcta: un "índice de cobertura" es uno que incluye las columnas que también se seleccionan. Por ejemplo, para la consulta SELECT c FROM t WHERE a=1 AND b=2, el índice t(a,b,c)está cubriendo pero t(a,b)no. El beneficio de los índices de cobertura es que todo el resultado de la consulta se puede extraer directamente del índice, mientras que los índices de no cobertura encuentran rápidamente las filas relevantes, pero aún necesita referirse a los datos de la tabla principal para seleccionar los valores.
Arthur Tacca

Respuestas:

15

En SQLite, las uniones se ejecutan como uniones de bucle anidadas, es decir, la base de datos pasa por una tabla y, para cada fila, busca filas coincidentes de la otra tabla.

Si hay un índice, la base de datos puede buscar rápidamente cualquier coincidencia en el índice y luego ir a la fila de la tabla correspondiente para obtener los valores de cualquier otra columna que sea necesaria.

En este caso, hay tres índices posibles. Sin ninguna información estadística (que se crearía ejecutando ANALYZE ), la base de datos elige la más pequeña para reducir las E / S. Sin embargo, el middleinitialíndice es inútil porque no reduce en gran medida el número de filas de la tabla que deben recuperarse; y el paso adicional a través del índice en realidad aumenta la E / S necesaria porque las filas de la tabla ya no se leen en orden, sino al azar.

Si no hay índice, la búsqueda de filas coincidentes requeriría una exploración completa de la tabla de la segunda tabla para cada fila de la primera tabla. Esto sería tan malo que la base de datos estima que vale la pena crear y luego descartar un índice temporal solo para esta consulta. Este índice temporal ("AUTOMÁTICO") se crea en todas las columnas utilizadas para la búsqueda. La operación COUNT (*) no necesita valores de ninguna otra columna, por lo que este índice es un índice de cobertura , lo que significa que no es necesario buscar la fila de la tabla correspondiente a una entrada de índice, lo que ahorra aún más I / O.

Para acelerar esta consulta, cree este índice de forma permanente, de modo que ya no sea necesario construir uno temporal:

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

El índice surnameactivado ya no es necesario porque el índice de tres columnas se puede usar para cualquier búsqueda en esta columna.
El índice givennamepuede ser útil si solo realiza búsquedas en esta columna.
El índice en middleinitialsiempre no tiene valor: una consulta que busca uno de los 26 valores posibles es más rápida si solo escanea toda la tabla.

CL.
fuente