TALLER - Transacciones

Instituto ICOP

Bases de Datos II Bases de Datos II

Docente: Ing. María Eugenia Weibel

🐘 Transacciones en PostgreSQL: Guía Completa de Estudio

🎯 Introducción a las Transacciones

¿Qué es una Transacción?

Una transacción es una unidad de trabajo lógica que agrupa una o más operaciones de base de datos que deben ejecutarse como un todo indivisible. En PostgreSQL, las transacciones garantizan que los datos permanezcan consistentes incluso cuando múltiples usuarios acceden simultáneamente a la base de datos.

Ciclo de Vida de una Transacción

-- Inicio de transacción
BEGIN;

-- Operaciones de la transacción
INSERT INTO accounts (id, balance, name) VALUES (1, 1000, 'Juan');
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

-- Finalización exitosa
COMMIT;

-- O cancelación (rollback)
ROLLBACK;

Estados de una Transacción

  • ACTIVE: La transacción está ejecutándose
  • PARTIALLY COMMITTED: Todas las operaciones completadas, esperando confirmación
  • COMMITTED: Cambios confirmados permanentemente
  • ABORTED: Transacción cancelada, cambios revertidos
  • FAILED: Error ocurrido, requiere rollback

⚙️ Propiedades ACID

Atomicidad (Atomicity)

Concepto: “Todo o nada” - Una transacción se ejecuta completamente o no se ejecuta en absoluto.

Ejemplo:

BEGIN;
-- Transferencia bancaria: debe ser atómica
UPDATE accounts SET balance = balance - 500 WHERE id = 1; -- Débito
UPDATE accounts SET balance = balance + 500 WHERE id = 2; -- Crédito
COMMIT; -- Ambas operaciones o ninguna

Implementación en PostgreSQL: - WAL (Write-Ahead Logging) - Rollback automático en caso de error - Puntos de guardado (savepoints)

Consistencia (Consistency)

Concepto: Los datos deben mantener todas las reglas de integridad antes y después de la transacción.

Tipos de Consistencia: - Consistencia de Entidad: Claves primarias únicas - Consistencia Referencial: Claves foráneas válidas - Consistencia de Dominio: Tipos de datos y restricciones - Consistencia Definida por Usuario: Triggers y checks

Ejemplo:

-- Restricción que mantiene consistencia
ALTER TABLE accounts ADD CONSTRAINT positive_balance 
CHECK (balance >= 0);

BEGIN;
UPDATE accounts SET balance = balance - 1500 WHERE id = 1;
-- Si balance < 0, la transacción falla manteniendo consistencia
COMMIT;

Isolamiento (Isolation)

Concepto: Las transacciones concurrentes no deben interferir entre sí.

Implementación: - Niveles de aislamiento configurables - Sistema de locks multinivel - MVCC (MultiVersion Concurrency Control)

Durabilidad (Durability)

Concepto: Una vez confirmada, los cambios persisten incluso ante fallos del sistema.

Mecanismos: - WAL (Write-Ahead Logging) - Checkpoints periódicos - Sincronización a disco


🔒 Niveles de Aislamiento

1. READ UNCOMMITTED

Características: - Nivel más bajo de aislamiento - Permite lecturas sucias (dirty reads) - Raramente usado en producción

Permite: - ✅ Dirty Reads - ✅ Non-Repeatable Reads
- ✅ Phantom Reads

Configuración:

BEGIN TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT * FROM accounts; -- Puede leer datos no confirmados
COMMIT;

2. READ COMMITTED (Por Defecto)

Características: - Nivel por defecto en PostgreSQL - Previene lecturas sucias - Balance entre consistencia y rendimiento

Permite: - ❌ Dirty Reads - ✅ Non-Repeatable Reads - ✅ Phantom Reads

Ejemplo:

-- Sesión 1
BEGIN;
UPDATE accounts SET balance = 1500 WHERE id = 1;
-- No hace COMMIT todavía

-- Sesión 2 (READ COMMITTED)
SELECT balance FROM accounts WHERE id = 1;
-- Verá el valor original (1000), no 1500

3. REPEATABLE READ

Características: - Garantiza lecturas repetibles dentro de la transacción - Utiliza snapshot de inicio de transacción - Previene dirty reads y non-repeatable reads

Permite: - ❌ Dirty Reads - ❌ Non-Repeatable Reads - ✅ Phantom Reads

Ejemplo:

BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT COUNT(*) FROM accounts WHERE balance > 500; -- Resultado: 10

-- Otra sesión inserta un registro
-- INSERT INTO accounts VALUES (99, 600, 'Ana');

