私たちが解決しようとしている高レベルの問題は、ユーザーが開始した ETL プロセスからレコードが重複するのを防ぐことです。これは、インポートする行ごとにプロシージャを呼び出します (これは簡略化のためであり、設計の変更は私の計画の一部ではありません)。質問)。したがって、基本的には次のようになります (テーブルには約 20 列あることに注意してください)。
手順 ImportRow ( @p1、@p2、@p3 など)
存在しない場合 ( TargetTable から
1を選択 WHERE col1 = @p1 AND col2 = @p2 AND col3 = @p3 など)INSERT INTO TargetTable(col1, col2, col3...)
VALUES (@p1, @p2, @p3)
したがって、2 人のユーザーが誤って同じインポーターを同時に実行すると、レコードが重複してしまうという競合状態が発生することは明らかです。
2番目のトランザクションが挿入を実行するのを防ぐために、喜んで使用SERIALIZABLE
します...IF NOT EXISTS
手順 ImportRow ( @p1、@p2、@p3 など)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
存在しない場合 (TargetTable から
1を選択
(UPDLOCK)
WHERE col1 = @p1 AND col2 = @p2 AND col3 = @p3 など)INSERT INTO TargetTable(col1, col2, col3...)
VALUES (@p1, @p2, @p3)
...しかし、ご存知のように、インデックスをカバーしないと、テーブル全体がロックされます...
...そして、20 列あるため、カバリング インデックスを追加できません。
ただし、最初の 2 つのパラメーター/列は基本的に「インポーター ID」と「現在の日付」であるため、実際にはそれのみをシリアル化しても問題ありません。つまり、同じインポーターが複数回実行されるのを防ぎます。つまり、ロックの粒度が「テーブル全体」と「行ごと」の間になるようにします。このようなもの:
手順 ImportRow ( @importer、@asOfDate、@p3、@p4、@p5 など)
-- インポーターをロックダウン
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
SELECT 1
FROM TargetTable (UPDLOCK)
WHERE importer = @importer AND asOfDate = @asOfDate存在しない場合 (TargetTable から
1を選択
(UPDLOCK)
WHERE importer = @importer AND asOfDate = @asOfDate AND col3 = @p3 AND col4 = @p4 AND col5 = @p5 など)INSERT INTO TargetTable(インポーター、asOfDate、col3、col4、col5...)
値 (@importer、@asOfDate、@p3、@p4、@p5)
importer と asOfDate にはカバー インデックスがあるので、それだけを serializable でロックダウンし、残りのテーブルは自由で明確にする必要があります。SELECT
問題は、最初のクエリから明らかに遅くて醜い結果セットが生成されることです。
この「部分的なシリアライズ可能」を行う別の方法はありますか? 一時テーブルを燃やそうと思っていたのですが、もったいないです。
SELECT 1
INTO #Ignored FROM TargetTable (UPDLOCK)
WHERE importer = @importer AND asOfDate = @asOfDate
私が考えることができる他の唯一のことは、別のテーブルに importer/asOfDate を配置し、そのペアに id ('importerDateId') を与えてから、TargetTable でその importerDateId を使用することです。つまり、もう少し正規化しますが、スキーマの変更を避けたい。