Compartilhamento de tecnologia

Como lidar com a resolução de conflitos para atualizações simultâneas de dados no PostgreSQL?

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

linda linha divisória

PostgreSQL


Em um ambiente de operação simultânea de banco de dados, múltiplas transações que tentam atualizar os mesmos dados ao mesmo tempo podem causar conflitos. O PostgreSQL fornece uma série de mecanismos para lidar com esses conflitos de atualização simultâneos para garantir a consistência e integridade dos dados.

linda linha divisória

1. Cenários de conflito de atualização simultânea

Podem ocorrer conflitos de atualização simultâneos quando duas ou mais transações tentam modificar a mesma linha de dados ao mesmo tempo. Os cenários comuns incluem:

  1. Modifique diferentes colunas da mesma linha ao mesmo tempo
  2. Atualize valores diferentes da mesma coluna ao mesmo tempo

linda linha divisória

2. Mecanismo de controle de simultaneidade no PostgreSQL

O PostgreSQL usa principalmente MVCC (Controle de simultaneidade multiversão) para lidar com transações simultâneas. O MVCC permite que uma transação leia uma versão de dados que atenda aos seus requisitos de nível de isolamento sem bloquear para bloquear as operações de leitura de outras transações. No entanto, ainda podem ocorrer conflitos durante as operações de gravação.

(1) Mecanismo de bloqueio

O PostgreSQL usa vários tipos de bloqueios para controlar o acesso simultâneo aos dados. Os tipos de bloqueio comuns incluem:

  1. Bloqueio Compartilhado: Permite que outras transações adquiram bloqueios compartilhados, mas evita que bloqueios exclusivos sejam adquiridos. Normalmente usado para operações de leitura.
  2. Bloqueio Exclusivo: Impede que outras transações adquiram qualquer tipo de bloqueio, frequentemente utilizado para operações de escrita.

A granularidade do bloqueio pode ser em nível de linha (nível de linha), nível de página (nível de página) e nível de tabela (nível de tabela).

(2) Nível de isolamento de transação

PostgreSQL oferece suporte a quatro níveis de isolamento de transação:

  1. Leia não confirmado: Este é o nível de isolamento mais baixo. Uma transação pode ler modificações de dados não confirmadas de outras transações, o que pode levar a problemas como leituras sujas, leituras não repetíveis e leituras fantasmas.
  2. Leitura confirmada: as transações só podem ler dados enviados, evitando leituras sujas, mas ainda podem ocorrer leituras não repetíveis e leituras fantasmas.
  3. Leitura repetível: Ler os mesmos dados várias vezes em uma transação obterá o mesmo resultado, evitando leituras não repetíveis, mas podem ocorrer leituras fantasmas.
  4. Serializável: O mais alto nível de isolamento, garantindo a execução serial das transações através de rigoroso controle de simultaneidade, evitando leituras sujas, leituras não repetíveis e leituras fantasmas.

linda linha divisória

3. Solução para conflitos de atualização simultânea

(1) Mecanismo de nova tentativa

Uma abordagem simples é fazer com que a transação seja repetida quando ocorrer um conflito. Os exemplos são os seguintes:

DO
$$
DECLARE
    conflict_detected BOOLEAN := FALSE;
BEGIN
    LOOP
        -- 尝试执行更新操作
        UPDATE products SET price = 100 WHERE id = 1;

        -- 检查是否有冲突(例如,通过检查受影响的行数)
        IF NOT FOUND THEN
            conflict_detected := TRUE;
        ELSE
            EXIT;
        END IF;

        -- 若有冲突,等待一段时间并重试
        IF conflict_detected THEN
            PERFORM pg_sleep(1);
        END IF;
    END LOOP;
END;
$$;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

No exemplo acima, se a operação de atualização não afetar nenhuma linha (indicando que pode haver um conflito), um sinalizador será definido, aguardará um período de tempo e tentará novamente.

(2) Use controle de simultaneidade otimista

O controle de simultaneidade otimista pressupõe que conflitos de simultaneidade raramente ocorrem. Neste método, a transação não é bloqueada ao atualizar os dados, mas verifica se os dados foram modificados por outras transações no momento do commit. Se não houver conflitos, a transação será confirmada com êxito; se houver conflitos, a transação será revertida e repetida conforme necessário;

-- 获取数据的初始版本
SELECT price AS original_price FROM products WHERE id = 1;

-- 进行业务处理和修改
UPDATE products SET price = 100 WHERE id = 1 AND price = original_price;
  • 1
  • 2
  • 3
  • 4
  • 5

No exemplo acima, a operação de atualização só será bem-sucedida se os dados não tiverem sido modificados por outras transações.

(3) Use controle de simultaneidade pessimista

O controle de simultaneidade pessimista assume que é provável que ocorram conflitos de simultaneidade e adquire os bloqueios necessários durante a execução da transação para bloquear outras transações potencialmente conflitantes.

BEGIN;

-- 获取排他锁
LOCK TABLE products IN SHARE ROW EXCLUSIVE MODE;

