Transacciones multidocumentos
Introducción
Las transacciones multidocumentos garantizan la integridad de los datos, porque si falla una sola de las operaciones que contienen, falla toda la transacción; por tanto, es imposible dejar nuestra base de datos en un estado intermedio que ponga en peligro la propia coherencia de la información que contiene. Por eso decimos que una transacción es atómica (es todo o nada).
Estas transacciones son independientes entre sí y se ejecutan en perfecto aislamiento, lo que significa que cualquier cambio realizado durante una transacción solo será visible fuera de ella una vez que haya sido validada y sus datos registrados permanentemente.
Acabamos de describir lo que en la jerga de las bases de datos se conoce como «propiedades ACID»: Atomicidad - Coherencia - Aislamiento - Durabilidad (Atomicity, Consistency, Isolation and Durability).
Introducidas por primera vez con la versión 4.0 de MongoDB y dirigidas únicamente a conjuntos de réplicas, las transacciones se han extendido a clusters fragmentados desde la versión 4.2, cuando se renombraron como transacciones distribuidas.
¡Ahora vamos a crear nuestra primera transacción! Para evitar tener que crear un conjunto de réplicas local, vamos a utilizar un clúster en Atlas, la solución en la nube de MongoDB disponible en: https://www.mongodb.com/atlas/database. Atlas ofrece clústeres gratuitos para aquellos que quieran...
Sesiones
Las transacciones y las sesiones están estrechamente vinculadas, como se puede ver en el código anterior: una transacción se ejecuta como parte de una sesión, que no puede tener más de una transacción abierta al mismo tiempo. La interrupción de una sesión, tanto si se desea como si no, cancela cualquier transacción en curso en esa sesión.
Veamos las diferentes partes de la sesión. En primer lugar, se crea a partir de la conexión actual a la base de datos (método getMongo()):
var sesión = db.getMongo().startSession()
A continuación, se inicia una transacción vinculada a esta sesión recién creada:
sesión.startTransaction()
Nuestra transacción es entonces confirmada o abortada. Para confirmarla, utilice commitTransaction de la siguiente manera:
sesión.commitTransaction()
Para interrumpir una transacción, y por lo tanto no realizar ninguna de las operaciones en ella, utilice abortTransaction:
sesión.abortTransaction()
Por último, la sesión debe cerrarse, sea cual sea el destino de nuestra transacción:
sesión.endSession()
Desde la sesión, también pudimos recuperar enlaces a nuestras colecciones y almacenarlos en variables, todo ello utilizando el método getDatabase:
var conciertos = sesión.getDatabase('test').conciertos
var tickets = sesión.getDatabase('test').tickets...
Un ejemplo: transacciones bancarias
Pedimos disculpas de antemano por la falta de originalidad del ejemplo que sigue, pero el escenario de las transacciones bancarias es EL caso más utilizado (¡y más revelador!) cuando hablamos de transacciones, sobre todo en bases de datos relacionales, ¡es cierto! Vamos a empezar creando una nueva base de datos llamada mibanco. En nuestro clúster Atlas, vamos a hacer clic en el botón Create Database (Crear base de datos) antes de introducir su nombre y el nombre de la primera colección que vamos a crear en ella, a saber, cuentas:


Una vez creada esta primera colección, creamos una segunda, esta vez llamada transacciones. Mientras que la primera contiene información básica sobre las cuentas bancarias de nuestros clientes ficticios (un número, un saldo), la segunda registra todos los flujos financieros que han tenido lugar en esas cuentas en una fecha determinada. Para ello, basta con hacer clic en el icono con el signo «+» que aparece junto al nombre de la base de datos:

Ahora tenemos una base de datos y sus dos colecciones listas para usar. Vamos a alimentar la colección de cuentas ejecutando inserciones desde la línea de comandos de MongoDB:
Atlas atlas-572dv1-shard-0 [primary] test> use mibanco
switched to db mibanco
Atlas atlas-572dv1-shard-0 [primary] mibanco> db.cuentas.insertany([
{"númcuenta": "ES7611112233334444", "saldo": 1000},
{"númcuenta": "ES761111223333555", "saldo": 1000}
])
{
acknowledged: true,
insertIds: {
'0': ObjectId("65d47916eaa5b922b5a3ca25"),
'1': ObjectId("65d47916eaa5b922b5a3ca26")
}
}
Ahora que la colección de cuentas ha sido rellenada con documentos, podemos empezar a implementar nuestra transacción. Esta estará contenida en una función JavaScript que llamaremos transferencia. No hace falta ser un banquero de renombre mundial para saber que una transferencia bancaria suele implicar a dos participantes:...
Conflictos de escritura
Cuando una transacción T1 está a punto de realizar cambios en un documento, puede que ya esté siendo modificado por una transacción T2. Si este es el caso, T2 establece un bloqueo hasta que esta transacción finalice. Si T1 no puede obtener un bloqueo en el documento en el que está compitiendo con T2, esperará, pero fallará después de un retraso de 5 milisegundos si no puede obtener los bloqueos.
Los bloqueos de escritura no son lo mismo que los bloqueos de lectura; son los únicos bloqueos que potencialmente pueden generar conflictos. Esto significa que los documentos que están siendo modificados por una transacción pueden ser leídos sin ningún problema: simplemente aparecerán en un estado que puede no ser el mismo en el que estarán una vez finalizada la transacción. Del mismo modo, la lectura de un documento no bloqueará una transacción que esté a punto de modificar su estado.
Limitar las transacciones
Este mecanismo tiene algunas limitaciones en MongoDB:
-
La duración de una transacción multidocumento está limitada a 60 segundos de forma predefinida. Después de este tiempo, la transacción simplemente será terminada por MongoDB. Si quiere cambiar este límite, necesitará jugar con transactionLifetimeLimitSeconds en la configuración de su servidor.
-
Los comandos de administración no están permitidos en una transacción (createUser, getParameter, etc.).
-
Las lecturas y escrituras están prohibidas en las colecciones local, admin y config.
-
También están prohibidas las entradas en colecciones cuyo prefijo sea system.
-
No es posible escribir en capped collections (colecciones limitadas).
Las transacciones son una característica muy utilizada en los sistemas de gestión de bases de datos relacionales y estaban muy ausentes en la mayoría de los programas alternativos a los totalmente relacionales. Esta expectativa se cumplió cuando aparecieron las transacciones multidocumento con la versión 4 de MongoDB. Para ser sinceros, a menos que sus aplicaciones sean extremadamente complejas, es muy probable que nunca necesite utilizarlas, ya que el modelo de datos flexible de MongoDB permite una desnormalización completa de los datos, lecturas y escrituras atómicas para un único documento (en contraposición...