Mejore el rendimiento de INSERT por segundo de SQLite

Optimizar SQLite es complicado. El rendimiento de las inserciones a granel de una aplicación C puede variar desde 85 inserciones por segundo hasta más de 96,000 inserciones por segundo.

Antecedentes: Estamos usando SQLite como parte de una aplicación de escritorio. Tenemos grandes cantidades de datos de configuración almacenados en archivos XML que se analizan y cargan en una base de datos SQLite para su posterior procesamiento cuando se inicializa la aplicación. SQLite es ideal para esta situación porque es rápido, no requiere una configuración especializada y la base de datos se almacena en el disco como un solo archivo.

Justificación: Al principio me decepcionó la actuación que estaba viendo. Resulta que el rendimiento de SQLite puede variar significativamente (tanto para inserciones masivas como para selecciones) dependiendo de cómo esté configurada la base de datos y cómo esté utilizando la API. No fue un asunto trivial averiguar cuáles eran todas las opciones y técnicas, así que pensé que sería prudente crear esta entrada de la wiki de la comunidad para compartir los resultados con los lectores de Stack Overflow con el fin de ahorrarles a otros el problema de las mismas investigaciones.

El experimento: En lugar de simplemente hablar sobre consejos de rendimiento en el sentido general (p. Ej. "¡Utilice una transacción!"), Pensé que era mejor escribir código C y realmente medir el impacto de varias opciones. Comenzaremos con algunos datos simples:

  • Un archivo de texto delimitado por TAB de 28 MB (aproximadamente 865,000 registros) horario completo de tránsito para la ciudad de Toronto
  • Mi máquina de prueba es una P3.60 de 4 GHz con Windows XP.
  • El código está compilado con Visual C + + 2005 como "Lanzamiento" con "Optimización completa" (/ Ox) y Favorecer el código rápido (/ Ot).
  • Estoy usando SQLite "Amalgamation", compilado directamente en mi aplicación de prueba. La versión de SQLite que tengo es un poco más antigua (3.6.7), pero sospecho que estos resultados serán comparables a la última versión (deje un comentario si piensa lo contrario).

¡Escribamos un código!

El Código: Un programa C simple que lee el archivo de texto línea por línea, divide la cadena en valores y luego inserta los datos en una base de datos SQLite. En esta versión "básica" del código, se crea la base de datos, pero en realidad no insertaremos datos:

/*************************************************************
    Baseline code to experiment with SQLite performance.

    Input data is a 28 MB TAB-delimited text file of the
    complete Toronto Transit System schedule/route info
    from http://www.toronto.ca/open/datasets/ttc-routes/

**************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "sqlite3.h"

#define INPUTDATA "C:\\TTC_schedule_scheduleitem_10-27-2009.txt"
#define DATABASE "c:\\TTC_schedule_scheduleitem_10-27-2009.sqlite"
#define TABLE "CREATE TABLE IF NOT EXISTS TTC (id INTEGER PRIMARY KEY, Route_ID TEXT, Branch_Code TEXT, Version INTEGER, Stop INTEGER, Vehicle_Index INTEGER, Day Integer, Time TEXT)"
#define BUFFER_SIZE 256

int main(int argc, char **argv) {

    sqlite3 * db;
    sqlite3_stmt * stmt;
    char * sErrMsg = 0;
    char * tail = 0;
    int nRetCode;
    int n = 0;

    clock_t cStartClock;

    FILE * pFile;
    char sInputBuf [BUFFER_SIZE] = "\0";

    char * sRT = 0;  /* Route */
    char * sBR = 0;  /* Branch */
    char * sVR = 0;  /* Version */
    char * sST = 0;  /* Stop Number */
    char * sVI = 0;  /* Vehicle */
    char * sDT = 0;  /* Date */
    char * sTM = 0;  /* Time */

    char sSQL [BUFFER_SIZE] = "\0";

    /*********************************************/
    /* Open the Database and create the Schema */
    sqlite3_open(DATABASE, &db);
    sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);

    /*********************************************/
    /* Open input file and import into Database*/
    cStartClock = clock();

    pFile = fopen (INPUTDATA,"r");
    while (!feof(pFile)) {

        fgets (sInputBuf, BUFFER_SIZE, pFile);

        sRT = strtok (sInputBuf, "\t");     /* Get Route */
        sBR = strtok (NULL, "\t");            /* Get Branch */
        sVR = strtok (NULL, "\t");            /* Get Version */
        sST = strtok (NULL, "\t");            /* Get Stop Number */
        sVI = strtok (NULL, "\t");            /* Get Vehicle */
        sDT = strtok (NULL, "\t");            /* Get Date */
        sTM = strtok (NULL, "\t");            /* Get Time */

        /* ACTUAL INSERT WILL GO HERE */

        n++;
    }
    fclose (pFile);

    printf("Imported %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC);

    sqlite3_close(db);
    return 0;
}

