CodeGym /Cursos /SQL SELF /Nível de isolamento SERIALIZABLE: isolament...

Nível de isolamento SERIALIZABLE: isolamento total e prevenção de Phantom Read

SQL SELF
Nível 40 , Lição 2
Disponível

SERIALIZABLE é o nível máximo de isolamento de transações no PostgreSQL. Esse nível garante que os resultados das transações paralelas vão ser os mesmos como se elas fossem executadas EM SEQUÊNCIA, uma depois da outra. Assim, nenhuma anomalia de execução paralela (tipo Dirty Read, Non-Repeatable Read, Phantom Read) pode acontecer.

Falando de boa, SERIALIZABLE garante ordem total e consistência entre as transações paralelas. É como se o PostgreSQL dissesse: "Todo mundo na fila, galera!"

Pra que serve o nível SERIALIZABLE? Às vezes tu quer ter 100% de certeza que teus dados vão ficar totalmente consistentes, mesmo com mudanças paralelas. Imagina uma cena no supermercado, onde vários caixas atendem clientes ao mesmo tempo. Se ninguém cuidar da ordem, pode acabar saindo mais produtos do que foi comprado. Com SERIALIZABLE isso simplesmente não rola.

Exemplo de configuração do nível SERIALIZABLE

Pra definir o nível de isolamento SERIALIZABLE, usa o comando:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

Por exemplo, vamos criar uma transação usando esse nível:

BEGIN; -- Começa a transação
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- Define o nível de isolamento
SELECT * FROM products WHERE category = 'Electronics'; -- Busca a lista de produtos
UPDATE products SET stock = stock - 1 WHERE product_id = 123; -- Atualiza o estoque
COMMIT; -- Confirma as mudanças

Caso: reserva de ingressos no cinema

Bora ver um exemplo real onde o nível SERIALIZABLE é indispensável. Imagina que tu tá desenvolvendo um sistema de reserva online de ingressos de cinema. Os usuários escolhem os assentos, e tu quer garantir que o mesmo assento não vai ser vendido pra dois clientes ao mesmo tempo.

Primeiro, vamos criar uma tabela pros assentos:

CREATE TABLE seats (
    seat_id SERIAL PRIMARY KEY,
    is_booked BOOLEAN DEFAULT FALSE
);

Agora vamos adicionar alguns assentos:

INSERT INTO seats (is_booked) VALUES (FALSE), (FALSE), (FALSE);

Segue um exemplo de transação com SERIALIZABLE.

Olha como dá pra fazer uma reserva segura de assento:

BEGIN; -- Início da transação
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- Nível de isolamento SERIALIZABLE

-- Verifica se o assento tá livre
SELECT is_booked FROM seats WHERE seat_id = 1;

-- Reserva o assento
UPDATE seats SET is_booked = TRUE WHERE seat_id = 1;

COMMIT; -- Confirma a reserva

Se uma segunda transação paralela tentar reservar o mesmo assento, o PostgreSQL não vai deixar rolar confusão nenhuma e vai lançar um erro de conflito de serialização.

Prevenção de Phantom Read

Agora bora entender as "leituras fantasmas" que a gente quer tanto evitar. Phantom Read acontece quando uma transação vê mudanças nos dados, adicionadas por outra transação enquanto ela ainda tá rolando. Por exemplo, tua transação espera um certo número de linhas, mas do nada outra transação adiciona ou remove linhas, mudando o resultado.

Olha esse exemplo:

Dados antes das transações

id balance user
1 1000 Alice
2 500 Bob

Transação 1

BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- Conta usuários com saldo maior que 400
SELECT COUNT(*) FROM accounts WHERE balance > 400;

-- Espera resultado: 2 (Alice e Bob)

Transação 2

Em outra sessão, rola uma transação paralela:

BEGIN;
INSERT INTO accounts (id, balance, user) VALUES (3, 700, 'Charlie');
COMMIT;

Voltando pra Transação 1

-- Repete a consulta
SELECT COUNT(*) FROM accounts WHERE balance > 400;

Agora, se tu não usar SERIALIZABLE, o resultado vai ser 3 em vez de 2, porque o Charlie foi adicionado enquanto a Transação 1 tava rolando. Isso é o tal do Phantom Read.

Mas com SERIALIZABLE o PostgreSQL garante que a Transação 1 não vai ver o Charlie, porque a "visão do mundo" dela fica congelada no início da transação.

Características e limitações do nível SERIALIZABLE

Já vimos como SERIALIZABLE ajuda a alcançar isolamento perfeito. Mas, fala sério, o que nesse mundo é perfeito sem uns defeitinhos? Bora ser sinceros.

Queda de performance
SERIALIZABLE exige bem mais recursos do que os níveis READ COMMITTED ou REPEATABLE READ. Por quê? O PostgreSQL precisa simular a execução sequencial das operações, monitorando todos os possíveis conflitos entre as transações.

Erros de serialização
Se o PostgreSQL perceber que não dá pra executar as transações numa "sequência perfeita", ele gera um erro de serialização (serialization_failure) e faz rollback na transação.

Exemplo de erro:

ERROR: could not serialize access due to concurrent update

Pra lidar com isso, dá pra tentar rodar a transação de novo depois do erro:

DO $$
DECLARE
    done BOOLEAN := FALSE;
BEGIN
    WHILE NOT done LOOP
        BEGIN
            -- Começa a transação
            BEGIN;
            SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

            -- Faz as operações
            UPDATE accounts SET balance = balance - 100 WHERE id = 1;

            -- Confirma as mudanças
            COMMIT;
            done := TRUE; -- Sai do loop se der tudo certo
        EXCEPTION WHEN serialization_failure THEN
            ROLLBACK; -- Rollback se der erro
        END;
    END LOOP;
END;
$$;

Esse é o jeito clássico em sistemas que usam SERIALIZABLE.

Importante!

Esse código tá escrito usando PL-SQL. A gente vai voltar nele depois. Só queria te mostrar um código bonito e funcional. E também mostrar pra que serve o PL-SQL :)

Quando usar SERIALIZABLE?

Esse nível de isolamento vale a pena quando o preço do erro é muito alto:

  • Transações financeiras, tipo processamento de pagamentos ou distribuição de bônus.
  • Sistemas de controle de estoque, pra evitar pedidos duplicados.
  • Reservas online, onde é importante evitar conflito na reserva de recursos.

Se tu tá desenvolvendo um sistema onde os dados precisam ser 100% consistentes e performance não é prioridade, SERIALIZABLE vai ser teu melhor parceiro.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION