Transacciones

Las transacciones son un concepto fundamental de todos los sistemas de bases de datos. El punto esencial de una transacción es que empaqueta múltiples pasos dentro de una operación única, todo o nada. Los estados intermedios entre cada paso no son visibles a otras transacciones concurrentes,y si ocurre alguna falla que previene que la transacción se complete, en definitiva, ninguno de los pasos afectan a la base de datos.

Por ejemplo, considere una base de datos bancaria que contiene los saldos de varias cuentas de clientes, y también los saldos de depósitos totales de las sucursales. Suponga que queremos registrar un pago de $100.00 de la cuenta de Alicia a la cuenta de Roberto. Simplificando bastante, los comandos SQL para esto se podrían parecer a:

UPDATE cuentas SET saldo = saldo - 100.00
    WHERE nombre = 'Alicia';
UPDATE sucursales SET saldo = saldo - 100.00
    WHERE nombre = (SELECT nombre_sucursal FROM cuentas WHERE nombre = 'Alicia');
UPDATE cuentas SET saldo = saldo + 100.00
    WHERE nombre = 'Roberto';
UPDATE sucursales SET saldo = saldo + 100.00
    WHERE nombre = (SELECT nombre_sucursal FROM cuentas WHERE nombre = 'Roberto');

El detalle de estos comandos no es importante; el punto importante es que hay varias modificaciones separadas involucradas para lograr este resultado en vez de una operación simple. Los encargados del banco van a querer asegurarse de que o todas estas modificaciones ocurran, o que ninguna lo haga. No van a querer que una falla del sistema resulte en que Roberto reciba $100.00 que no fue debitado a Alicia. Ni que Alicia se enoje en el caso de que su cuenta fuese debitada sin que se le acredite a Roberto. Necesitamos garantizar que si algo sale mal en la operación, ninguno de los pasos ejecutados tengan efecto. Agrupando las actualizaciones dentro de una transacción nos da esta garantía. Se dice que una transacción es atómica: desde el punto de vista de otras transacciones, o sucede completamente o no sucede en absoluto.

También queremos garantizar que una vez que la transacción es completada y reconocida por el sistema de base de datos, ha sido de hecho registrada permanentemente y no se perderá aún en el caso de que sobrevenga después de eso un accidente (crash). Por ejemplo, si estamos registrando un retiro de efectivo de Roberto, no queremos que bajo ninguna oportunidad el débito de su cuenta desaparezca en un accidente (crash) justo después de que él se vaya del banco. Una base de datos transaccional garantiza que todas las actualizaciones hechas por una transacción se registran en almacenamiento permanente (por ejemplo, en el disco) antes de que la transacción sea reportada como completa.

Otra propiedad importante de las bases de datos transaccionales está relacionada estrechamente con la noción de actualizaciones atómicas: cuando múltiples transacciones se están ejecutando al mismo tiempo, cada una no debería poder ver los cambios incompletos hechos por el resto. Por ejemplo, si una transacción está ocupada totalizando todos los saldos de una sucursal, no debería poder incluir el débito de la cuenta de Alicia pero no el crédito de la cuenta de la sucursal de Roberto, ni viceversa. Por lo tanto las transacciones deben ser a todo o nada no sólo en términos de efectos permanentes a la base de datos, sino también en términos de su visibilidad mientras van sucediendo. Las actualizaciones hechas hasta el momento por una transacción abierta son invisibles a otras transacciones hasta que dicha transacción se complete, con lo cual todas las actualizaciones se vuelven visibles simultáneamente.

En PostgreSQL, una transacción se crea al rodear los comandos SQL de la transacción con las sentencias BEGIN y COMMIT. Por lo que nuestra transacción bancaria en realidad se parecería a:

BEGIN;
UPDATE cuentas SET saldo = saldo - 100.00
    WHERE name = 'Alicia';
-- etc etc
COMMIT;

Si, a mitad de camino de la transacción, decidimos que no queremos comprometerla (quizás justo nos dimos cuenta que el saldo de Alicia se hizo negativo), podemos enviar el comando ROLLBACK en vez de COMMIT, y las actualizaciones hasta aquí serán canceladas.

De hecho PostgreSQL trata a cada sentencia SQL como si fuera ejecutada dentro de una transacción. Si no envía un comando BEGIN, cada sentencia individual tendrá un BEGIN implícito y (si es exitosa) un COMMIT alrededor de ella. En ocasiones, un grupo de sentencias rodeadas por un BEGIN y COMMIT es llamado un bloque de transacción.

Nota: Algunas biblioteca cliente ejecutan automáticamente un comando BEGIN y COMMIT, por lo que podría obtener el efecto de bloque de transacción sin pedirlo. Verifique la documentación para la interfaz que esté usando.

Es posible controlar las sentencias en una transacción de manera mas granular usando puntos de retorno (savepoints). Los puntos de retorno permiten selectivamente desechar partes de una transacción, y al mismo tiempo confirmar el resto. Luego de definir un punto de retorno con SAVEPOINT, puede necesitar volver atrás a dicho punto con ROLLBACK TO. Todos los cambios en la base de datos entre la definición del punto de retorno y la vuelta atrás son desechados, pero los cambios anteriores al punto de retorno son mantenidos.

Luego de volver atrás a un punto de retorno, continúa estando definido, por lo que puede volver atrás varias veces. A la inversa, si está segur de que no necesitará volver nuevamente a un punto de retorno particular, el mismo puede ser liberado, con lo que el sistema puede liberar recursos. Tenga en mente que tanto liberar el punto de retorno o volviendo atrás automáticamente liberará todos los puntos de retorno que fueron definidos luego.

Todo esto está sucediendo dentro de un bloque de transacción, por lo que nada de esto es visible a otras sesiones de la base de datos. Cuando y si confirma el bloque de transacción, las acciones confirmadas se volverán visible como una unidad a otras sesiones, mientras que las acciones vueltas atrás nunca serán visibles en absoluto.

Recordando la base de datos bancaria, suponga que debitamos $100.00 de la cuenta de Alicia, y acreditamos a la cuenta de Roberto, solo para encontrar posteriormente que deberíamos haber acreditado la cuenta de Walter. Podríamos hacerlo usando puntos de retorno así:

BEGIN;
UPDATE cuentas SET saldo = saldo - 100.00
    WHERE nombre = 'Alicia';
SAVEPOINT mi_punto_retorno;
UPDATE cuentas SET saldo = saldo + 100.00
    WHERE nombre = 'Roberto';
-- ups ... olvidemos aquello y usemos la cuenta de Walter
ROLLBACK TO mi_punto_retorno;
UPDATE cuentas SET saldo = saldo + 100.00
    WHERE nombre = 'Walter';
COMMIT;

Por supuesto este ejemplo es demasiado simplificado, pero hay basante control posible entre dentro de un bloque de transacción mediante el uso de puntos de retornos. Más aún, ROLLBACK TO es la única manera de retomar el control de un bloque de transacción que ha sido puesto en estado abortado por el sistema en el caso de error, sino solo se podría volver atrás toda la transacción y recomenzar.