この奇妙なエラーは、8週間エラーが発生しなかった後、過去数日間に3回発生し、困惑しています。
これはエラーメッセージです。
Executing the query "EXEC dbo.MergeTransactions" failed with the following error: "Cannot insert duplicate key row in object 'sales.Transactions' with unique index 'NCI_Transactions_ClientID_TransactionDate'. The duplicate key value is (1001, 2018-12-14 19:16:29.00, 304050920).".
私たちが持っているインデックスはユニークではありません。気付いた場合は、エラーメッセージの重複するキー値がインデックスと一致していません。奇妙なことに、プロシージャを再実行すると、成功します。
これは私が見つけた最新のリンクで、問題がありますが、解決策がありません。
私のシナリオに関するいくつかのこと:
各テーブルには45のフィールドがありますが、主にインデックスで使用されるフィールドをリストしました。 updateステートメントのTransactionID(クラスター化キー)を(不必要に)更新しています。先週まで何ヶ月も問題がなかったのは奇妙です。そして、それはSSISを介して散発的にのみ発生しています。
テーブル
USE [DB]
GO
/****** Object: Table [sales].[Transactions] Script Date: 5/29/2019 1:37:49 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[sales].[Transactions]') AND type in (N'U'))
BEGIN
CREATE TABLE [sales].[Transactions]
(
[TransactionID] [bigint] NOT NULL,
[ClientID] [int] NOT NULL,
[TransactionDate] [datetime2](2) NOT NULL,
/* snip*/
[BusinessUserID] [varchar](150) NOT NULL,
[BusinessTransactionID] [varchar](150) NOT NULL,
[InsertDate] [datetime2](2) NOT NULL,
[UpdateDate] [datetime2](2) NOT NULL,
CONSTRAINT [PK_Transactions_TransactionID] PRIMARY KEY CLUSTERED
(
[TransactionID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, DATA_COMPRESSION=PAGE) ON [DB_Data]
) ON [DB_Data]
END
GO
USE [DB]
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[sales].[Transactions]') AND name = N'NCI_Transactions_ClientID_TransactionDate')
begin
CREATE NONCLUSTERED INDEX [NCI_Transactions_ClientID_TransactionDate] ON [sales].[Transactions]
(
[ClientID] ASC,
[TransactionDate] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, DATA_COMPRESSION = PAGE) ON [DB_Data]
END
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[sales].[DF_Transactions_Units]') AND type = 'D')
BEGIN
ALTER TABLE [sales].[Transactions] ADD CONSTRAINT [DF_Transactions_Units] DEFAULT ((0)) FOR [Units]
END
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[sales].[DF_Transactions_ISOCurrencyCode]') AND type = 'D')
BEGIN
ALTER TABLE [sales].[Transactions] ADD CONSTRAINT [DF_Transactions_ISOCurrencyCode] DEFAULT ('USD') FOR [ISOCurrencyCode]
END
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[sales].[DF_Transactions_InsertDate]') AND type = 'D')
BEGIN
ALTER TABLE [sales].[Transactions] ADD CONSTRAINT [DF_Transactions_InsertDate] DEFAULT (sysdatetime()) FOR [InsertDate]
END
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[sales].[DF_Transactions_UpdateDate]') AND type = 'D')
BEGIN
ALTER TABLE [sales].[Transactions] ADD CONSTRAINT [DF_Transactions_UpdateDate] DEFAULT (sysdatetime()) FOR [UpdateDate]
END
GO
一時テーブル
same columns as the mgdata. including the relevant fields. Also has a non-unique clustered index
(
[BusinessTransactionID] [varchar](150) NULL,
[BusinessUserID] [varchar](150) NULL,
[PostalCode] [varchar](25) NULL,
[TransactionDate] [datetime2](2) NULL,
[Units] [int] NOT NULL,
[StartDate] [datetime2](2) NULL,
[EndDate] [datetime2](2) NULL,
[TransactionID] [bigint] NULL,
[ClientID] [int] NULL,
)
CREATE CLUSTERED INDEX ##workingTransactionsMG_idx ON #workingTransactions (TransactionID)
It is populated in batches (500k rows at a time), something like this
IF OBJECT_ID(N'tempdb.dbo.#workingTransactions') IS NOT NULL DROP TABLE #workingTransactions;
select fields
into #workingTransactions
from import.Transactions
where importrowid between two number ranges -- pseudocode
主キー
CONSTRAINT [PK_Transactions_TransactionID] PRIMARY KEY CLUSTERED
(
[TransactionID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, DATA_COMPRESSION=PAGE) ON [Data]
) ON [Data]
非クラスター化インデックス
CREATE NONCLUSTERED INDEX [NCI_Transactions_ClientID_TransactionDate] ON [sales].[Transactions]
(
[ClientID] ASC,
[TransactionDate] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, DATA_COMPRESSION = PAGE)
サンプル更新ステートメント
-- updates every field
update t
set
t.transactionid = s.transactionid,
t.[CityCode]=s.[CityCode],
t.TransactionDate=s.[TransactionDate],
t.[ClientID]=s.[ClientID],
t.[PackageMonths] = s.[PackageMonths],
t.UpdateDate = @UpdateDate
FROM #workingTransactions s
JOIN [DB].[sales].[Transactions] t
ON s.[TransactionID] = t.[TransactionID]
WHERE CAST(HASHBYTES('SHA2_256 ',CONCAT( S.[BusinessTransactionID],'|',S.[BusinessUserID],'|', etc)
<> CAST(HASHBYTES('SHA2_256 ',CONCAT( T.[BusinessTransactionID],'|',T.[BusinessUserID],'|', etc)
私の質問は、内部で何が起こっているのですか?そして、解決策は何ですか?参考までに、上のリンクはこれについて言及しています:
この時点で、私はいくつかの理論を持っています:
- メモリプレッシャーまたは大規模な並列更新計画に関連するバグですが、別のタイプのエラーが予想されます。これまでのところ、これらの孤立した散発的なエラーの低リソースのタイムフレームを関連付けることはできません。
- UPDATEステートメントまたはデータのバグが主キーの実際の重複違反を引き起こしていますが、SQLサーバーの不明瞭なバグが原因で、誤ったインデックス名を引用するエラーメッセージが表示されています。
- コミットされていない読み取りの分離から生じるダーティリードにより、挿入が2回行われる大規模な並列更新しかし、ETL開発者は、デフォルトの読み取りコミットが使用されていると主張しており、実行時にプロセスが実際に使用されている分離レベルを正確に判断することは困難です。
実行計画を回避策として調整した場合、おそらくMAXDOP(1)のヒントを使用するか、セッショントレースフラグを使用してスプール操作を無効にすると、エラーがなくなるだけですが、これがパフォーマンスにどのように影響するかは不明です
バージョン
Microsoft SQL Server 2017(RTM-CU13)(KB4466404)-14.0.3048.4(X64)2018年11月30日12:57:58 Copyright(C)2017 Microsoft Corporation Enterprise Edition(64-bit)on Windows Server 2016 Standard 10.0(Build 14393 :)
私の質問は、内部で何が起こっているのですか?そして、解決策は何ですか?
バグです。問題は、たまにしか発生せず、再現が難しいことです。それでも、Microsoftのサポートに連絡するのが最善の方法です。更新処理は非常に複雑なので、非常に詳細な調査が必要になります。
関連する複雑さの例については、私の投稿 フィルターされたインデックスのMERGEバグ と インデックス付きビューの誤った結果 をご覧ください。それらのどちらもあなたの問題に直接関係していませんが、風味を与えます。
もちろん、それはすべてかなり一般的です。おそらくもっと便利かもしれませんが、現在のUPDATE
ステートメントを書き直してみる必要があります。 documentation が言うように:
FROM句を指定して更新操作の基準を提供する場合は注意してください。ステートメントに、更新される列オカレンスごとに1つの値しか使用できないように指定されていないFROM句が含まれている場合、つまりUPDATEステートメントが確定的でない場合、UPDATEステートメントの結果は未定義です。
あなたのUPDATE
は確定的ではないので、結果はundefined。ターゲット行ごとに最大で1つのソース行が識別されるように変更する必要があります。その変更がない場合、更新の結果はany個々のソース行を反映しない可能性があります。
質問で与えられたものに大まかにモデル化されたテーブルを使用して、例を示しましょう:
CREATE TABLE dbo.Transactions
(
TransactionID bigint NOT NULL,
ClientID integer NOT NULL,
TransactionDate datetime2(2) NOT NULL,
CONSTRAINT PK_dbo_Transactions
PRIMARY KEY CLUSTERED (TransactionID),
INDEX dbo_Transactions_ClientID_TranDate
(ClientID, TransactionDate)
);
CREATE TABLE #Working
(
TransactionID bigint NULL,
ClientID integer NULL,
TransactionDate datetime2(2) NULL,
INDEX cx CLUSTERED (TransactionID)
);
単純にするために、ターゲットテーブルに1行、ソースに4行を配置します。
INSERT dbo.Transactions
(TransactionID, ClientID, TransactionDate)
VALUES
(1, 1, '2019-01-01');
INSERT #Working
(TransactionID, ClientID, TransactionDate)
VALUES
(1, 2, NULL),
(1, NULL, '2019-03-03'),
(1, 3, NULL),
(1, NULL, '2019-02-02');
4つのソース行はすべてTransactionID
のターゲットと一致するので、TransactionID
だけで結合する更新(問題のようなもの)を実行する場合、どちらが使用されますか?
UPDATE T
SET T.TransactionID = W.TransactionID,
T.ClientID = W.ClientID,
T.TransactionDate = W.TransactionDate
FROM #Working AS W
JOIN dbo.Transactions AS T
ON T.TransactionID = W.TransactionID;
(TransactionID
列の更新はデモでは重要ではありません。必要に応じてコメントアウトできます。)
最初の驚きは、ターゲットテーブルが列にnullを許可しないにもかかわらず(すべての候補行にnullが含まれる)、UPDATE
がエラーなしで完了することです。
重要な点は、結果はundefinedであり、この場合、どのソース行にも一致しない結果が生成されることです。
SELECT
T.TransactionID,
T.ClientID,
T.TransactionDate
FROM dbo.Transactions AS T;
╔═══════════════╦══════════╦════════════════════════╗
║ TransactionID ║ ClientID ║ TransactionDate ║
╠═══════════════╬══════════╬════════════════════════╣
║ 1 ║ 2 ║ 2019-03-03 00:00:00.00 ║
╚═══════════════╩══════════╩════════════════════════╝
更新は、同等のMERGE
ステートメントとして記述した場合に成功するように記述してください。このステートメントは、同じターゲット行を複数回更新する試みをチェックします。 MERGE
を直接使用することはお勧めしません。これは、多くの実装バグの影響を受け、通常はパフォーマンスが低下するためです。
おまけとして、現在の更新を確定的であるように書き換えると、時々発生するバグの問題もなくなることがわかります。もちろん、非決定的なアップデートを書いている人のために、製品のバグはまだ存在しています。