私はそれを達成するために常に次のようなものを使用していました。
INSERT INTO TheTable
SELECT
@primaryKey,
@value1,
@value2
WHERE
NOT EXISTS
(SELECT
NULL
FROM
TheTable
WHERE
PrimaryKey = @primaryKey)
...しかし、負荷がかかると、主キー違反が発生しました。これは、このテーブルにまったく挿入する唯一のステートメントです。これは、上記のステートメントがアトミックではないことを意味しますか?
問題は、これを自由に再現することはほとんど不可能であることです。
おそらく次のように変更できます。
INSERT INTO TheTable
WITH
(HOLDLOCK,
UPDLOCK,
ROWLOCK)
SELECT
@primaryKey,
@value1,
@value2
WHERE
NOT EXISTS
(SELECT
NULL
FROM
TheTable
WITH
(HOLDLOCK,
UPDLOCK,
ROWLOCK)
WHERE
PrimaryKey = @primaryKey)
ただし、間違ったロックを使用している、またはロックなどを使用しすぎている可能性があります。
Stackoverflow.comで「IF(SELECT COUNT(*)... INSERT」など)が提案されている他の質問を見てきましたが、単一のSQLステートメントがアトミックであるという(おそらく誤った)仮定の下に常にいました。
誰にもアイデアはありますか?
"JFDI" パターンはどうですか?
_BEGIN TRY
INSERT etc
END TRY
BEGIN CATCH
IF ERROR_NUMBER() <> 2627
RAISERROR etc
END CATCH
_
真剣に、これは、特に大容量の場合、ロックなしで最も速く、最も同時です。 UPDLOCKがエスカレートされ、テーブル全体がロックされた場合はどうなりますか?
レッスン4を読む :
レッスン4:インデックスを調整する前にupsertプロシージャを開発するとき、最初に
If Exists(Select…)
行がすべてのアイテムに対して起動することを信頼しました重複を禁止します。 N。同じアイテムが同じミリ秒でアップサートにヒットし、両方のトランザクションが存在しないことを確認して挿入を実行するため、短時間で何千もの重複がありました。ソリューションをテストした後、一意のインデックスを使用してエラーをキャッチし、トランザクションが行を参照して挿入ではなく更新を実行できるように再試行しました。
もともとは存在していなかったHOLDLOCKを追加しました。このヒントのないバージョンは無視してください。
私の知る限り、これで十分です:
INSERT INTO TheTable
SELECT
@primaryKey,
@value1,
@value2
WHERE
NOT EXISTS
(SELECT 0
FROM TheTable WITH (UPDLOCK, HOLDLOCK)
WHERE PrimaryKey = @primaryKey)
また、行が存在する場合は実際に更新し、存在しない場合は挿入する場合は、 この質問 が役立つ場合があります。
MERGEを使用できます:
MERGE INTO Target
USING (VALUES (@primaryKey, @value1, @value2)) Source (key, value1, value2)
ON Target.key = Source.key
WHEN MATCHED THEN
UPDATE SET value1 = Source.value1, value2 = Source.value2
WHEN NOT MATCHED BY TARGET THEN
INSERT (Name, ReasonType) VALUES (@primaryKey, @value1, @value2)
これが「公式」な方法かどうかはわかりませんが、INSERT
を試してみて、失敗した場合はUPDATE
にフォールバックできます。
まず、コミュニティへの貢献について、@ gbnに大声で叫んでください。彼のアドバイスに従っている頻度を説明することさえできません。
とにかく、十分なファンボイイング。
彼の答えにわずかに追加するために、おそらくそれを「強化」します。私のような人たちにとって、<> 2627
シナリオ(空のCATCH
はオプションではありません)。 technet からこの小さなナゲットを見つけました。
BEGIN TRY
INSERT etc
END TRY
BEGIN CATCH
IF ERROR_NUMBER() <> 2627
BEGIN
DECLARE @ErrorMessage NVARCHAR(4000);
DECLARE @ErrorSeverity INT;
DECLARE @ErrorState INT;
SELECT @ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE();
RAISERROR (
@ErrorMessage,
@ErrorSeverity,
@ErrorState
);
END
END CATCH