Niveles de aislamiento transaccional para los más pequeños.



Hoy me gustaría traer una sección extremadamente interesante, pero a menudo cubierta de secretos para programadores mortales ordinarios de la base de datos (DB): niveles de aislamiento de transacciones. Como muestra la práctica, muchas personas asociadas con TI, en particular con el trabajo con bases de datos, tienen poca comprensión de por qué estos niveles son necesarios y cómo se pueden utilizar para su propio beneficio.

Poco de teoría


Las transacciones en sí no requieren explicaciones especiales; una transacción es N (N≥1) consultas a la base de datos que se ejecutarán con éxito todas juntas o no se ejecutarán. El aislamiento de transacciones muestra la cantidad de transacciones paralelas que se afectan entre sí.
Al elegir un nivel de transacción, estamos tratando de llegar a un consenso en la elección entre una alta consistencia de datos entre transacciones y la velocidad de estas transacciones.
Vale la pena señalar que la velocidad de ejecución más alta y la consistencia más baja se leen sin comprometer . La velocidad de ejecución más baja y la mayor consistencia son serializables .

Preparación del medio ambiente


Por ejemplo, se seleccionó MySQL DBMS. PostgreSQL también podría usarse, pero no es compatible con el nivel de aislamiento de lectura no confirmada , y en su lugar utiliza el nivel de lectura confirmada . Y resultó que diferentes DBMS perciben los niveles de aislamiento de manera diferente. Pueden tener varios matices para garantizar el aislamiento, tener niveles adicionales o no ser conocidos.

Cree un entorno utilizando la imagen MySQL terminada con Docker Hub. Y llenar la base de datos.

docker-compose.yaml
version: '3.4' services: db: image: mysql:8 environment: - MYSQL_ROOT_PASSWORD=12345 command: --init-file /init.sql volumes: - data:/var/lib/mysql - ./init.sql:/init.sql expose: - "3306" ports: - "3309:3306" volumes: data: 


Poblar la base de datos
 create database if not exists bank; use bank; create table if not exists accounts ( id int unsigned auto_increment primary key, login varchar(255) not null, balance bigint default 0 not null, created_at timestamp default now() ) collate=utf8mb4_unicode_ci; insert into accounts (login, balance) values ('petya', 1000); insert into accounts (login, balance) values ('vasya', 2000); insert into accounts (login, balance) values ('mark', 500); 


Consideremos cómo funcionan los niveles y sus características.
Ejecutaremos ejemplos en 2 transacciones ejecutadas simultáneamente. Condicionalmente, una transacción en la ventana izquierda se llamará transacción 1 (T1), en la ventana derecha - transacción 2 (T2).

Leer sin comprometerse


El nivel con la peor consistencia de datos, pero la velocidad de transacción más alta. El nombre del nivel habla por sí mismo: cada transacción ve cambios no comprometidos en otra transacción (el fenómeno de la lectura sucia ). Veamos cómo se afectan entre sí tales transacciones.

Paso 1. Comenzamos 2 transacciones paralelas.



Paso 2. Observamos qué información tenemos al principio.



Paso 3. Ahora realizamos las operaciones INSERT, DELETE, UPDATE en T1, y vemos lo que ahora ve otra transacción.



T2 ve datos de otra transacción que aún no se ha confirmado.

Paso 4. Y T2 puede obtener algunos datos.



Paso 5. Cuando revierta los cambios a T1, los datos recibidos por T2 serán erróneos.



En este nivel, es imposible utilizar datos sobre la base de qué conclusiones y decisiones críticas que son importantes para la aplicación se toman porque estas conclusiones pueden estar lejos de la realidad.
Este nivel se puede usar, por ejemplo, para cálculos aproximados de algo. El resultado COUNT (*) o MAX (*) se puede usar en algunos informes no estrictos.
Otro ejemplo es el modo de depuración. Cuando durante una transacción, desea ver qué sucede con la base de datos.

Leer comprometido


Para este nivel, las transacciones ejecutadas simultáneamente solo ven cambios confirmados de otras transacciones. Por lo tanto, este nivel proporciona protección contra la lectura sucia .

