のテーブル構造を仮定しますMyTable(KEY, datafield1, datafield2...)
。
多くの場合、既存のレコードを更新するか、存在しない場合は新しいレコードを挿入します。
基本的に:
IF (key exists)
run update command
ELSE
run insert command
これを書くための最良のパフォーマンスの方法は何ですか?
のテーブル構造を仮定しますMyTable(KEY, datafield1, datafield2...)
。
多くの場合、既存のレコードを更新するか、存在しない場合は新しいレコードを挿入します。
基本的に:
IF (key exists)
run update command
ELSE
run insert command
これを書くための最良のパフォーマンスの方法は何ですか?
トランザクションを忘れないでください。パフォーマンスは良好ですが、単純な (IF EXISTS..) アプローチは非常に危険です。
複数のスレッドが挿入または更新を実行しようとすると、簡単に主キー違反が発生する可能性があります。
@Beau Crawford と @Esteban が提供するソリューションは、一般的なアイデアを示していますが、エラーが発生しやすいものです。
デッドロックと PK 違反を回避するには、次のようなものを使用できます。
begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
update table set ...
where key = @key
end
else
begin
insert into table (key, ...)
values (@key, ...)
end
commit tran
また
begin tran
update table with (serializable) set ...
where key = @key
if @@rowcount = 0
begin
insert into table (key, ...) values (@key,..)
end
commit tran
非常によく似た以前の質問に対する私の詳細な回答を参照してください
@Beau Crawfordは SQL 2005 以下では良い方法ですが、 rep を付与する場合はSO it の最初の男に行く必要があります。唯一の問題は、挿入の場合、まだ 2 つの IO 操作であることです。
MS Sql2008merge
は、SQL:2003 標準から導入されています。
merge tablename with(HOLDLOCK) as target
using (values ('new value', 'different value'))
as source (field1, field2)
on target.idfield = 7
when matched then
update
set field1 = source.field1,
field2 = source.field2,
...
when not matched then
insert ( idfield, field1, field2, ... )
values ( 7, source.field1, source.field2, ... )
これは本当に 1 つの IO 操作ですが、ひどいコードです :-(
UPSERT を実行します。
UPDATE MyTable SET FieldA=@FieldA WHERE Key=@Key IF @@ROWCOUNT = 0 MyTable (FieldA) 値 (@FieldA) に挿入
多くの人が を使用することを提案しますが、使用しないようMERGE
に注意してください。デフォルトでは、複数のステートメント以上に並行性と競合状態から保護されず、他の危険をもたらします。
この「より単純な」構文が利用可能であっても、私はまだこのアプローチを好みます (簡潔にするためにエラー処理は省略されています)。
BEGIN TRANSACTION;
UPDATE dbo.table WITH (UPDLOCK, SERIALIZABLE)
SET ... WHERE PK = @PK;
IF @@ROWCOUNT = 0
BEGIN
INSERT dbo.table(PK, ...) SELECT @PK, ...;
END
COMMIT TRANSACTION;
多くの人がこの方法を提案します:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
IF EXISTS (SELECT 1 FROM dbo.table WHERE PK = @PK)
BEGIN
UPDATE ...
END
ELSE
BEGIN
INSERT ...
END
COMMIT TRANSACTION;
ただし、これにより、更新する行を見つけるためにテーブルを 2 回読み取る必要がある場合があることを確認できます。最初のサンプルでは、行を見つける必要があるのは 1 回だけです。(どちらの場合も、最初の読み取りで行が見つからない場合、挿入が発生します。)
他の人はこのように提案します:
BEGIN TRY
INSERT ...
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 2627
UPDATE ...
END CATCH
ただし、ほとんどすべての挿入が失敗するまれなシナリオを除いて、最初に防止できたはずの例外を SQL Server にキャッチさせる以外の理由がない場合、これは問題になります。私はここで多くを証明します:
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)
編集:
悲しいかな、私自身の不利益であっても、select なしでこれを行うソリューションは、1 つ少ないステップでタスクを達成するため、より優れているように思われることを認めなければなりません。
一度に複数のレコードを UPSERT する場合は、ANSI SQL:2003 DML ステートメント MERGE を使用できます。
MERGE INTO table_name WITH (HOLDLOCK) USING table_name ON (condition)
WHEN MATCHED THEN UPDATE SET column1 = value1 [, column2 = value2 ...]
WHEN NOT MATCHED THEN INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...])
これについてコメントするのはかなり遅いですが、MERGE を使用したより完全な例を追加したいと思います。
このような Insert+Update ステートメントは通常 "Upsert" ステートメントと呼ばれ、SQL Server で MERGE を使用して実装できます。
非常に良い例がここにあります: http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
上記では、ロックと同時実行のシナリオについても説明しています。
参考までに以下に引用します。
ALTER PROCEDURE dbo.Merge_Foo2
@ID int
AS
SET NOCOUNT, XACT_ABORT ON;
MERGE dbo.Foo2 WITH (HOLDLOCK) AS f
USING (SELECT @ID AS ID) AS new_foo
ON f.ID = new_foo.ID
WHEN MATCHED THEN
UPDATE
SET f.UpdateSpid = @@SPID,
UpdateTime = SYSDATETIME()
WHEN NOT MATCHED THEN
INSERT
(
ID,
InsertSpid,
InsertTime
)
VALUES
(
new_foo.ID,
@@SPID,
SYSDATETIME()
);
RETURN @@ERROR;
それは使用パターンによって異なります。詳細に迷うことなく、使用状況の全体像を見る必要があります。たとえば、レコードが作成された後の使用パターンが 99% の更新である場合、「UPSERT」が最適なソリューションです。
最初の挿入 (ヒット) の後は、すべて単一ステートメントの更新であり、ifs や buts はありません。挿入の「where」条件が必要です。そうしないと、重複が挿入され、ロックを処理したくなくなります。
UPDATE <tableName> SET <field>=@field WHERE key=@key;
IF @@ROWCOUNT = 0
BEGIN
INSERT INTO <tableName> (field)
SELECT @field
WHERE NOT EXISTS (select * from tableName where key = @key);
END
ステートメントを使用できますMERGE
。このステートメントは、存在しない場合はデータを挿入し、存在する場合は更新するために使用されます。
MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`
UPDATE if-no-rows-updated の次に INSERT ルートに進む場合は、最初に INSERT を実行して競合状態を防ぐことを検討してください (間に DELETE がないと仮定します)。
INSERT INTO MyTable (Key, FieldA)
SELECT @Key, @FieldA
WHERE NOT EXISTS
(
SELECT *
FROM MyTable
WHERE Key = @Key
)
IF @@ROWCOUNT = 0
BEGIN
UPDATE MyTable
SET FieldA=@FieldA
WHERE Key=@Key
IF @@ROWCOUNT = 0
... record was deleted, consider looping to re-run the INSERT, or RAISERROR ...
END
競合状態を回避することは別として、ほとんどの場合、レコードが既に存在する場合、これにより INSERT が失敗し、CPU が浪費されます。
SQL2008 以降では、おそらく MERGE を使用することをお勧めします。
最初に更新を試みてから挿入を試みた場合、競合状態は本当に問題になりますか? キーkeyの値を設定したい 2 つのスレッドがあるとします。
スレッド 1: 値 = 1
スレッド 2: 値 = 2
競合状態のシナリオ例
他のスレッドは挿入に失敗します (重複キーのエラーあり) - スレッド 2。
しかし; マルチスレッド環境では、OS スケジューラがスレッドの実行順序を決定します。上記のシナリオでは、この競合状態が発生し、実行順序を決定したのは OS でした。つまり、「スレッド 1」または「スレッド 2」がシステムの観点から「最初」だったというのは誤りです。
スレッド 1 とスレッド 2 の実行時間が非常に近い場合、競合状態の結果は重要ではありません。唯一の要件は、スレッドの 1 つが結果の値を定義する必要があることです。
実装について: update に続いて insert がエラー「duplicate key」になる場合、これは成功として扱われるべきです。
また、もちろん、データベース内の値が最後に書き込んだ値と同じであると想定してはなりません。
MS SQL Server 2008 では、MERGE ステートメントが導入されました。これは、SQL:2003 標準の一部であると私は信じています。多くの人が示しているように、1 つの行のケースを処理することは大したことではありませんが、大規模なデータセットを処理する場合、カーソルが必要であり、すべてのパフォーマンスの問題が発生します。大規模なデータセットを処理する場合、MERGE ステートメントは非常に歓迎されます。
以下のソリューションを試してみましたが、挿入ステートメントの同時要求が発生したときに機能します。
begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
update table set ...
where key = @key
end
else
begin
insert table (key, ...)
values (@key, ...)
end
commit tran
sprocs を直接実行しているこれらの悪意のあるユーザーからの恐怖から誰もが HOLDLOCK-s にジャンプする前に :-)設計によって新しい PK-s の一意性を保証する必要があることを指摘させてください(ID キー、Oracle のシーケンスジェネレーター、一意のインデックス)外部 ID、インデックスでカバーされるクエリ)。それが問題のアルファとオメガです。それがない場合、ユニバースの HOLDLOCK-s があなたを救うことはありません。それがある場合は、最初の選択で UPDLOCK を超えるものは必要ありません (または最初に更新を使用します)。
Sproc は通常、非常に制御された条件下で、信頼できる呼び出し元 (中間層) を想定して実行されます。つまり、単純な upsert パターン (更新 + 挿入またはマージ) で重複した PK が検出された場合、これは中間層またはテーブルの設計にバグがあることを意味し、そのような場合に SQL が障害を叫び、レコードを拒否するのは良いことです。この場合に HOLDLOCK を設定すると、パフォーマンスが低下するだけでなく、例外を処理し、潜在的に欠陥のあるデータを取り込むことになります。
そうは言っても、MERGEまたはUPDATEを使用してからINSERTを使用すると、最初の選択に(UPDLOCK)を追加することを覚えておく必要がないため、サーバー上でより簡単になり、エラーが発生しにくくなります。また、小さなバッチで挿入/更新を行っている場合は、トランザクションが適切かどうかを判断するためにデータを知る必要があります。無関係なレコードの単なるコレクションであり、追加の「エンベロープ」トランザクションは有害です。
このクエリを使用できます。すべての SQL Server エディションで動作します。シンプルで明快です。ただし、2 つのクエリを使用する必要があります。MERGEが使えないなら使える
BEGIN TRAN
UPDATE table
SET Id = @ID, Description = @Description
WHERE Id = @Id
INSERT INTO table(Id, Description)
SELECT @Id, @Description
WHERE NOT EXISTS (SELECT NULL FROM table WHERE Id = @Id)
COMMIT TRAN
注: 否定的な回答について説明してください
SQL Server 2008 では、MERGE ステートメントを使用できます。
if exists ... else ... を実行するには、少なくとも 2 つの要求を実行する必要があります (1 つは確認用、もう 1 つはアクションを実行するため)。次のアプローチでは、レコードが存在する場合は 1 つだけ、挿入が必要な場合は 2 つ必要です。
DECLARE @RowExists bit
SET @RowExists = 0
UPDATE MyTable SET DataField1 = 'xxx', @RowExists = 1 WHERE Key = 123
IF @RowExists = 0
INSERT INTO MyTable (Key, DataField1) VALUES (123, 'xxx')
私は通常、最初に存在することを確認し、次に正しいパスが何であれ、他のポスターのいくつかが言ったことを行います。これを行う際に覚えておくべきことの 1 つは、SQL によってキャッシュされた実行計画が、いずれかのパスに対して最適ではない可能性があるということです。これを行う最善の方法は、2 つの異なるストアド プロシージャを呼び出すことだと思います。
最初の SP: 存在する場合 SecondSP を呼び出す (UpdateProc) そうしないと ThirdSP を呼び出す (InsertProc)
さて、私は自分自身のアドバイスにはあまり従わないので、それは控えめに考えてください。
ADO.NET を使用する場合、DataAdapter がこれを処理します。
自分で処理したい場合は、次の方法があります。
キー列に主キー制約があることを確認してください。
次にあなた:
逆の方法で行うこともできます。つまり、最初に挿入を行い、挿入が失敗した場合は更新を行います。更新は挿入よりも頻繁に行われるため、通常は最初の方法の方が適しています。
選択を行い、結果が得られた場合は更新し、そうでない場合は作成します。