El control"

Ejecutar el código tal como está en realidad no realiza ninguna operación de base de datos, pero nos dará una idea de qué tan rápido son las operaciones de procesamiento de cadenas y E / S del archivo C sin formato.

Se importaron 864913 registros en 0.94 segundos

¡Estupendo! Podemos hacer 920,000 inserciones por segundo, siempre que en realidad no hagamos inserciones :-)


El "peor escenario"

Vamos a generar la cadena SQL usando los valores leídos del archivo e invocaremos esa operación SQL usando sqlite3_exec:

sprintf(sSQL, "INSERT INTO TTC VALUES (NULL, '%s', '%s', '%s', '%s', '%s', '%s', '%s')", sRT, sBR, sVR, sST, sVI, sDT, sTM);
sqlite3_exec(db, sSQL, NULL, NULL, &sErrMsg);

Esto será lento porque el SQL se compilará en el código VDBE para cada inserción y cada inserción ocurrirá en su propia transacción. Que lento

Se importaron 864913 registros en 9933.61 segundos

¡Ay! ¡2 horas y 45 minutos! Eso es solo 85 inserciones por segundo.

Usando una transacción

De forma predeterminada, SQLite evaluará cada declaración INSERT / UPDATE dentro de una transacción única. Si realiza una gran cantidad de inserciones, es recomendable envolver su operación en una transacción:

sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);

pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {

    ...

}
fclose (pFile);

sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);

Se importaron 864913 registros en 38.03 segundos

Eso es mejor. Simplemente envolver todos nuestros insertos en una sola transacción mejoró nuestro rendimiento para 23,000 inserciones por segundo.

Uso de un estado de cuenta preparado

Usar una transacción fue una gran mejora, pero volver a compilar la declaración SQL para cada inserción no tiene sentido si usamos el mismo SQL una y otra vez. Usemos sqlite3_prepare_v2 para compilar nuestra declaración SQL una vez y luego vincular nuestros parámetros a esa declaración usando sqlite3_bind_text:

/* Open input file and import into the database */
cStartClock = clock();

sprintf(sSQL, "INSERT INTO TTC VALUES (NULL, @RT, @BR, @VR, @ST, @VI, @DT, @TM)");
sqlite3_prepare_v2(db,  sSQL, BUFFER_SIZE, &stmt, &tail);

sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);

pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {

    fgets (sInputBuf, BUFFER_SIZE, pFile);

    sRT = strtok (sInputBuf, "\t");   /* Get Route */
    sBR = strtok (NULL, "\t");        /* Get Branch */
    sVR = strtok (NULL, "\t");        /* Get Version */
    sST = strtok (NULL, "\t");        /* Get Stop Number */
    sVI = strtok (NULL, "\t");        /* Get Vehicle */
    sDT = strtok (NULL, "\t");        /* Get Date */
    sTM = strtok (NULL, "\t");        /* Get Time */

    sqlite3_bind_text(stmt, 1, sRT, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 2, sBR, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 3, sVR, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 4, sST, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 5, sVI, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 6, sDT, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 7, sTM, -1, SQLITE_TRANSIENT);

    sqlite3_step(stmt);

    sqlite3_clear_bindings(stmt);
    sqlite3_reset(stmt);

    n++;
}
fclose (pFile);

sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);

