sql - seccion - race condition vulnerability
¿Las transacciones de la base de datos previenen las condiciones de carrera? (4)
Depende de los rdbms específicos. Generalmente, las transacciones adquieren bloqueos según se decide durante el plan de evaluación de la consulta. Algunos pueden solicitar bloqueos de nivel de tabla, otro nivel de columna, otro nivel de registro, el segundo es el preferido para el rendimiento. La respuesta corta a tu pregunta es sí.
En otras palabras, una transacción está destinada a agrupar un conjunto de consultas y representarlas como una operación atómica. Si la operación falla, los cambios se deshacen. No sé exactamente qué hace el adaptador que está utilizando, pero si se ajusta a la definición de transacciones, debería estar bien.
Si bien esto garantiza la prevención de las condiciones de la raza, no previene explícitamente la inanición o los puntos muertos. El administrador de bloqueo de transacciones está a cargo de eso. Las cerraduras de mesa se usan en algún momento, pero tienen un precio considerable para reducir el número de operaciones simultáneas.
No está del todo claro para mí qué hacen las transacciones en los sistemas de bases de datos. Sé que pueden usarse para deshacer una lista completa de actualizaciones (por ejemplo, deducir dinero en una cuenta y agregarla a otra), pero ¿es eso todo lo que hacen? Específicamente, ¿pueden usarse para prevenir condiciones de carrera? Por ejemplo:
// Java/JPA example
em.getTransaction().begin();
User u = em.find(User.class, 123);
u.credits += 10;
em.persist(u); // Note added in 2016: this line is actually not needed
em.getTransaction().commit();
(Sé que esto probablemente podría escribirse como una única consulta de actualización, pero ese no es siempre el caso)
¿Está este código protegido contra condiciones de carrera?
Estoy más interesado en MySQL5 + InnoDB, pero las respuestas generales también son bienvenidas.
Depende del nivel de aislamiento (en serializable evitará la condición de carrera, ya que generalmente en el nivel de aislamiento serializable las transacciones se procesan en secuencia, no en paralelo (o al menos se usa bloqueo exclusivo, por lo que las transacciones que modifican las mismas filas se realizan en Para evitar la condición de carrera, mejor bloquee manualmente el registro (mysql, por ejemplo, es compatible con la instrucción "seleccionar ... para actualizar", que incluye bloqueo de escritura en los registros seleccionados)
El nivel de la base de datos admite la atomicidad de las transacciones en diversos grados, denominados niveles de aislamiento. Consulte la documentación de su sistema de gestión de bases de datos para conocer los niveles de aislamiento admitidos y sus compensaciones. El nivel de aislamiento más fuerte, Serializable , requiere que las transacciones se ejecuten como si fueran ejecutadas una por una. Esto normalmente se implementa mediante el uso de bloqueos exclusivos en la base de datos. Esto puede ser causa de puntos muertos, que el sistema de administración de la base de datos detecta y corrige al deshacer algunas transacciones involucradas. Este enfoque a menudo se conoce como bloqueo pesimista .
Muchos mapeadores de objetos relacionales (incluidos los proveedores de JPA) también admiten el bloqueo optimista , donde los conflictos de actualización no se evitan en la base de datos, sino que se detectan en el nivel de aplicación, que luego revierte la transacción. Si tiene habilitado el bloqueo optimista, una ejecución típica de su código de ejemplo emitirá las siguientes consultas de SQL:
select id, version, credits from user where id = 123;
Digamos que esto vuelve (123, 13, 100).
update user set version = 14, credit = 110 where id = 123 and version = 13;
La base de datos nos dice cuántas filas se actualizaron. Si era uno, no había actualización conflictiva. Si era cero, se produjo una actualización conflictiva y el proveedor de JPA lo hará
rollback;
y lanzar una excepción para que el código de la aplicación pueda manejar la transacción fallida, por ejemplo, volviendo a intentarlo.
Resumen: con cualquiera de los dos enfoques, su declaración se puede proteger de las condiciones de la raza.
TL / DR: Las transacciones no impiden inherentemente todas las condiciones de carrera. Aún necesita bloqueo, manejo abortar y reintentar, u otras medidas de protección en todas las implementaciones de bases de datos del mundo real. blog.2ndquadrant.com/… .
Aislamiento
A lo que te refieres con tu pregunta es el isolation I ACID . La idea académicamente pura es que las transacciones deben proporcionar un aislamiento perfecto, de modo que el resultado sea el mismo que si todas las transacciones se ejecutaran en serie. En realidad, ese es raramente el caso en implementaciones RDBMS reales; las capacidades varían según la implementación, y las reglas pueden debilitarse mediante el uso de un nivel de aislamiento más débil como READ COMMITTED
. En la práctica, no puede asumir que las transacciones impidan todas las condiciones de carrera , incluso en el aislamiento SERIALIZABLE
.
Algunos RDBMS tienen habilidades más fuertes que otros. Por ejemplo, PostgreSQL 9.2 y más recientes tienen un aislamiento SERIALIZABLE
bastante bueno que detecta la mayoría de las posibles interacciones (pero no todas) entre las transacciones y anula todas las transacciones en conflicto, excepto una . Por lo que puede ejecutar transacciones en paralelo de forma bastante segura.
Pocos sistemas, si es que tienen alguno 3 , tienen un aislamiento SERIALIZABLE
realmente perfecto que evita todas las posibles carreras y anomalías, incluidos problemas como la escalada de bloqueos y el bloqueo de pedidos de bloqueos.
Incluso con un fuerte aislamiento, algunos sistemas (como PostgreSQL) abortarán las transacciones en conflicto, en lugar de hacerlos esperar y ejecutarlos en serie. Su aplicación debe recordar lo que estaba haciendo y volver a intentar la transacción. Entonces, si bien la transacción ha evitado que las anomalías relacionadas con la concurrencia se almacenen en la base de datos, se hace de una manera que no es transparente para la aplicación.
Atomicidad
Podría decirse que el propósito principal de una transacción de base de datos es que proporciona una confirmación atómica . Los cambios no entrarán en vigor hasta que confirme la transacción. Cuando te comprometes, todos los cambios surten efecto en el mismo instante en lo que respecta a otras transacciones. Ninguna transacción puede ver solo algunos de los cambios que realiza una transacción 1,2 . De manera similar, si ROLLBACK
, ninguno de los cambios de la transacción será visto por ninguna otra transacción; Es como si tu transacción nunca hubiera existido.
Esa es la A en ACID .
Durabilidad
Otra es la durabilidad - la D en ACID . Especifica que cuando realiza una transacción, debe guardarse en un almacenamiento que sobrevivirá a una falla como la pérdida de energía o un reinicio repentino.
Consistencia:
Ver wikipedia
Control de concurrencia optimista.
En lugar de usar niveles de bloqueo y / o altos niveles de aislamiento, es común que los ORM como Hibernate, EclipseLink, etc. usen el control de concurrencia optimista (a menudo llamado "bloqueo optimista") para superar las limitaciones de los niveles de aislamiento más débiles y al mismo tiempo preservar el rendimiento.
Una característica clave de este enfoque es que le permite abarcar el trabajo en múltiples transacciones, lo que es una gran ventaja con los sistemas que cuentan con un alto número de usuarios y pueden tener grandes retrasos entre las interacciones con cualquier usuario determinado.
Referencias
Además de los enlaces en el texto, consulte el capítulo de documentación de PostgreSQL sobre bloqueo, aislamiento y concurrencia . Incluso si está utilizando un RDBMS diferente, aprenderá mucho de los conceptos que explica.
1 Estoy ignorando el nivel de aislamiento READ UNCOMMITTED
rara vez se implementa aquí por simplicidad; permite lecturas sucias.
2 Como señala @meriton, el corolario no es necesariamente cierto. Las lecturas fantasmas ocurren en cualquier cosa debajo de SERIALIZABLE
. Una parte de una transacción en curso no ve algunos cambios (por una transacción aún no confirmada), luego la siguiente parte de la transacción en curso ve los cambios cuando la otra transacción se confirma.
3 Bueno, IIRC SQLite2 lo hace en virtud de bloquear toda la base de datos cuando se intenta escribir, pero eso no es lo que yo llamaría una solución ideal para los problemas de concurrencia.