-- 进行数据更新
UPDATE products SET price = 100 WHERE id = 1;

COMMIT;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

(4) Campo de versão do aplicativo

Adicione um campo de versão à tabela para rastrear alterações nos dados.

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    price DECIMAL(10, 2),
    version INT DEFAULT 0
);
  • 1
  • 2
  • 3
  • 4
  • 5

Ao atualizar os dados, incremente também o campo de versão:

UPDATE products SET price = 100, version = version + 1 WHERE id = 1 AND version = <expected_version>;
  • 1

Se o número de linhas afetadas pela atualização for 0, há um conflito porque a versão esperada é inconsistente com a versão real.

(5) Resolução de conflitos com base no carimbo de data/hora

Adicione um campo de carimbo de data/hora a cada linha de dados para registrar a hora da última modificação dos dados.

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    price DECIMAL(10, 2),
    last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
  • 1
  • 2
  • 3
  • 4
  • 5

Ao atualizar, atualize apenas os dados com um carimbo de data/hora anterior ao carimbo de data/hora lido pela transação atual:

UPDATE products SET price = 100 WHERE id = 1 AND last_modified <= <read_timestamp>;
  • 1

linda linha divisória

4. Considerações em Aplicações Práticas

(1) Impacto no desempenho

  1. Diferentes métodos de resolução de conflitos têm impactos diferentes no desempenho do banco de dados. Por exemplo, o uso do bloqueio pode fazer com que outras transações esperem, aumentar o tempo de bloqueio do sistema e, assim, afetar a simultaneidade. O controle de simultaneidade otimista tem melhor desempenho quando os conflitos ocorrem raramente, mas quando os conflitos ocorrem com frequência, pode levar a um grande número de novas tentativas de transação, aumentando o tempo geral de execução.
  2. A aplicação de campos de versão ou métodos baseados em carimbo de data/hora pode exigir espaço de armazenamento adicional para manter informações de versão ou carimbo de data/hora, além de julgamento e processamento adicionais durante a atualização.

(2) Adaptabilidade da lógica de negócios

  1. Certos cenários de negócios podem ser mais adequados para um método específico de resolução de conflitos. Por exemplo, se a empresa tiver requisitos muito elevados de consistência de dados e não puder tolerar quaisquer inconsistências, então o controle de simultaneidade pessimista ou o nível de isolamento de serialização podem ser uma escolha melhor.
  2. O controle de simultaneidade otimista pode ser mais apropriado para cenários onde os conflitos são menos frequentes e os requisitos de tempo de resposta são maiores.

(3) Distribuição de dados e padrões de acesso

  1. Se o acesso aos dados for altamente simultâneo e múltiplas transações muitas vezes acessam as mesmas linhas de dados ao mesmo tempo, os métodos de resolução de conflitos precisam ser escolhidos com mais cuidado para evitar bloqueios e conflitos excessivos.
  2. Para situações em que a distribuição dos dados é relativamente uniforme e a probabilidade de conflito é baixa, podem ser utilizados métodos relativamente simples e eficientes, como o controle de simultaneidade otimista.

linda linha divisória

5. Análise de exemplo

Suponha que temos um sistema de gerenciamento de estoque para uma loja online com um inventory tabela para armazenar quantidades de itens em estoque.

CREATE TABLE inventory (
    product_id INT PRIMARY KEY,
    quantity INT,
    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
  • 1
  • 2
  • 3
  • 4
  • 5

Existem agora duas transações simultâneas:

Transação 1:

BEGIN;
SELECT * FROM inventory WHERE product_id = 1;
-- 假设读取到的数量为 10
UPDATE inventory SET quantity = 5 WHERE product_id = 1 AND last_updated <= <read_timestamp>;
COMMIT;
  • 1
  • 2
  • 3
  • 4
  • 5

Transação 2:

BEGIN;
SELECT * FROM inventory WHERE product_id = 1;
-- 假设也读取到的数量为 10
UPDATE inventory SET quantity = 8 WHERE product_id = 1 AND last_updated <= <read_timestamp>;
COMMIT;
  • 1
  • 2
  • 3
  • 4
  • 5

Se essas duas transações forem executadas quase ao mesmo tempo, poderá ocorrer um conflito.

Se usarmos resolução de conflitos baseada em carimbo de data/hora:

  1. A transação 1 obteve o timestamp atual ao ler os dados (T1)。
  2. A transação 2 obteve um timestamp um pouco posterior ao ler os dados (T2)。

Quando a transação 1 tenta atualizar, se nenhuma outra transação modificou os dados desde que os leu (ou seja, last_updated <= T1), a atualização foi bem-sucedida.

Quando a transação 2 tenta atualizar, se descobrir que os dados last_updated mais do que oT2(Indicando que foi modificado após a leitura da transação 2), a atualização falha e a transação 2 pode optar por reverter e tentar novamente ou realizar outro processamento de acordo com a lógica de negócios.


linda linha divisória

🎉相关推荐

PostgreSQL