printf("Imported %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC);

sqlite3_finalize(stmt);
sqlite3_close(db);

return 0;

Se importaron 864913 registros en 16.27 segundos

¡Lindo! Hay un poco más de código (no olvides llamar sqlite3_clear_bindings y sqlite3_reset), pero hemos más que duplicado nuestro rendimiento para 53,000 inserciones por segundo.

PRAGMA sincrónico = APAGADO

De forma predeterminada, SQLite se detendrá después de emitir un comando de escritura a nivel del sistema operativo. Esto garantiza que los datos se escriban en el disco. Configurando synchronous = OFF, le estamos indicando a SQLite que simplemente entregue los datos al sistema operativo para su escritura y luego continúe. Existe la posibilidad de que el archivo de la base de datos se corrompa si la computadora sufre una falla catastrófica (o falla de energía) antes de que los datos se escriban en el plato:

/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg);

Se importaron 864913 registros en 12.41 segundos

Las mejoras ahora son más pequeñas, pero estamos a la altura 69,600 inserciones por segundo.

PRAGMA journal_mode = MEMORIA

Considere almacenar el diario de reversión en la memoria evaluando PRAGMA journal_mode = MEMORY. Su transacción será más rápida, pero si pierde energía o su programa falla durante una transacción, su base de datos podría quedar en un estado corrupto con una transacción parcialmente completada:

/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg);

Se importaron 864913 registros en 13.50 segundos

Un poco más lento que la optimización anterior en 64,000 inserciones por segundo.

PRAGMA sincrónico = APAGADO y PRAGMA journal_mode = MEMORIA

Combinemos las dos optimizaciones anteriores. Es un poco más arriesgado (en caso de una falla), pero solo estamos importando datos (no administrando un banco):

/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg);

Se importaron 864913 registros en 12.00 segundos

¡Fantástico! Somos capaces de hacer 72,000 inserciones por segundo.

Usar una base de datos en memoria

Por diversión, aprovechemos todas las optimizaciones anteriores y redefinamos el nombre del archivo de la base de datos para que estemos trabajando completamente en RAM:

#define DATABASE ":memory:"

Se importaron 864913 registros en 10.94 segundos

No es muy práctico almacenar nuestra base de datos en RAM, pero es impresionante que podamos realizar 79,000 inserciones por segundo.

Refactorización del código C

Aunque no es específicamente una mejora de SQLite, no me gusta el extra char* operaciones de asignación en el while círculo. Refactoricemos rápidamente ese código para pasar la salida de strtok() directamente en sqlite3_bind_text()y dejemos que el compilador intente acelerar las cosas para nosotros:

pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {

    fgets (sInputBuf, BUFFER_SIZE, pFile);

    sqlite3_bind_text(stmt, 1, strtok (sInputBuf, "\t"), -1, SQLITE_TRANSIENT); /* Get Route */
    sqlite3_bind_text(stmt, 2, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Branch */
    sqlite3_bind_text(stmt, 3, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Version */
    sqlite3_bind_text(stmt, 4, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Stop Number */
    sqlite3_bind_text(stmt, 5, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Vehicle */
    sqlite3_bind_text(stmt, 6, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Date */
    sqlite3_bind_text(stmt, 7, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Time */

    sqlite3_step(stmt);        /* Execute the SQL Statement */
    sqlite3_clear_bindings(stmt);    /* Clear bindings */
    sqlite3_reset(stmt);        /* Reset VDBE */

    n++;
}
fclose (pFile);

Nota: volvemos a utilizar un archivo de base de datos real. Las bases de datos en memoria son rápidas, pero no necesariamente prácticas

Se importaron 864913 registros en 8.94 segundos

Una ligera refactorización del código de procesamiento de cadenas utilizado en nuestro enlace de parámetros nos ha permitido realizar 96,700 inserciones por segundo. Creo que es seguro decir que esto es bastante rápido. A medida que comencemos a modificar otras variables (es decir, tamaño de página, creación de índices, etc.), este será nuestro punto de referencia.


Resumen (hasta ahora)

¡Espero que sigas conmigo! La razón por la que comenzamos por este camino es que el rendimiento de la inserción masiva varía enormemente con SQLite, y no siempre es obvio qué cambios deben realizarse para acelerar nuestra operación. Usando el mismo compilador (y opciones de compilador), la misma versión de SQLite y los mismos datos, hemos optimizado nuestro código y nuestro uso de SQLite para llevar desde el peor de los casos de 85 inserciones por segundo a más de 96,000 inserciones por segundo.


CREAR ÍNDICE y luego INSERTAR vs. INSERTAR luego CREAR ÍNDICE

Antes de empezar a medir SELECT rendimiento, sabemos que crearemos índices. Se ha sugerido en una de las respuestas a continuación que al hacer inserciones masivas, es más rápido crear el índice después de que se hayan insertado los datos (en lugar de crear el índice primero y luego insertar los datos). Intentemos:

Crear índice y luego insertar datos

sqlite3_exec(db, "CREATE  INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);
...

Se importaron 864913 registros en 18.13 segundos

Insertar datos y luego crear índice

...
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "CREATE  INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg);

Se importaron 864913 registros en 13.66 segundos

Como era de esperar, las inserciones masivas son más lentas si una columna está indexada, pero hace una diferencia si el índice se crea después de insertar los datos. Nuestra línea de base sin índice es de 96,000 inserciones por segundo. Crear el índice primero y luego insertar datos nos da 47,700 inserciones por segundo, mientras que insertar los datos primero y luego crear el índice nos da 63,300 inserciones por segundo.


Con mucho gusto tomaría sugerencias para probar otros escenarios ... Y pronto estaré compilando datos similares para consultas SELECT.

preguntado el 10 de noviembre de 09 a las 19:11

¡Buen punto! En nuestro caso, estamos tratando con aproximadamente 1.5 millones de pares clave / valor leídos de archivos de texto XML y CSV en 200k registros. Pequeño en comparación con las bases de datos que ejecutan sitios como SO, pero lo suficientemente grande como para que el ajuste del rendimiento de SQLite sea importante. -

"Tenemos grandes cantidades de datos de configuración almacenados en archivos XML que se analizan y cargan en una base de datos SQLite para su posterior procesamiento cuando se inicializa la aplicación". ¿Por qué no guarda todo en la base de datos sqlite en primer lugar, en lugar de almacenar en XML y luego cargar todo en el momento de la inicialización? -

Has intentado no llamar sqlite3_clear_bindings(stmt);? Establece los enlaces cada vez a través de lo cual debería ser suficiente: Antes de llamar a sqlite3_step () por primera vez o inmediatamente después de sqlite3_reset (), la aplicación puede invocar una de las interfaces sqlite3_bind () para adjuntar valores a los parámetros. Cada llamada a sqlite3_bind () anula los enlaces anteriores en el mismo parámetro (ver: sqlite.org/cintro.html). No hay nada en el docs para esa función diciendo que debes llamarlo. -

¿Hiciste mediciones repetidas? La "victoria" de los 4 por evitar 7 punteros locales es extraña, incluso asumiendo un optimizador confuso. -

No use feof() para controlar la terminación de su bucle de entrada. Utilice el resultado devuelto por fgets(). stackoverflow.com/a/15485689/827263 -

10 Respuestas

Varios consejos:

  1. Coloque inserciones / actualizaciones en una transacción.
  2. Para versiones anteriores de SQLite: considere un modo de diario menos paranoico (pragma journal_mode). Hay NORMAL, y luego está OFF, que puede aumentar significativamente la velocidad de inserción si no está demasiado preocupado por la posibilidad de que la base de datos se corrompa si el sistema operativo falla. Si su aplicación falla, los datos deberían estar bien. Tenga en cuenta que en las versiones más recientes, OFF/MEMORY La configuración no es segura para los bloqueos a nivel de la aplicación.
  3. Jugar con tamaños de página también marca la diferencia (PRAGMA page_size). Tener páginas de mayor tamaño puede hacer que las lecturas y las escrituras sean un poco más rápidas, ya que las páginas más grandes se almacenan en la memoria. Tenga en cuenta que se utilizará más memoria para su base de datos.
  4. Si tiene índices, considere llamar CREATE INDEX después de hacer todas tus inserciones. Esto es significativamente más rápido que crear el índice y luego hacer sus inserciones.
  5. Debe tener mucho cuidado si tiene acceso simultáneo a SQLite, ya que toda la base de datos está bloqueada cuando se realizan las escrituras, y aunque son posibles varios lectores, las escrituras se bloquearán. Esto se ha mejorado un poco con la adición de un WAL en las versiones más recientes de SQLite.
  6. Aproveche el ahorro de espacio ... las bases de datos más pequeñas van más rápido. Por ejemplo, si tiene pares clave-valor, intente convertir la clave en un INTEGER PRIMARY KEY si es posible, que reemplazará la columna de número de fila única implícita en la tabla.
  7. Si está utilizando varios subprocesos, puede intentar utilizar el caché de página compartida, que permitirá que las páginas cargadas se compartan entre subprocesos, lo que puede evitar costosas llamadas de E / S.
  8. No use !feof(file)!

También hice preguntas similares aquí y aquí.

Respondido el 04 de junio de 18 a las 16:06

Los documentos no conocen un PRAGMA journal_mode NORMAL sqlite.org/pragma.html#pragma_journal_mode - OneWorld

Ha pasado un tiempo, mis sugerencias se aplicaron a versiones anteriores antes de que se introdujera un WAL. Parece que DELETE es la nueva configuración normal, y ahora también hay configuraciones OFF y MEMORY. Supongo que OFF / MEMORY mejorará el rendimiento de escritura a expensas de la integridad de la base de datos, y OFF desactiva las reversiones por completo. - snazzer

para el n. ° 7, ¿tiene un ejemplo sobre cómo habilitar caché de página compartida utilizando el contenedor c # system.data.sqlite? - aaron hudon

# 4 trajo recuerdos antiguos: hubo al menos un caso en los tiempos anteriores en el que eliminar un índice antes de un grupo de adiciones y volver a crearlo luego aceleró significativamente las inserciones. Es posible que aún funcione más rápido en los sistemas modernos para algunos agregados en los que sabe que tiene acceso exclusivo a la mesa durante el período. - Bill K

Aprobado por el n. ° 1: yo mismo he tenido muy buena suerte con las transacciones. - Enno

Intenta usar SQLITE_STATIC en lugar de SQLITE_TRANSIENT para esos insertos.

SQLITE_TRANSIENT hará que SQLite copie los datos de la cadena antes de regresar.

SQLITE_STATIC le dice que la dirección de memoria que le dio será válida hasta que se haya realizado la consulta (que en este bucle siempre es el caso). Esto le ahorrará varias operaciones de asignación, copia y desasignación por ciclo. Posiblemente una gran mejora.

Respondido 28 ago 15, 17:08

Evitando sqlite3_clear_bindings(stmt).

El código en la prueba establece los enlaces cada vez que deberían ser suficientes.

El sistema Introducción a la API de C de los documentos SQLite dice:

Antes de llamar sqlite3_step () por primera vez o inmediatamente después sqlite3_reset (), la aplicación puede invocar el sqlite3_bind () interfaces para adjuntar valores a los parámetros. Cada llamada a sqlite3_bind () anula los enlaces anteriores en el mismo parámetro

No hay nada en los documentos para sqlite3_clear_bindings diciendo que debe llamarlo además de simplemente configurar los enlaces.

Mas detalle: Evite_sqlite3_clear_bindings ()

Respondido 24 ago 19, 10:08

Maravillosamente correcto: "Contrariamente a la intuición de muchos, sqlite3_reset () no restablece los enlaces en una declaración preparada. Utilice esta rutina para restablecer todos los parámetros del host a NULL". - sqlite.org/c3ref/clear_bindings.html - Francisco Straccia

En inserciones a granel

Inspirado por esta publicación y por la pregunta de Stack Overflow que me llevó aquí: ¿Es posible insertar varias filas a la vez en una base de datos SQLite? - He publicado mi primer ir repositorio:

https://github.com/rdpoor/CreateOrUpdate

que carga a granel una matriz de ActiveRecords en MySQL, SQLite o PostgreSQL bases de datos. Incluye una opción para ignorar los registros existentes, sobrescribirlos o generar un error. Mis puntos de referencia rudimentarios muestran una mejora de 10 veces la velocidad en comparación con las escrituras secuenciales: YMMV.

Lo estoy usando en código de producción donde con frecuencia necesito importar grandes conjuntos de datos, y estoy bastante contento con él.

contestado el 23 de mayo de 17 a las 13:05

@Jess: Si sigue el enlace, verá que se refería a la sintaxis de inserción por lotes. - Alix Axel

El enlace ya no funciona :( - afaulconbridge

@afaulconbridge: Probablemente igual de bien: sospecho que logrará aceleraciones comparables, pero más seguras y fáciles, simplemente envolviendo sus inserciones en una sola transacción. - intrépido_ tonto

Las importaciones a granel parecen funcionar mejor si puede dividir sus INSERTAR / ACTUALIZAR declaraciones. Un valor de 10,000 o más me ha funcionado bien en una tabla con solo unas pocas filas, YMMV ...

Respondido 21 Feb 12, 08:02

Debería ajustar x = 10,000 para que x = cache [= cache_size * page_size] / tamaño promedio de su inserto. - Alix Axel

Si solo le importa la lectura, la versión algo más rápida (pero puede leer datos obsoletos) es leer desde múltiples conexiones de múltiples subprocesos (conexión por subproceso).

Primero busque los elementos en la tabla:

SELECT COUNT(*) FROM table

luego lea en las páginas (LIMIT / OFFSET):

SELECT * FROM table ORDER BY _ROWID_ LIMIT <limit> OFFSET <offset>

donde y se calculan por hilo, así:

int limit = (count + n_threads - 1)/n_threads;

para cada hilo:

int offset = thread_index * limit

Para nuestra base de datos pequeña (200 MB), esto hizo un 50-75% de aceleración (3.8.0.2 64 bits en Windows 7). Nuestras tablas están muy no normalizadas (1000-1500 columnas, aproximadamente 100,000 o más filas).

Demasiados o muy pocos subprocesos no lo harán, necesita comparar y perfilar usted mismo.

También para nosotros, SHAREDCACHE hizo que el rendimiento fuera más lento, así que puse PRIVATECACHE manualmente (porque estaba habilitado globalmente para nosotros)

Respondido el 08 de junio de 19 a las 07:06

No pude obtener ninguna ganancia de las transacciones hasta que elevé cache_size a un valor más alto, es decir PRAGMA cache_size=10000;

Respondido 15 Abr '15, 10:04

Tenga en cuenta que el uso de un valor positivo para cache_size establece el número de páginas para almacenar en caché, no el tamaño total de RAM. Con el tamaño de página predeterminado de 4kB, esta configuración retendrá hasta 40 MB de datos por archivo abierto (o por proceso, si se ejecuta con caché compartido). - Groo

Después de leer este tutorial, intenté implementarlo en mi programa.

Tengo 4-5 archivos que contienen direcciones. Cada archivo tiene aproximadamente 30 millones de registros. Estoy usando la misma configuración que está sugiriendo, pero mi número de INSERT por segundo es muy bajo (~ 10.000 registros por segundo).

Aquí es donde falla tu sugerencia. Utiliza una sola transacción para todos los registros y una sola inserción sin errores ni fallas. Digamos que está dividiendo cada registro en múltiples inserciones en diferentes tablas. ¿Qué pasa si se rompe el récord?

El comando ON CONFLICT no se aplica, porque si tiene 10 elementos en un registro y necesita que cada elemento se inserte en una tabla diferente, si el elemento 5 obtiene un error CONSTRAINT, entonces las 4 inserciones anteriores también deben ir.

Entonces aquí es donde viene la reversión. El único problema con la reversión es que pierde todas sus inserciones y comienza desde arriba. ¿Cómo puedes solucionar esto?

Mi solución fue usar múltiples actas. Comienzo y finalizo una transacción cada 10.000 registros (No preguntes por qué ese número, fue el más rápido que probé). Creé una matriz de tamaño 10.000 e inserté los registros exitosos allí. Cuando ocurre el error, hago una reversión, comienzo una transacción, inserto los registros de mi matriz, confirmo y luego comienzo una nueva transacción después del registro roto.

Esta solución me ayudó a evitar los problemas que tengo al tratar con archivos que contienen registros incorrectos / duplicados (tenía casi un 4% de registros incorrectos).

El algoritmo que creé me ayudó a reducir mi proceso en 2 horas. El proceso de carga final del archivo de 1 hora y 30 minutos sigue siendo lento, pero no se compara con las 4 horas que tomó inicialmente. Logré acelerar las inserciones de 10.000 / sa ~ 14.000 / s

Si alguien tiene alguna otra idea sobre cómo acelerarlo, estoy abierto a sugerencias.

ACTUALIZACIÓN:

Además de mi respuesta anterior, debe tener en cuenta que se inserta por segundo dependiendo del disco duro que esté utilizando también. Lo probé en 3 PC diferentes con diferentes discos duros y obtuve enormes diferencias en los tiempos. PC1 (1 hora 30 minutos), PC2 (6 horas) PC3 (14 horas), así que comencé a preguntarme por qué sería eso.

Después de dos semanas de investigación y verificación de múltiples recursos: disco duro, RAM, caché, descubrí que algunas configuraciones en su disco duro pueden afectar la tasa de E / S. Al hacer clic en propiedades en la unidad de salida deseada, puede ver dos opciones en la pestaña general. Opt1: comprimir esta unidad, Opt2: permitir que los archivos de esta unidad tengan contenido indexado.

Al deshabilitar estas dos opciones, las 3 PC ahora tardan aproximadamente el mismo tiempo en finalizar (1 hora y 20 a 40 minutos). Si encuentra inserciones lentas, verifique si su disco duro está configurado con estas opciones. Le ahorrará mucho tiempo y dolores de cabeza tratando de encontrar la solución.

Respondido el 20 de junio de 20 a las 10:06

Sugeriré lo siguiente. * Use SQLITE_STATIC vs SQLITE_TRANSIENT para evitar una copia de cadena. Debe asegurarse de que la cadena no se cambie antes de que se ejecute la transacción. * Use inserción masiva INSERT INTO stop_times VALUES (NULL,?,?,?,?,?,?,?,? ,?), (NULL,?,?,?,?,?,?,?,?,?), (NULL,?,?,?,?,?,?,?,?,?), (NULL ,?,?,?,?,?,?,?,?,?), (NULL,?,?,?,?,?,?,?,?,?) * Mmap del archivo para reducir el número de syscalls. - más ruin

Al hacer eso, puedo importar 5,582,642 registros en 11.51 segundos - más ruin

La respuesta a su pregunta es que el SQLite 3 más nuevo tiene un rendimiento mejorado, úselo.

Esta respuesta ¿Por qué la inserción de SQLAlchemy con sqlite es 25 veces más lenta que usar sqlite3 directamente? by SqlAlchemy Orm Author tiene 100k inserciones en 0.5 segundos, y he visto resultados similares con python-sqlite y SqlAlchemy. Lo que me lleva a creer que el rendimiento ha mejorado con SQLite 3.

Respondido 01 Abr '20, 03:04

Utilice ContentProvider para insertar los datos masivos en db. El siguiente método utilizado para insertar datos masivos en la base de datos. Esto debería mejorar el rendimiento de INSERT por segundo de SQLite.

private SQLiteDatabase database;
database = dbHelper.getWritableDatabase();

public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {

database.beginTransaction();

for (ContentValues value : values)
 db.insert("TABLE_NAME", null, value);

database.setTransactionSuccessful();
database.endTransaction();

}

Llamar al método bulkInsert:

App.getAppContext().getContentResolver().bulkInsert(contentUriTable,
            contentValuesArray);

Enlace: https://www.vogella.com/tutorials/AndroidSQLite/article.html consulte Uso de la sección ContentProvider para obtener más detalles

respondido 12 mar '19, 09:03

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.