Tengo un problema de rendimiento en SQLite con SELECT COUNT (*) en tablas grandes.SQLite: COUNT lento en tablas grandes
Como todavía no recibí una respuesta utilizable e hice algunas pruebas adicionales, edité mi pregunta para incorporar mis nuevos hallazgos.
tengo 2 tablas:
CREATE TABLE Table1 (
Key INTEGER NOT NULL,
... several other fields ...,
Status CHAR(1) NOT NULL,
Selection VARCHAR NULL,
CONSTRAINT PK_Table1 PRIMARY KEY (Key ASC))
CREATE Table2 (
Key INTEGER NOT NULL,
Key2 INTEGER NOT NULL,
... a few other fields ...,
CONSTRAINT PK_Table2 PRIMARY KEY (Key ASC, Key2 ASC))
Tabla 1 tiene alrededor de 8 millones de discos y Tabla2 tiene alrededor de 51 millones de registros, y la DatabaseFile es de más de 5 GB.
Tabla 1 tiene 2 índices más:
CREATE INDEX IDX_Table1_Status ON Table1 (Status ASC, Key ASC)
CREATE INDEX IDX_Table1_Selection ON Table1 (Selection ASC, Key ASC)
campo "Estado" se requiere, pero tiene sólo 6 valores distintos, "Selección" no es necesaria y tiene sólo alrededor de 1,5 millones de valores diferentes de cero y sólo alrededor 600k valores distintos
Hice algunas pruebas en ambas tablas, puede ver los tiempos a continuación, y agregué el "plan de consulta de explicación" para cada solicitud (QP). Puse el archivo de la base de datos en una memoria USB para poder eliminarlo después de cada prueba y obtener resultados confiables sin interferencia de la memoria caché del disco. Algunas solicitudes son más rápidas en USB (supongo que debido a la falta de tiempo de búsqueda), pero algunas son más lentas (escaneos de tabla).
SELECT COUNT(*) FROM Table1
Time: 105 sec
QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~1000000 rows)
SELECT COUNT(Key) FROM Table1
Time: 153 sec
QP: SCAN TABLE Table1 (~1000000 rows)
SELECT * FROM Table1 WHERE Key = 5123456
Time: 5 ms
QP: SEARCH TABLE Table1 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
Time: 16 sec
QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows)
SELECT * FROM Table1 WHERE Selection = 'SomeValue' AND Key > 5123456 LIMIT 1
Time: 9 ms
QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Selection (Selection=?) (~3 rows)
Como se puede ver los recuentos son muy lentos, pero son rápidos selecciona normales (excepto para el segundo uno, que tomó 16 segundos).
Lo mismo vale para la Tabla2:
SELECT COUNT(*) FROM Table2
Time: 528 sec
QP: SCAN TABLE Table2 USING COVERING INDEX sqlite_autoindex_Table2_1(~1000000 rows)
SELECT COUNT(Key) FROM Table2
Time: 249 sec
QP: SCAN TABLE Table2 (~1000000 rows)
SELECT * FROM Table2 WHERE Key = 5123456 AND Key2 = 0
Time: 7 ms
QP: SEARCH TABLE Table2 USING INDEX sqlite_autoindex_Table2_1 (Key=? AND Key2=?) (~1 rows)
¿Por qué no usar SQLite el índice creado automáticamente en la clave principal en la tabla 1? ¿Y por qué, cuando usa el índice automático en la Tabla 2, todavía toma mucho tiempo?
Creé las mismas tablas con el mismo contenido y los mismos índices en SQL Server 2008 R2 y allí los recuentos son casi instantáneos.
Uno de los siguientes comentarios sugería ejecutar ANALYZE en la base de datos. Lo hice y me llevó 11 minutos completarlo. Después de eso, me encontré con algunas de las pruebas de nuevo:
SELECT COUNT(*) FROM Table1
Time: 104 sec
QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~7848023 rows)
SELECT COUNT(Key) FROM Table1
Time: 151 sec
QP: SCAN TABLE Table1 (~7848023 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
Time: 5 ms
QP: SEARCH TABLE Table1 USING INTEGER PRIMARY KEY (rowid>?) (~196200 rows)
SELECT COUNT(*) FROM Table2
Time: 529 sec
QP: SCAN TABLE Table2 USING COVERING INDEX sqlite_autoindex_Table2_1(~51152542 rows)
SELECT COUNT(Key) FROM Table2
Time: 249 sec
QP: SCAN TABLE Table2 (~51152542 rows)
Como se puede ver, las consultas tomaron al mismo tiempo (excepto el plan de consulta que ahora se muestra el número real de filas), sólo el más lento de selección es ahora también rápido.
A continuación, creo un índice dan adicional en el campo Clave de la Tabla1, que debe corresponder al índice automático. Hice esto en la base de datos original, sin los datos de ANALIZAR. Tomó más de 23 minutos crear este índice (recuerde, esto está en una memoria USB).
CREATE INDEX IDX_Table1_Key ON Table1 (Key ASC)
Entonces me encontré de nuevo las pruebas:
SELECT COUNT(*) FROM Table1
Time: 4 sec
QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Key(~1000000 rows)
SELECT COUNT(Key) FROM Table1
Time: 167 sec
QP: SCAN TABLE Table2 (~1000000 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
Time: 17 sec
QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows)
Como se puede ver, el índice ayudó con la cuenta (*), pero no con el recuento (Key).
Finaly, que crea la tabla utilizando una restricción de columna en lugar de una restricción de tabla:
CREATE TABLE Table1 (
Key INTEGER PRIMARY KEY ASC NOT NULL,
... several other fields ...,
Status CHAR(1) NOT NULL,
Selection VARCHAR NULL)
Entonces me encontré de nuevo las pruebas:
SELECT COUNT(*) FROM Table1
Time: 6 sec
QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~1000000 rows)
SELECT COUNT(Key) FROM Table1
Time: 28 sec
QP: SCAN TABLE Table1 (~1000000 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
Time: 10 sec
QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows)
Aunque los planes de consulta son los mismos, la los tiempos son mucho mejores Por qué es esto ?
El problema es que ALTER TABLE no permite convertir una tabla existente y tengo muchas bases de datos existentes que no puedo convertir a esta forma. Además, usar un contraint de columna en lugar de restricción de tabla no funcionará para Table2.
¿Alguien tiene alguna idea de lo que estoy haciendo mal y cómo resolver este problema?
Utilicé System.Data.SQLite versión 1.0.74.0 para crear las tablas y para ejecutar las pruebas usé SQLiteSpy 1.9.1.
Gracias,
Marc
Si tiene problemas de rendimiento con SQLite, la solución suele ser pasar a un servidor de bases de datos más grande (recomiendo Postgres en lugar de MS SQL). – Borealid
No tengo otros problemas de rendimiento, todas las otras selecciones son rápidas (y uso los índices correctos), las inserciones y las actualizaciones son rápidas, solo el recuento me molesta. – Marc
Lo que es realmente extraño, porque (para DB2, al menos) la mayoría de los RDBMS probablemente usan información efectivamente almacenada en caché: si solicita el recuento de _todas las filas (o está restringido por algo en un índice), generalmente puede leer esa información el índice en sí: el índice conoce el número de entradas. Es doblemente extraño en el sentido de que dices que todos los demás 'SELECT's son rápidos; ¡necesitan saber el recuento de registros para poder optimizarlos correctamente! A menos que ocurra algo extraño, y usted está bloqueando la tabla (nivel de transacción de lectura repetible, o algo así?) ... –