Los pasos 1 y 2 son similares al ejemplo anterior.

Paso 3. También realizamos 3 operaciones simples con la tabla de cuentas (T1) y hacemos una selección completa de estas tablas en ambas transacciones.



Y veremos que el fenómeno de la lectura sucia está ausente en T2.

Paso 4. Arreglamos los cambios en T1 y verificamos lo que T2 ve ahora.



Ahora T2 ve todo lo que T1 ha hecho. Este es el llamado fenómeno de lectura no repetida cuando vemos líneas actualizadas y eliminadas (ACTUALIZAR, BORRAR), y el fenómeno de lectura de fantasmas cuando vemos registros agregados (INSERTAR).

Lectura repetible


Un nivel para evitar el fenómeno de la lectura no repetida . Es decir no vemos registros modificados y eliminados en otra transacción con otra transacción. Pero aún vemos los registros insertados de otra transacción. Leer fantasmas no va a ninguna parte.

Repita los pasos 1 y 2 nuevamente.

Paso 3. En T1, ejecutamos consultas INSERT, UPDATE y DELETE. Luego, en T2 intentamos actualizar la misma línea que se actualizó en T1.



Y obtenemos el bloqueo: T2 esperará hasta que T1 confirme los cambios o retroceda.

Paso 4. Arregle los cambios realizados por T1. Y lea nuevamente los datos de la tabla de cuentas en T2.



Como puede ver, no se observan los fenómenos de la lectura no repetida y la lectura de fantasmas . ¿Cómo es que, por defecto, la lectura repetible nos permite prevenir solo el fenómeno de la lectura no repetida ?

De hecho, MySQL carece del efecto de leer fantasmas para el nivel de lectura repetible . Y en PostgreSQL, también lo eliminaron para este nivel. Aunque en la representación clásica de este nivel, debemos observar este efecto.

Un pequeño ejemplo abstracto es el servicio de generación de certificados de regalo (códigos) y su uso. Por ejemplo, un atacante generó un código de certificado para sí mismo e intenta activarlo, intentando enviar varias solicitudes seguidas para activar un cupón. En este caso, comenzaremos varias transacciones ejecutadas simultáneamente trabajando con el mismo cupón. Y en algunas situaciones, puede ocurrir una activación doble o incluso triple del cupón (el usuario recibirá 2x / 3x bonos). Con la lectura repetible, en este caso, se producirá un bloqueo y la activación tendrá lugar una vez, y en los 2 niveles anteriores, es posible la activación múltiple. Un problema similar también se puede resolver utilizando la consulta SELECCIONAR PARA ACTUALIZAR , que también bloqueará el registro actualizado (cupón).

Serializable


El nivel en el que las transacciones se comportan como si nada más existiera, no hay influencia entre sí. En la representación clásica, este nivel elimina el efecto de leer fantasmas .

Paso 1. Comience la transacción.

Paso 2. T2 leemos la tabla de cuentas, luego T1 intentamos actualizar los datos leídos por T2.



Obtenemos bloqueo: no podemos cambiar los datos en una transacción leída en otra.

Paso 3. Tanto INSERT como DELETE nos llevan a la cerradura en T1.



Hasta que T2 complete su trabajo, no podremos trabajar con los datos que ha leído. Obtenemos la máxima consistencia de datos, no se registrarán datos adicionales. El precio de esto es una velocidad de transacción lenta debido a bloqueos frecuentes, por lo que con una arquitectura de aplicación deficiente esto puede ser un truco para usted.

Conclusiones


En la mayoría de las aplicaciones, el nivel de aislamiento rara vez cambia y se usa el valor predeterminado (por ejemplo, en MySQL es lectura repetible , en PostgreSQL es lectura confirmada ).

Pero periódicamente hay problemas en los que encontrar un mejor equilibrio entre la alta consistencia de datos o la velocidad de transacción puede ayudar a resolver algunos problemas de la aplicación.

Source: https://habr.com/ru/post/469415/


All Articles