web-dev-qa-db-ja.com

`count(*)`は、どのトランザクションレベルのトランザクションでも同じ結果を返すことが保証されていますか?

テーブルを作成した場合。

CREATE TABLE foo AS
SELECT CASE WHEN random() > 0.5 THEN x END AS x
FROM generate_series(1,10) AS x;

そして、私はトランザクションで以下を実行します

BEGIN;
  SELECT count(*)
  FROM foo
  WHERE x IS NOT NULL;

  --time

  SELECT count(*)
  FROM foo
  WHERE x IS NOT NULL;
END;

どのトランザクションレベルの下で、私の結果がトランザクション内で同じであることが保証されますか?

3
Evan Carroll

私の10セント、そして PostgreSQLドキュメント に基づく:

仮定:独自のトランザクションは、テーブルfooから関連する値を変更しません

両方のクエリで同じcount(*)を使用するということは、次のことを意味します。

  1. 別のトランザクションが最初の選択と2番目の選択の間のfooテーブルにさらに行を書き込んだ可能性があるため、_dirty reads_は使用できません。あなたはそれらを見るべきではありません。

  2. 実際には列を読み取っていないので、_nonrepeatable reads_は問題ではありません。

  3. _phantom reads_を含めることはできません。つまり、別のトランザクションがx列がNULLである行をnull以外の値に変更した場合、トランザクションはそれらの変更に影響を与えることに気づく必要はありませんWHERE条件。

  4. _serialization anomalies_の判断方法がよくわかりません。私の学んだ推測では、これは必要ありません。しかし、これは本当に議論の余地があります

これらの条件下で、 トランザクション分離レベル の表は、_Repeatable read_が以下の基準に準拠していることを明確にします。

  1. ダーティリードがない
  2. 繰り返し不可の読み取りがない
  3. ファントム読み取りがない(PostgreSQLでは、標準で要求されていない)

...そのため、両方の選択ステートメントで同じcount(*)が得られます。

3
joanolo

「トランザクション内」で同じであることが保証されています

これには2つの意味があり、それが問題です。トランザクションの外側で何が起こっているのか、「スナップショット」で内側で何が起こっているのかがわかります。 count(*)の結果には、次の2つのことが起こります。

  • カウントは変更できます
    トランザクションレベル_READ COMMITTED_(および_READ UNCOMMITTED_)。
  • カウントは変更できません
    トランザクションレベル_REPEATABLE READ_およびSERIALIZABLE

変更することと変更しないことは、あなたが知る必要があるすべてではありません。 _REPEATABLE READ_およびSERIALIZABLEsnapshotsで機能します。つまり、counts(*)は変更されませんが、データベースですでに変更されている可能性があるものに関しては何も意味しません。

いくつかを簡略化して確認し、上記の初期化コードREINITを呼び出します。ここでは、これら2つのステートメントのみを扱います。

  1. SELECT count(*) FROM foo WHERE x IS NULL;
  2. _UPDATE foo SET x = 1 WHERE x IS NULL;_

ここで、2つのセッションを実行するとします。

_REINIT
1#     BEGIN; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
1#     SELECT count(*) FROM foo WHERE x IS NULL;
   2#  UPDATE foo SET x = 1 WHERE x IS NULL;
1#     SELECT count(*) FROM foo WHERE x IS NULL;
_

次に、count(*)は_1#_のトランザクションで何を表示しますか?そして、両方のトランザクションがコミットした後、_#1_、次に_#2_?スポイラー警告:

トランザクションでは、同じ番号が表示されます。 UPDATEはすでにコミットされているため、トランザクション以外では0が表示されます。

これで、トランザクションレベルが低くなると、_REPEATABLE READ_のように、_READ COMMITTED_のように、2番目のSELECT by _#1_がコミットされた行を表示します。そして、より高いトランザクションレベルで。最初のSELECT count(*)は、_x IS NULL_である行に対してのみ述語ロックを取得します。では、レベルをSERIALIZABLEに上げて同じシーケンスを実行するとどうなるでしょうか(述語ロック付き)。

_REINIT
1#      BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
1#      SELECT count(*) FROM foo WHERE x IS NULL;
   2#   UPDATE foo SET x = 1 WHERE x IS NULL;
1#      SELECT count(*) FROM foo WHERE x IS NULL;
_

スポイラー警告:

何もない。同じこと。

どうして? 同時実行モデルはUPDATEsを考慮しませんnless何かがそれらの行を変更しようとし、両方がコミットすることを意図しています。両方ともSELECTingであり、スナップショットを変更していない。ひいては、

_if (SELECT count(*) 
      FROM foo
     WHERE x IS NULL
   ) < arg THEN
    RETURN 0 ;
end if ;
_
  • 次に、デフォルトのトランザクションレベル_READ COMMITTED_で、残りのトランザクションとは異なる数値が表示される場合があります。2番目のSELECT間で同時トランザクションがコミットされた可能性があります。そのため、_REPEATABLE READ_またはSERIALIZABLEを使用しないと役に立たなくなります。
  • ただし、他のトランザクションモードでは、_REPEATABLE READ_でロックの問題が発生するか、トランザクションの外部から他に何かがテーブルにアクセスすると、SERIALIZABLEでのコミット時にエラーが発生する可能性があります。繰り返しますが、限られたポイントがあります。 count(*)クエリが行を返した場合、たとえば、他のコードはそれらの行を更新して、それらが既に変更されているか、作業中のスナップショットのときにもう存在しないことを見つけようとする場合があります。コミットします。

したがって、条件付きでトランザクションを実行しないでください。何かをして、それを処理します。


†SQL標準では、レベル_READ UNCOMMITTED_が規定されています。 PostgreSQLでは そのレベルのエイリアス それはより厳しい分離レベル_READ COMMITTED_です。

0
Evan Carroll