ไม่สามารถแทรกแถวคีย์ซ้ำในดัชนีที่ไม่ซ้ำกันใช่ไหม


14

เราพบข้อผิดพลาดแปลก ๆ สามครั้งในช่วงสองสามวันที่ผ่านมาหลังจากปราศจากข้อผิดพลาดเป็นเวลา 8 สัปดาห์และฉันก็นิ่งงัน

นี่คือข้อความแสดงข้อผิดพลาด:

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).".

ดัชนีที่เรามีไม่ซ้ำกัน หากคุณสังเกตเห็นค่าคีย์ที่ซ้ำกันในข้อความแสดงข้อผิดพลาดจะไม่ตรงกับดัชนี สิ่งที่แปลกคือถ้าฉันเรียกใช้ proc อีกครั้งมันก็ประสบความสำเร็จ

นี่คือลิงค์ล่าสุดที่ฉันพบว่ามีปัญหาของฉัน แต่ฉันไม่เห็นวิธีแก้ปัญหา

https://www.sqlservercentral.com/forums/topic/error-cannot-insert-duplicate-key-row-in-a-non-unique-index

สองสามสิ่งเกี่ยวกับสถานการณ์ของฉัน:

  • Proc กำลังอัปเดต TransactionID (ส่วนหนึ่งของคีย์หลัก) - ฉันคิดว่านี่เป็นสาเหตุของข้อผิดพลาด แต่ไม่รู้ว่าเพราะอะไร เราจะลบตรรกะนั้น
  • เปิดใช้งานการติดตามการเปลี่ยนแปลงบนตาราง
  • การทำธุรกรรมอ่านปราศจากข้อผูกมัด

มี 45 เขตข้อมูลสำหรับแต่ละตารางฉันส่วนใหญ่แสดงรายการที่ใช้ในดัชนี ฉันกำลังปรับปรุง 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 Server ที่คลุมเครือบางอย่างเกิดขึ้นและข้อความแสดงข้อผิดพลาดที่อ้างอิงชื่อดัชนีผิด
  • การอ่านสกปรกเกิดจากการแยกแบบไม่มีข้อผูกมัดทำให้เกิดการอัพเดตแบบขนานขนาดใหญ่เพื่อแทรกสองครั้ง แต่นักพัฒนา ETL อ้างว่าได้ใช้การอ่านเริ่มต้นแล้วและเป็นการยากที่จะกำหนดระดับการแยกที่กระบวนการใช้จริงในขณะรันไทม์

ฉันสงสัยว่าถ้าฉันปรับแต่งแผนการดำเนินการเป็นวิธีแก้ปัญหาบางที MAXDOP (1) คำใบ้หรือการใช้การตั้งค่าสถานะการติดตามเซสชันเพื่อปิดใช้งานการดำเนินการสปูลข้อผิดพลาดจะหายไป แต่ก็ไม่มีความชัดเจนว่าจะส่งผลกระทบอย่างไรต่อประสิทธิภาพ

รุ่น

Microsoft SQL Server 2017 (RTM-CU13) (KB4466404) - 14.0.3048.4 (X64) 30 พ.ย. 2018 12:57:58 ลิขสิทธิ์ (C) 2017 Microsoft Corporation รุ่นองค์กร (64 บิต) บน Windows Server 2016 มาตรฐาน 10.0 (Build 14393) :)

คำตอบ:


10

คำถามของฉันคือสิ่งที่เกิดขึ้นภายใต้ประทุน? และทางออกคืออะไร?

มันเป็นข้อผิดพลาด ปัญหาคือมันเกิดขึ้นเป็นครั้งคราวเท่านั้นและจะยากที่จะทำซ้ำ ยังโอกาสที่ดีที่สุดของคุณคือการมีส่วนร่วมสนับสนุน Microsoft การประมวลผลการอัพเดทมีความซับซ้อนอย่างเหลือเชื่อดังนั้นสิ่งนี้จะต้องมีการตรวจสอบอย่างละเอียด

สำหรับตัวอย่างของการเรียงลำดับของความซับซ้อนที่เกี่ยวข้องกับการมีลักษณะที่ฉันโพสต์Bug ผสานกับดัชนีกรองและผลที่ไม่ถูกต้องมีการจัดทำดัชนีชม ทั้งสองเกี่ยวข้องกับปัญหาของคุณโดยตรง แต่พวกเขาให้รสชาติ

