Kodokon kodokon.com

Concurrencia: bloqueos, niveles de aislamiento, lecturas fantasma

Comprende el modelo de bloqueo de SQLite, sus transacciones y las anomalías de aislamiento descritas por el estándar SQL.

12 min · 3 preguntas

Abrir esta lección en Kodokon

SQLite bloquea a nivel del archivo de base de datos completo, no de la fila. En el modo por defecto (rollback journal), los lectores comparten un bloqueo SHARED, pero solo un escritor a la vez puede obtener el bloqueo EXCLUSIVE. Consecuencia: las escrituras se serializan. Creemos una tabla para trabajar con transacciones.

SQL
CREATE TABLE accounts (
  id INTEGER PRIMARY KEY,
  balance INTEGER NOT NULL
);

INSERT INTO accounts (id, balance)
VALUES (1, 100), (2, 50);

-- Read: deferred transaction (default)
BEGIN;
SELECT balance FROM accounts WHERE id = 1;
COMMIT;
Una transacción explícita envuelta por BEGIN/COMMIT.

El bloqueo evoluciona por etapas: UNLOCKED, luego SHARED (lectura), RESERVED (intención de escribir), PENDING, y finalmente EXCLUSIVE (escritura). Si una transacción ya mantiene el bloqueo de escritura, otra recibe el error SQLITE_BUSY. En lugar de fallar de inmediato, define un tiempo de espera con busy_timeout, y activa el modo WAL para dejar que los lectores y el escritor avancen en paralelo.

SQL
-- A single writer, concurrent readers
PRAGMA journal_mode = WAL;

-- Wait up to 5 s if the database is busy
PRAGMA busy_timeout = 5000;
WAL desbloquea las lecturas concurrentes; busy_timeout espera.

El estándar SQL describe tres anomalías: la lectura sucia (leer datos no confirmados), la lectura no repetible (releer una fila y ver un valor diferente) y la lectura fantasma (repetir una consulta de rango y ver aparecer filas nuevas). Como SQLite serializa a los escritores, se comporta en la práctica como SERIALIZABLE: estas anomalías no aparecen (fuera del modo de caché compartida con read_uncommitted). En otros sistemas, eliges el nivel de forma explícita.

SQL
-- Atomic transfer: immediate write lock
BEGIN IMMEDIATE;
UPDATE accounts
SET balance = balance - 10
WHERE id = 1;
UPDATE accounts
SET balance = balance + 10
WHERE id = 2;
COMMIT;
BEGIN IMMEDIATE reserva el bloqueo antes de cualquier escritura.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿Qué nivel de aislamiento ofrece SQLite en la práctica?
    • READ UNCOMMITTED, las lecturas sucias son posibles por defecto
    • READ COMMITTED, como PostgreSQL
    • SERIALIZABLE, porque solo un escritor actúa a la vez
    • Ninguno, no hay transacciones
  2. ¿Cuál es la diferencia entre BEGIN y BEGIN IMMEDIATE?
    • BEGIN IMMEDIATE toma el bloqueo de escritura de inmediato; BEGIN espera al primer acceso de escritura
    • BEGIN IMMEDIATE desactiva el rollback journal
    • BEGIN IMMEDIATE crea una nueva base de datos
    • No hay ninguna diferencia de comportamiento
  3. ¿Qué es una lectura fantasma?
    • Leer un valor que otra transacción todavía no ha confirmado
    • Releer la misma fila y obtener un valor diferente
    • Repetir una consulta de rango y ver aparecer filas nuevas insertadas entretanto
    • Leer una fila que se acaba de eliminar