SELECT COUNT(*) FROM accounts WHERE balance > 500; -- Resultado: 10 (igual)
COMMIT;

4. SERIALIZABLE

Características: - Nivel más alto de aislamiento - Emula ejecución serial de transacciones - Previene todos los problemas de concurrencia

Permite: - ❌ Dirty Reads - ❌ Non-Repeatable Reads - ❌ Phantom Reads

Implementación:

BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- PostgreSQL usa Serializable Snapshot Isolation (SSI)
SELECT * FROM accounts WHERE balance > 1000;
UPDATE accounts SET balance = balance * 1.1 WHERE balance > 1000;
COMMIT;

⚠️ Problemas de Concurrencia

1. Dirty Read (Lectura Sucia)

Definición: Una transacción lee datos modificados por otra transacción que aún no ha hecho commit.

Problema:

-- Transacción A
BEGIN;
UPDATE accounts SET balance = 2000 WHERE id = 1;
-- No hace COMMIT

-- Transacción B (READ UNCOMMITTED)
SELECT balance FROM accounts WHERE id = 1; -- Lee 2000

-- Transacción A
ROLLBACK; -- Los cambios se pierden, B leyó datos "sucios"

Prevención: Usar READ COMMITTED o superior.

2. Non-Repeatable Read (Lectura No Repetible)

Definición: Una transacción lee el mismo dato dos veces y obtiene valores diferentes.

Problema:

-- Transacción A (READ COMMITTED)
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- Lee 1000

-- Transacción B
UPDATE accounts SET balance = 1500 WHERE id = 1;
COMMIT;

-- Transacción A (continuando)
SELECT balance FROM accounts WHERE id = 1; -- Ahora lee 1500
COMMIT;

Prevención: Usar REPEATABLE READ o SERIALIZABLE.

3. Phantom Read (Lectura Fantasma)

Definición: Una consulta ejecutada dos veces retorna conjuntos de registros diferentes.

Problema:

-- Transacción A (REPEATABLE READ)
BEGIN;
SELECT COUNT(*) FROM accounts WHERE balance > 500; -- Resultado: 5

-- Transacción B
INSERT INTO accounts VALUES (100, 600, 'Carlos');
COMMIT;

-- Transacción A (continuando)
SELECT COUNT(*) FROM accounts WHERE balance > 500; -- Resultado: 6
COMMIT;

Prevención: Usar SERIALIZABLE.

4. Lost Update (Actualización Perdida)

Definición: Dos transacciones actualizan el mismo dato y una sobrescribe a la otra.

Problema y Solución:

-- Problema: Actualización perdida
-- T1: balance = 1000, lee valor
-- T2: balance = 1000, lee valor
-- T1: actualiza a 1100 (1000 + 100)
-- T2: actualiza a 1200 (1000 + 200) -- ¡Perdió el cambio de T1!

-- Solución: Usar SELECT FOR UPDATE
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- Esto bloquea el registro para otras transacciones
UPDATE accounts SET balance = balance + 100 WHERE id = 1;
COMMIT;

🔐 Mecanismos de Bloqueo

Tipos de Locks

1. Row-Level Locks (Bloqueos de Fila)

FOR UPDATE:

BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- Bloqueo exclusivo: nadie más puede modificar esta fila
UPDATE accounts SET balance = balance + 100 WHERE id = 1;
COMMIT;

FOR SHARE:

BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR SHARE;
-- Bloqueo compartido: otros pueden leer, pero no modificar
-- Operaciones de lectura...
COMMIT;

FOR NO KEY UPDATE:

-- Permite actualizaciones concurrentes que no afecten claves
SELECT * FROM accounts WHERE id = 1 FOR NO KEY UPDATE;

FOR KEY SHARE:

-- Bloqueo más débil, solo previene cambios en claves
SELECT * FROM accounts WHERE id = 1 FOR KEY SHARE;

2. Table-Level Locks (Bloqueos de Tabla)

-- Bloqueo explícito de tabla
BEGIN;
LOCK TABLE accounts IN SHARE MODE;
-- Solo permite lecturas concurrentes
SELECT * FROM accounts;
COMMIT;

-- Otros modos de bloqueo de tabla:
LOCK TABLE accounts IN ACCESS SHARE MODE;      -- Más permisivo
LOCK TABLE accounts IN ROW SHARE MODE;
LOCK TABLE accounts IN ROW EXCLUSIVE MODE;
LOCK TABLE accounts IN SHARE UPDATE EXCLUSIVE MODE;
LOCK TABLE accounts IN SHARE MODE;
LOCK TABLE accounts IN SHARE ROW EXCLUSIVE MODE;
LOCK TABLE accounts IN EXCLUSIVE MODE;
LOCK TABLE accounts IN ACCESS EXCLUSIVE MODE;  -- Más restrictivo

