技術共有

PostgreSQL でのデータの同時更新の競合解決を処理するにはどうすればよいですか?

2024-07-12

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

美しい分割線

PostgreSQL


データベースの同時運用環境では、複数のトランザクションが同時に同じデータを更新しようとすると、競合が発生する可能性があります。 PostgreSQL は、データの一貫性と整合性を確保するために、これらの同時更新の競合を処理する一連のメカニズムを提供します。

美しい分割線

1. 同時更新の競合シナリオ

同時更新の競合は、2 つ以上のトランザクションがデータの同じ行を同時に変更しようとすると発生することがあります。一般的なシナリオには次のようなものがあります。

  1. 同じ行の異なる列を同時に変更する
  2. 同じ列の異なる値を同時に更新する

美しい分割線

2. PostgreSQL の同時実行制御メカニズム

PostgreSQL は主に MVCC (Multiversion Concurrency Control) を使用して同時トランザクションを処理します。 MVCC を使用すると、トランザクションは、他のトランザクションの読み取り操作をブロックするロックを行わずに、分離レベルの要件を満たすデータ バージョンを読み取ることができます。ただし、書き込み操作中に競合が発生する可能性があります。

(1) 遮断機構

PostgreSQL は、データへの同時アクセスを制御するためにいくつかのタイプのロックを使用します。一般的なロックのタイプは次のとおりです。

  1. 共有ロック: 他のトランザクションが共有ロックを取得できるようにしますが、排他ロックは取得できません。通常、読み取り操作に使用されます。
  2. 排他的ロック: 他のトランザクションが、書き込み操作によく使用されるあらゆるタイプのロックを取得できないようにします。

ロックの粒度は、行レベル (Row-Level)、ページ レベル (Page-Level)、およびテーブル レベル (Table-Level) です。

(2) トランザクション分離レベル

PostgreSQL は 4 つのトランザクション分離レベルをサポートしています。

  1. コミットされていない読み取り: これは最も低い分離レベルであり、トランザクションは他のトランザクションのコミットされていないデータ変更を読み取ることができ、ダーティ リード、反復不可能な読み取り、ファントム リードなどの問題が発生する可能性があります。
  2. コミットされた読み取り: トランザクションは送信されたデータの読み取りのみを行うことができ、ダーティ リードは回避されますが、非反復読み取りとファントム読み取りが発生する可能性があります。
  3. 反復可能な読み取り注:トランザクション内で同じデータを複数回読み取ると同じ結果が得られ、反復不可能な読み取りは回避されますが、ファントム読み取りが発生する可能性があります。
  4. シリアル化可能: 最高の分離レベル。厳密な同時実行制御を通じてトランザクションのシリアル実行を保証し、ダーティ リード、反復不可能なリード、ファントム リードを回避します。

美しい分割線

3. 同時更新の競合の解決策

(1) リトライ機構

簡単なアプローチは、競合が発生したときにトランザクションを再試行することです。例は次のとおりです。

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

上記の例では、更新操作がどの行にも影響しない場合 (競合がある可能性があることを示します)、フラグが設定され、一定期間待機してから再試行されます。

(2) オプティミスティック同時実行制御を使用する

オプティミスティック同時実行制御は、同時実行の競合がほとんど発生しないことを前提としています。このメソッドでは、トランザクションはデータを更新するときにロックしませんが、コミット時にデータが他のトランザクションによって変更されているかどうかを確認します。競合がない場合、トランザクションは正常にコミットされます。競合がある場合、トランザクションはロールバックされ、必要に応じて再試行されます。

-- 获取数据的初始版本
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

上記の例では、データが他のトランザクションによって変更されていない場合にのみ更新操作が成功します。

(3) ペシミスティック同時実行制御を使用する

ペシミスティック同時実行制御では、同時実行の競合が発生する可能性が高いと想定し、トランザクションの実行中に必要なロックを取得して、競合する可能性のある他のトランザクションをブロックします。

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) アプリケーションバージョン欄

データの変更を追跡するには、テーブルにバージョン フィールドを追加します。

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

データを更新するときは、バージョン フィールドもインクリメントします。

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

更新の影響を受ける行数が 0 の場合、予期されるバージョンが実際のバージョンと一致しないため、競合が発生します。

(5) タイムスタンプに基づく競合解決

データの各行にタイムスタンプ フィールドを追加して、データの最終変更時刻を記録します。

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

更新するときは、現在のトランザクションによって読み取られたタイムスタンプよりも古いタイムスタンプを持つデータのみを更新します。

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

美しい分割線

4. 実用化における考慮事項

(1) 業績への影響

  1. 競合解決方法が異なれば、データベースのパフォーマンスに与える影響も異なります。たとえば、ブロッキングを使用すると、他のトランザクションが待機し、システムのブロッキング時間が増加し、同時実行性に影響を与える可能性があります。オプティミスティック同時実行制御は、競合がめったに発生しない場合にはパフォーマンスが向上しますが、競合が頻繁に発生すると、トランザクションの再試行が大量に発生し、全体の実行時間が長くなる可能性があります。
  2. バージョン フィールドまたはタイムスタンプ ベースのメソッドを適用すると、バージョンまたはタイムスタンプ情報を維持し、更新時に追加の判断と処理を実行するために追加の記憶領域が必要になる場合があります。

(2) ビジネスロジックの適応性

  1. 特定のビジネス シナリオでは、特定の競合解決方法の方が適している場合があります。たとえば、ビジネスでデータの整合性に対する要件が非常に高く、不整合が許容できない場合は、悲観的な同時実行制御またはシリアル化分離レベルの方が適切な選択となる可能性があります。
  2. オプティミスティック同時実行制御は、競合の頻度が低く、応答時間の要件が高いシナリオに適している可能性があります。

(3) データの分散とアクセスのパターン

  1. データへの同時アクセスが高く、複数のトランザクションが同じデータ行に同時にアクセスすることが多い場合は、過剰なブロックや競合を避けるために、競合解決方法をより慎重に選択する必要があります。
  2. データの分布が比較的均一で競合の可能性が低い状況では、オプティミスティック同時実行制御などの比較的単純で効率的な方法を使用できます。

美しい分割線

5. 分析例

オンライン ストアの在庫管理システムがあるとします。 inventory 商品の在庫数量を格納するテーブル。

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

現在、2 つの同時トランザクションがあります。

トランザクション 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

トランザクション 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

これら 2 つのトランザクションがほぼ同時に実行されると、競合が発生する可能性があります。

タイムスタンプベースの競合解決を使用する場合:

  1. トランザクション 1 は、データの読み取り時に現在のタイムスタンプを取得しました (T1)。
  2. トランザクション 2 は、データの読み取り時にわずかに遅いタイムスタンプを取得しました (T2)。

トランザクション 1 が更新しようとしたとき、データを読み取ってから他のトランザクションがそのデータを変更していない場合 (つまり、 last_updated <= T1)、更新は成功しました。

トランザクション 2 が更新しようとしたときに、データが last_updated 以上T2(トランザクション 2 が読み取った後に変更されたことを示します)、更新は失敗し、トランザクション 2 はロールバックして再試行するか、ビジネス ロジックに従って他の処理を実行するかを選択できます。


美しい分割線

🎉相关推荐

PostgreSQL