เขียนการปรับปรุงที่กำหนดขึ้น

แน่นอนว่าเป็นเรื่องทั่วไป บางทีอาจมีประโยชน์มากกว่านี้ฉันบอกได้เลยว่าคุณควรเขียนUPDATEข้อความปัจจุบันของคุณใหม่ ตามเอกสารระบุว่า:

ใช้ความระมัดระวังเมื่อระบุอนุประโยค FROM เพื่อกำหนดเกณฑ์สำหรับการดำเนินการอัพเดต ผลลัพธ์ของคำสั่ง UPDATE จะไม่ได้กำหนดถ้าคำสั่งนั้นมีส่วนคำสั่ง FROM ที่ไม่ได้ระบุในลักษณะที่จะมีเพียงค่าเดียวเท่านั้นสำหรับแต่ละคอลัมน์ที่เกิดขึ้นซึ่งได้รับการปรับปรุงนั่นคือถ้าคำสั่ง UPDATE ไม่ได้กำหนดไว้

ของคุณUPDATEคือไม่ได้กำหนดและผลจึงไม่ได้กำหนด คุณควรเปลี่ยนเพื่อให้มีการระบุแถวของแถวต้นทางมากที่สุดหนึ่งแถวสำหรับแต่ละแถวเป้าหมาย หากไม่มีการเปลี่ยนแปลงผลลัพธ์ของการอัปเดตอาจไม่สะท้อนแถวของแหล่งข้อมูลใด ๆ

ตัวอย่าง

ให้ฉันแสดงตัวอย่างโดยใช้ตารางที่สร้างแบบจำลองอย่างหลวม ๆ กับที่ให้ไว้ในคำถาม:

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)
);

เพื่อให้ทุกอย่างง่ายขึ้นให้วางหนึ่งแถวในตารางเป้าหมายและสี่แถวในแหล่งที่มา:

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');

ทั้งหมดสี่แถวแหล่งที่มาตรงกับเป้าหมายบน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คอลัมน์ไม่สำคัญสำหรับการสาธิตคุณสามารถแสดงความคิดเห็นได้หากต้องการ)

ความประหลาดใจแรกคือการUPDATEทำให้เสร็จสมบูรณ์โดยไม่มีข้อผิดพลาดแม้จะมีตารางเป้าหมายที่ไม่อนุญาตให้มีค่า null ในคอลัมน์ใด ๆ

จุดสำคัญคือผลลัพธ์ไม่ได้ถูกกำหนดและในกรณีนี้จะสร้างผลลัพธ์ที่ไม่ตรงกับแถวของแหล่งที่มาใด ๆ :

SELECT
    T.TransactionID,
    T.ClientID,
    T.TransactionDate
FROM dbo.Transactions AS T;
╔═══════════════╦══════════╦════════════════════════╗
║ TransactionID ║ ClientID ║    TransactionDate     ║
╠═══════════════╬══════════╬════════════════════════╣
║             1 ║        2 ║ 2019-03-03 00:00:00.00 ║
╚═══════════════╩══════════╩════════════════════════╝

db <> การสาธิตซอ

รายละเอียดเพิ่มเติม: การรวมใด ๆ ที่ใช้งานไม่ได้

ควรเขียนการอัปเดตเพื่อให้สำเร็จหากเขียนเป็นMERGEคำสั่งที่เทียบเท่าซึ่งจะตรวจสอบความพยายามในการอัปเดตแถวเป้าหมายเดียวกันมากกว่าหนึ่งครั้ง ฉันไม่แนะนำให้ใช้MERGEโดยตรงเพราะมันมีข้อผิดพลาดในการใช้งานมากมายและโดยปกติแล้วจะมีประสิทธิภาพที่แย่ลง

ในฐานะโบนัสคุณอาจพบว่าการเขียนการอัพเดตปัจจุบันของคุณใหม่เพื่อกำหนดไว้จะส่งผลให้ปัญหาข้อบกพร่องของคุณเป็นครั้งคราวเช่นกัน ข้อผิดพลาดของผลิตภัณฑ์จะยังคงมีอยู่สำหรับผู้ที่เขียนการปรับปรุงแน่นอนไม่ใช่แน่นอน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.