3. Advisory Locks (Bloqueos de Aplicación)

-- Bloqueos definidos por la aplicación
SELECT pg_advisory_lock(123456);
-- Lógica de aplicación que requiere sincronización
SELECT pg_advisory_unlock(123456);

-- Versión que no bloquea
SELECT pg_try_advisory_lock(123456); -- Retorna true/false

MVCC (MultiVersion Concurrency Control)

PostgreSQL usa MVCC para permitir alta concurrencia:

Funcionamiento: - Cada fila tiene información de versión (xmin, xmax) - Las transacciones ven snapshots consistentes - No hay bloqueos de lectura tradicionales - Los writers no bloquean a los readers

Ejemplo de Versionado:

-- Estado inicial: (xmin=100, xmax=null, data='Juan, 1000')

-- Transacción 101 actualiza:
UPDATE accounts SET balance = 1100 WHERE id = 1;
-- Nuevo registro: (xmin=101, xmax=null, data='Juan, 1100')
-- Registro anterior: (xmin=100, xmax=101, data='Juan, 1000')

-- Las transacciones ven la versión apropiada según su snapshot

⚡ Deadlocks

¿Qué es un Deadlock?

Un deadlock ocurre cuando dos o más transacciones se bloquean mutuamente, esperando recursos que la otra tiene.

Ejemplo de Deadlock

-- Sesión 1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- Lock en fila 1
-- Esperando...
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- Intenta lock en fila 2

-- Sesión 2 (ejecutándose simultáneamente)
BEGIN;
UPDATE accounts SET balance = balance - 50 WHERE id = 2;  -- Lock en fila 2
UPDATE accounts SET balance = balance + 50 WHERE id = 1;  -- Intenta lock en fila 1
-- ¡DEADLOCK!

Detección y Resolución de Deadlocks

PostgreSQL automáticamente: 1. Detecta deadlocks usando un detector de ciclos 2. Elige una víctima (transacción más barata de abortar) 3. Aborta la transacción víctima con error 4. Permite que la otra transacción continúe

Configuración:

-- Tiempo antes de detectar deadlock (por defecto: 1 segundo)
SET deadlock_timeout = '2s';

-- Ver deadlocks en el log
SET log_lock_waits = on;

Prevención de Deadlocks

  1. Orden consistente de acceso a recursos:
-- Siempre acceder a tablas en el mismo orden
-- Mal: tabla A, luego tabla B en una transacción; tabla B, luego A en otra
-- Bien: siempre tabla A, luego tabla B
  1. Transacciones cortas:
-- Minimizar el tiempo de bloqueo
BEGIN;
-- Operaciones rápidas y específicas
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT; -- Confirmar rápidamente
  1. Usar timeouts:
-- Configurar timeout de transacción
SET statement_timeout = '30s';

💻 Comandos y Sintaxis

Comandos Básicos de Transacción

-- Iniciar transacción
BEGIN;
START TRANSACTION;

-- Confirmar cambios
COMMIT;

-- Cancelar cambios
ROLLBACK;

-- Transacción con nivel de aislamiento específico
BEGIN ISOLATION LEVEL SERIALIZABLE;
BEGIN ISOLATION LEVEL REPEATABLE READ;
BEGIN ISOLATION LEVEL READ COMMITTED;
BEGIN ISOLATION LEVEL READ UNCOMMITTED;

-- Transacción de solo lectura
BEGIN READ ONLY;

-- Transacción con características múltiples
BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE;

Savepoints (Puntos de Guardado)

BEGIN;

INSERT INTO accounts VALUES (1, 1000, 'Juan');

SAVEPOINT sp1;

INSERT INTO accounts VALUES (2, 500, 'María');
-- Error en esta operación

ROLLBACK TO SAVEPOINT sp1;
-- Solo revierte hasta el savepoint, mantiene la primera inserción

INSERT INTO accounts VALUES (2, 600, 'María'); -- Corregido

COMMIT;

Configuración de Transacciones

-- A nivel de sesión
SET default_transaction_isolation = 'repeatable read';
SET default_transaction_read_only = on;
SET default_transaction_deferrable = on;

-- A nivel de base de datos
ALTER DATABASE mydb SET default_transaction_isolation = 'serializable';

-- A nivel de usuario
ALTER ROLE myuser SET default_transaction_isolation = 'repeatable read';

✅ Mejores Prácticas

1. Diseño de Transacciones

