12 Por qué usar transacciones

SQLite está optimizado para transacciones.

Para evitar errores de consistencia de bases de datos, todas las sentencias SQL son atómicas, es decir, en caso de interrupción del programa, por razones internas o externas, cualquier sentencia SQL o bien se ejecuta completamente, o no se ejecuta en absoluto. No hay término medio.

Esto significa que cualquier sentencia SQL que implique escribir en disco se asegurará de que la información realmente se escriba en el fichero, lo que implica más tiempo de ejecución.

Esto resulta muy evidente cuando se hacen muchas modificaciones, por ejemplo, si una rutina añade cientos o miles de filas en una tabla, hacer las inserciones una a una requiere sensiblemente más tiempo que hacerlo dentro de una transacción.

Este ejemplo ilustra la diferencia entre insertar 100 registros sin usar transacciones e inserter 10000 en una transacción:

// Agregar sqlite a opciones de enlazado
#include <sqlite/sqlite3.h>
#include <iostream>
#include <cstring>
#include <ctime>
#include <cstdio>

using namespace std;

int main() {
    int rc;
    sqlite3 *db;
    sqlite3_stmt *ppStmt;
    char consulta[128];
    char nombre[80];
    char email[60];
    time_t ti, tf;

    ti = time(0);
    strcpy(consulta, "INSERT INTO usuario VALUES(?,?)");
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        for(int i = 0; i < 100; i++) {
            cout << i << (char)13;
            sprintf(nombre, "Nombre %4d", i);
            sprintf(email, "eMail %4d", i);
            sqlite3_bind_text(ppStmt, 1, nombre, -1, SQLITE_STATIC);
            sqlite3_bind_text(ppStmt, 2, email, -1, SQLITE_STATIC);
            if(SQLITE_DONE != sqlite3_step(ppStmt)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
            sqlite3_reset(ppStmt);
        }
        sqlite3_finalize(ppStmt);
    }
    tf = time(0);
    cout << "Tiempo para 100 sin transaccion: " << tf - ti << " segundos" << endl;

    sqlite3_exec(db, "DELETE FROM usuario WHERE nombre LIKE \"Nombre %\";VACUUM;", 0, 0, 0);

    sqlite3_exec(db, "BEGIN", 0, 0, 0);
    ti = time(0);
    strcpy(consulta, "INSERT INTO usuario VALUES(?,?)");
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        for(int i = 0; i < 10000; i++) {
            cout << i << (char)13;
            sprintf(nombre, "Nombre %4d", i);
            sprintf(email, "eMail %4d", i);
            sqlite3_bind_text(ppStmt, 1, nombre, -1, SQLITE_STATIC);
            sqlite3_bind_text(ppStmt, 2, email, -1, SQLITE_STATIC);
            if(SQLITE_DONE != sqlite3_step(ppStmt)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
            sqlite3_reset(ppStmt);
        }
        sqlite3_finalize(ppStmt);
    }
    tf = time(0);
    cout << "Tiempo para 10000 registros con transaccion: " << tf - ti << " segundos" << endl;
    sqlite3_exec(db, "END", 0, 0, 0);

    sqlite3_exec(db, "DELETE FROM usuario WHERE nombre LIKE \"Nombre %\";VACUUM;", 0, 0, 0);

    sqlite3_close(db);

    return 0;
}

La salida es elocuente (en mi ordenador al menos):

Tiempo para 100 sin transaccion: 15 segundos
Tiempo para 10000 registros con transaccion: 3 segundos