Níveis de isolamento transacional para os menores



Hoje eu gostaria de trazer uma seção extremamente interessante, mas frequentemente coberta de segredos para os programadores mortais comuns do banco de dados (DB) - níveis de isolamento de transações. Como mostra a prática, muitas pessoas associadas à TI, em particular ao trabalho com bancos de dados, têm pouco entendimento sobre por que esses níveis são necessários e como podem ser usados ​​para seu próprio benefício.

Pouco de teoria


As transações em si não exigem explicações especiais; uma transação é N (N≥1) consultas ao banco de dados que serão executadas com êxito todas juntas ou não. O isolamento da transação mostra quanto as transações paralelas estão afetando uma à outra.
Escolhendo um nível de transação, estamos tentando chegar a um consenso na escolha entre alta consistência de dados entre as transações e a velocidade dessas mesmas transações.
Vale ressaltar que a velocidade mais alta de execução e a menor consistência são lidas sem confirmação . A menor velocidade de execução e maior consistência é serializável .

Preparação do ambiente


Por exemplo, o MySQL DBMS foi selecionado. O PostgreSQL também pode ser usado, mas não suporta o nível de isolamento não confirmado de leitura e usa o nível de confirmação confirmada de leitura . E, como se viu, diferentes DBMSs percebem os níveis de isolamento de maneira diferente. Eles podem ter várias nuances para garantir o isolamento, ter níveis adicionais ou não serem bem conhecidos.

Crie um ambiente usando a imagem MySQL finalizada com o Docker Hub. E preencha o banco de dados.

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: 


Preenchendo o Banco de Dados
 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); 


Vamos considerar como os níveis funcionam e seus recursos.
Executaremos exemplos em 2 transações executadas simultaneamente. Condicionalmente, uma transação na janela esquerda será chamada de transação 1 (T1), na janela direita - transação 2 (T2).

Leitura não confirmada


O nível com a pior consistência de dados, mas a maior velocidade de transação. O nome do nível fala por si - cada transação vê alterações não confirmadas em outra transação (o fenômeno da leitura suja ). Vamos ver como essas transações se afetam.

Etapa 1. Começamos 2 transações paralelas.



Etapa 2. Examinamos quais informações temos no início.



Etapa 3. Agora, executamos as operações INSERT, DELETE, UPDATE em T1 e vemos o que outra transação agora vê.



T2 vê dados de outra transação que ainda não foi confirmada.

Etapa 4. E o T2 pode obter alguns dados.



Etapa 5. Quando você reverter as alterações para T1, os dados recebidos por T2 serão incorretos.



Nesse nível, é impossível usar dados com base nas conclusões e decisões críticas importantes para a aplicação, pois essas conclusões podem estar longe da realidade.
Este nível pode ser usado, por exemplo, para cálculos aproximados de alguma coisa. O resultado COUNT (*) ou MAX (*) pode ser usado em alguns relatórios não estritos.
Outro exemplo é o modo de depuração. Quando, durante uma transação, você deseja ver o que acontece com o banco de dados.

Leitura confirmada


Para esse nível, as transações executadas simultaneamente veem apenas alterações confirmadas de outras transações. Portanto, esse nível fornece proteção contra leitura suja .

As etapas 1 e 2 são semelhantes ao exemplo anterior.

Etapa 3. Também realizamos 3 operações simples com a tabela de contas (T1) e fazemos uma seleção completa dessas tabelas nas duas transações.



E veremos que o fenômeno da leitura suja está ausente em T2.

Etapa 4. Corrigimos as alterações no T1 e verificamos o que o T2 agora vê.



Agora o T2 vê tudo o que o T1 fez. Esse é o chamado fenômeno da leitura não repetida quando vemos linhas atualizadas e excluídas (UPDATE, DELETE), e o fenômeno da leitura de fantasmas quando vemos registros adicionados (INSERT).

Leitura repetida


Um nível para evitar o fenômeno da leitura não repetida . I.e. não vemos registros alterados e excluídos em outra transação com outra transação. Mas ainda vemos os registros inseridos de outra transação. Ler fantasmas não vai a lugar algum.

Repita as etapas 1 e 2 novamente.

Etapa 3. No T1, executamos as consultas INSERT, UPDATE e DELETE. Depois, em T2, tentamos atualizar a mesma linha que foi atualizada em T1.



E temos bloqueio: o T2 aguardará até que o T1 confirme as alterações ou retroceda.

Etapa 4. Corrija as alterações feitas pelo T1. E leia novamente os dados da tabela de contas em T2.



Como você pode ver, os fenômenos de leitura não repetida e fantasmas de leitura não são observados. Como, por padrão, a leitura repetível nos permite impedir apenas o fenômeno da leitura não repetida ?

De fato, o MySQL não possui o efeito de ler fantasmas para o nível de leitura repetível . E no PostgreSQL, eles também se livraram dele para este nível. Embora na representação clássica desse nível, devemos observar esse efeito.

Um pequeno exemplo abstrato é o serviço de geração de certificados de presente (códigos) e seu uso. Por exemplo, um invasor gerou um código de certificado para ele mesmo e tenta ativá-lo, tentando enviar várias solicitações consecutivas para ativar um cupom. Nesse caso, iniciaremos várias transações executadas simultaneamente trabalhando com o mesmo cupom. E, em algumas situações, pode ocorrer a ativação dupla ou tripla do cupom (o usuário receberá bônus 2x / 3x). Com a leitura repetível, nesse caso, ocorrerá um bloqueio e a ativação ocorrerá uma vez e, nos 2 níveis anteriores, é possível a ativação múltipla. Um problema semelhante também pode ser resolvido usando a consulta SELECT FOR UPDATE , que também bloqueará o registro atualizado (cupom).

Serializable


O nível em que as transações se comportam como se nada mais existisse, não há influência uma sobre a outra. Na representação clássica, esse nível elimina o efeito de ler fantasmas .

Etapa 1. Inicie a transação.

Etapa 2. T2, lemos a tabela de contas e, em seguida, T1, tentamos atualizar os dados lidos por T2.



Temos bloqueio: não podemos alterar os dados em uma transação lida em outra.

Etapa 3. INSERT e DELETE nos levam ao bloqueio em T1.



Até que T2 termine seu trabalho, não poderemos trabalhar com os dados que ele leu. Obtemos a máxima consistência dos dados, nenhum dado extra será gravado. O preço para isso é uma velocidade de transação lenta devido a bloqueios frequentes; portanto, com uma arquitetura de aplicativo ruim, isso pode ser um truque para você.

Conclusões


Na maioria das aplicações, o nível de isolamento raramente muda e o valor padrão é usado (por exemplo, no MySQL é de leitura repetível , no PostgreSQL é de leitura comprometida ).

Periodicamente, porém, existem problemas em que encontrar um melhor equilíbrio entre alta consistência dos dados ou velocidade da transação pode ajudar a resolver algum problema de aplicativo.

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


All Articles