-- ✅ BIEN: Transacciones cortas y específicas
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

-- ❌ MAL: Transacciones largas con lógica de aplicación
BEGIN;
SELECT * FROM accounts WHERE id = 1;
-- ... lógica compleja en la aplicación ...
-- ... espera por input del usuario ...
UPDATE accounts SET balance = new_balance WHERE id = 1;
COMMIT;

2. Manejo de Errores

-- Estructura recomendada para manejo de errores
DO $$
DECLARE
    account_id INTEGER := 1;
    transfer_amount DECIMAL := 100;
BEGIN
    -- Iniciar transacción implícita
    UPDATE accounts SET balance = balance - transfer_amount 
    WHERE id = account_id;
    
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Cuenta % no encontrada', account_id;
    END IF;
    
    UPDATE accounts SET balance = balance + transfer_amount 
    WHERE id = 2;
    
    -- Si todo va bien, confirma automáticamente
EXCEPTION
    WHEN OTHERS THEN
        -- Rollback automático en caso de error
        RAISE NOTICE 'Error en transferencia: %', SQLERRM;
        RAISE; -- Re-lanza el error
END $$;

3. Optimización de Concurrencia

-- Usar SELECT FOR UPDATE solo cuando sea necesario
-- ✅ BIEN: Solo cuando vas a actualizar
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

-- ❌ MAL: Bloquear innecesariamente
SELECT * FROM accounts FOR UPDATE; -- Bloquea toda la tabla

4. Índices para Concurrencia

-- Crear índices apropiados para reducir bloqueos
CREATE INDEX CONCURRENTLY idx_accounts_balance ON accounts(balance);
CREATE INDEX CONCURRENTLY idx_accounts_status ON accounts(status) 
WHERE status = 'active';

🔍 Casos Prácticos

Caso 1: Sistema Bancario

-- Transferencia bancaria segura
CREATE OR REPLACE FUNCTION transfer_funds(
    from_account INTEGER,
    to_account INTEGER,
    amount DECIMAL
) RETURNS BOOLEAN AS $$
DECLARE
    from_balance DECIMAL;
BEGIN
    -- Iniciar transacción implícita
    
    -- Bloquear cuenta origen y verificar saldo
    SELECT balance INTO from_balance 
    FROM accounts 
    WHERE id = from_account 
    FOR UPDATE;
    
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Cuenta origen % no existe', from_account;
    END IF;
    
    IF from_balance < amount THEN
        RAISE EXCEPTION 'Saldo insuficiente: % disponible, % requerido', 
                       from_balance, amount;
    END IF;
    
    -- Realizar transferencia
    UPDATE accounts SET balance = balance - amount 
    WHERE id = from_account;
    
    UPDATE accounts SET balance = balance + amount 
    WHERE id = to_account;
    
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Cuenta destino % no existe', to_account;
    END IF;
    
    -- Registrar transacción
    INSERT INTO transaction_log (from_account, to_account, amount, timestamp)
    VALUES (from_account, to_account, amount, NOW());
    
    RETURN TRUE;
    
EXCEPTION
    WHEN OTHERS THEN
        -- El rollback es automático
        RETURN FALSE;
END;
$$ LANGUAGE plpgsql;

-- Uso
SELECT transfer_funds(1, 2, 150.00);

Caso 2: Sistema de Inventario

-- Reserva de productos con control de stock
CREATE OR REPLACE FUNCTION reserve_product(
    product_id INTEGER,
    quantity INTEGER,
    customer_id INTEGER
) RETURNS INTEGER AS $$
DECLARE
    current_stock INTEGER;
    reservation_id INTEGER;
BEGIN
    -- Bloquear producto para verificar stock
    SELECT stock INTO current_stock
    FROM products
    WHERE id = product_id
    FOR UPDATE;
    
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Producto % no existe', product_id;
    END IF;
    
    IF current_stock < quantity THEN
        RAISE EXCEPTION 'Stock insuficiente: % disponible, % solicitado',
                       current_stock, quantity;
    END IF;
    
    -- Crear reserva
    INSERT INTO reservations (product_id, customer_id, quantity, created_at)
    VALUES (product_id, customer_id, quantity, NOW())
    RETURNING id INTO reservation_id;
    
    -- Reducir stock
    UPDATE products 
    SET stock = stock - quantity
    WHERE id = product_id;
    
    RETURN reservation_id;
END;
$$ LANGUAGE plpgsql;

Caso 3: Sistema de Votación

-- Sistema de votación con prevención de votos duplicados
CREATE OR REPLACE FUNCTION cast_vote(
    voter_id INTEGER,
    candidate_id INTEGER
) RETURNS BOOLEAN AS $$
BEGIN
    -- Verificar si ya votó (usando constraint único)
    INSERT INTO votes (voter_id, candidate_id, voted_at)
    VALUES (voter_id, candidate_id, NOW());
    
    -- Actualizar contador de candidato
    UPDATE candidates 
    SET vote_count = vote_count + 1
    WHERE id = candidate_id;
    
    RETURN TRUE;
    
EXCEPTION
    WHEN unique_violation THEN
        RAISE NOTICE 'El votante % ya emitió su voto', voter_id;
        RETURN FALSE;
END;
$$ LANGUAGE plpgsql;

-- Constraint para prevenir votos duplicados
ALTER TABLE votes ADD CONSTRAINT unique_voter 
UNIQUE (voter_id);

📊 Monitoreo y Diagnóstico

Consultas de Monitoreo

1. Transacciones Activas

-- Ver transacciones activas
SELECT 
    pid,
    usename,
    application_name,
    client_addr,
    backend_start,
    xact_start,
    query_start,
    state,
    query
FROM pg_stat_activity 
WHERE state IN ('active', 'idle in transaction')
ORDER BY xact_start;

2. Bloqueos Actuales

-- Ver bloqueos activos
SELECT 
    blocked_locks.pid AS blocked_pid,
    blocked_activity.usename AS blocked_user,
    blocking_locks.pid AS blocking_pid,
    blocking_activity.usename AS blocking_user,
    blocked_activity.query AS blocked_statement,
    blocking_activity.query AS current_statement_in_blocking_process
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity 
    ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks blocking_locks 
    ON blocking_locks.locktype = blocked_locks.locktype
    AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database
    AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
    AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
    AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
    AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
    AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
    AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
    AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
    AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
    AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity 
    ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;

3. Estadísticas de Deadlock

-- Ver estadísticas de deadlocks
SELECT 
    datname,
    deadlocks,
    temp_files,
    temp_bytes
FROM pg_stat_database 
WHERE datname = current_database();

4. Transacciones Largas

-- Encontrar transacciones que llevan mucho tiempo
SELECT 
    pid,
    now() - xact_start AS transaction_duration,
    query,
    state
FROM pg_stat_activity 
WHERE xact_start IS NOT NULL
  AND now() - xact_start > interval '5 minutes'
ORDER BY transaction_duration DESC;

Configuración de Logs

-- Configurar logging para debugging
ALTER SYSTEM SET log_lock_waits = on;
ALTER SYSTEM SET log_statement = 'all';
ALTER SYSTEM SET log_duration = on;
ALTER SYSTEM SET log_min_duration_statement = 1000; -- ms
ALTER SYSTEM SET deadlock_timeout = '1s';

-- Recargar configuración
SELECT pg_reload_conf();

Herramientas de Análisis

-- Extensión pg_stat_statements para análisis de consultas
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- Ver consultas que más tiempo consumen
SELECT 
    query,
    calls,
    total_time,
    mean_time,
    stddev_time,
    rows
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;

📝 Resumen Ejecutivo

Puntos Clave para Recordar

  1. Las transacciones garantizan ACID: Atomicidad, Consistencia, Aislamiento y Durabilidad

  2. PostgreSQL usa MVCC: Permite alta concurrencia sin bloquear lecturas

  3. Niveles de aislamiento:

    • READ COMMITTED (por defecto): Balance entre consistencia y rendimiento
    • REPEATABLE READ: Para análisis que requieren consistencia
    • SERIALIZABLE: Máxima consistencia, posibles problemas de rendimiento
  4. Mantén transacciones cortas: Reduce bloqueos y mejora concurrencia

  5. Maneja errores apropiadamente: Usa bloques de excepción y rollbacks

  6. Monitorea regularmente: Usa vistas del sistema para identificar problemas

Comandos Esenciales

-- Control de transacciones
BEGIN; COMMIT; ROLLBACK;

-- Bloqueos explícitos
SELECT ... FOR UPDATE;
SELECT ... FOR SHARE;

-- Configuración de aislamiento
BEGIN ISOLATION LEVEL REPEATABLE READ;

-- Savepoints
SAVEPOINT sp1; ROLLBACK TO sp1;

-- Monitoreo
SELECT * FROM pg_stat_activity;
SELECT * FROM pg_locks;

Este material de estudio cubre todos los aspectos fundamentales de las transacciones en PostgreSQL, desde conceptos básicos hasta técnicas avanzadas de optimización y monitoreo. Úsalo como referencia y guía práctica para implementar transacciones robustas y eficientes en tus aplicaciones.