Không thể chèn hàng khóa trùng lặp vào một chỉ mục không duy nhất?


13

Chúng tôi đã gặp phải lỗi lạ này ba lần trong vài ngày qua, sau khi không có lỗi trong 8 tuần và tôi đã bị bối rối.

Đây là thông báo lỗi:

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

Chỉ số chúng tôi có không phải là duy nhất. Nếu bạn nhận thấy, giá trị khóa trùng lặp trong thông báo lỗi thậm chí không khớp với chỉ mục. Điều kỳ lạ là nếu tôi chạy lại Proc, nó thành công.

Đây là liên kết gần đây nhất tôi có thể thấy có vấn đề của mình nhưng tôi không thấy giải pháp.

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

Một vài điều về kịch bản của tôi:

  • Công ty đang cập nhật Giao dịch (một phần của khóa chính) - Tôi nghĩ đây là nguyên nhân gây ra lỗi nhưng không biết tại sao? Chúng tôi sẽ loại bỏ logic đó.
  • Theo dõi thay đổi được bật trên bàn
  • Giao dịch đọc không cam kết

Có 45 trường cho mỗi bảng, tôi chủ yếu liệt kê những trường được sử dụng trong các chỉ mục. Tôi đang cập nhật TransactionID (khóa cụm) trong câu lệnh cập nhật (không cần thiết). Kỳ lạ là chúng tôi đã không có bất kỳ vấn đề nào trong nhiều tháng cho đến tuần trước. Và nó chỉ xảy ra lẻ tẻ qua SSIS.

Bàn

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

bảng tạm thời

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

Khóa chính

 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]

Chỉ mục không phân cụm

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)

tuyên bố cập nhật mẫu

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

Câu hỏi của tôi là, những gì đang xảy ra dưới mui xe? Và giải pháp là gì? Để tham khảo, liên kết ở trên đề cập đến điều này:

Tại thời điểm này, tôi có một vài lý thuyết:

  • Lỗi liên quan đến áp lực bộ nhớ hoặc kế hoạch cập nhật song song lớn, nhưng tôi sẽ mong đợi một loại lỗi khác và cho đến nay tôi không thể tương quan tài nguyên thấp sẽ định khung thời gian của các lỗi bị cô lập và lẻ tẻ này.
  • Một lỗi trong câu lệnh hoặc dữ liệu CẬP NHẬT đang gây ra sự vi phạm trùng lặp thực tế trên khóa chính, nhưng một số lỗi SQL Server tối nghĩa đang dẫn đến và thông báo lỗi trích dẫn tên chỉ mục sai.
  • Đọc bẩn kết quả từ việc đọc cách ly không được cam kết gây ra một bản cập nhật song song lớn để chèn kép. Nhưng các nhà phát triển ETL tuyên bố đọc mặc định đã cam kết được sử dụng và thật khó để xác định chính xác mức độ cô lập mà quá trình thực sự được sử dụng trong thời gian chạy.

Tôi nghi ngờ rằng nếu tôi điều chỉnh kế hoạch thực hiện như một cách giải quyết, có lẽ là gợi ý MAXDOP (1) hoặc sử dụng cờ theo dõi phiên để vô hiệu hóa hoạt động của bộ đệm, lỗi sẽ biến mất, nhưng không rõ điều này sẽ ảnh hưởng đến hiệu suất như thế nào

Phiên bản

Microsoft SQL Server 2017 (RTM-CU13) (KB4466404) - 14.0.3048.4 (X64) 30 tháng 11 năm 2018 12:57:58 Bản quyền (C) 2017 Microsoft Corporation Enterprise Edition (64-bit) trên Windows Server 2016 Standard 10.0 (Build 14393 :)

Câu trả lời:


10

Câu hỏi của tôi là, những gì đang xảy ra dưới mui xe? Và giải pháp là gì?

Đây là một lỗi. Vấn đề là nó chỉ xảy ra đôi khi, và sẽ rất khó để sinh sản. Tuy nhiên, cơ hội tốt nhất của bạn là tham gia hỗ trợ của Microsoft. Quá trình xử lý cập nhật rất phức tạp, vì vậy điều này sẽ đòi hỏi một cuộc điều tra rất chi tiết.

Để biết ví dụ về loại phức tạp có liên quan, hãy xem bài viết của tôi Lỗi MERGE với Chỉ mục được lọcKết quả không chính xác với Chế độ xem được lập chỉ mục . Không ai trong số những người liên quan trực tiếp đến vấn đề của bạn, nhưng họ cho một hương vị.

Viết một bản cập nhật xác định

Tất nhiên đó là khá chung chung. Có lẽ hữu ích hơn, tôi có thể nói rằng bạn nên tìm cách viết lại UPDATEtuyên bố hiện tại của bạn . Như tài liệu nói:

Hãy thận trọng khi chỉ định mệnh đề TỪ để cung cấp các tiêu chí cho hoạt động cập nhật. Kết quả của câu lệnh UPDATE không được xác định nếu câu lệnh bao gồm mệnh đề TỪ không được chỉ định theo cách chỉ có một giá trị cho mỗi lần xuất hiện của cột được cập nhật, đó là nếu câu lệnh UPDATE không có tính xác định.

Của bạn UPDATEkhông xác định , và do đó các kết quả được undefined . Bạn nên thay đổi nó để tối đa một hàng nguồn được xác định cho mỗi hàng mục tiêu. Nếu không có thay đổi đó, kết quả của bản cập nhật có thể không phản ánh bất kỳ hàng nguồn riêng lẻ nào .

Thí dụ

Để tôi chỉ cho bạn một ví dụ, sử dụng các bảng được mô hình hóa lỏng lẻo trên các bảng được đưa ra trong câu hỏi:

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

Để giữ cho mọi thứ đơn giản, hãy đặt một hàng trong bảng đích và bốn hàng trong nguồn:

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

Tất cả bốn hàng nguồn khớp với mục tiêu trên TransactionID, vậy cái nào sẽ được sử dụng nếu chúng tôi chạy một bản cập nhật (giống như trong câu hỏi) tham gia TransactionIDmột mình?

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;

(Cập nhật TransactionIDcột không quan trọng đối với bản demo, bạn có thể nhận xét nếu muốn.)

Điều ngạc nhiên đầu tiên là việc UPDATEhoàn thành không có lỗi, mặc dù bảng đích không cho phép null trong bất kỳ cột nào (tất cả các hàng ứng cử viên đều chứa null).

Điểm quan trọng là kết quả không được xác định và trong trường hợp này tạo ra kết quả khớp với không có hàng nào trong nguồn:

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 <> fiddle demo

Thêm chi tiết: BẤT K Ag cốt liệu bị hỏng

Bản cập nhật phải được viết sao cho thành công nếu được viết dưới dạng MERGEcâu lệnh tương đương , kiểm tra các lần thử cập nhật cùng một hàng mục tiêu nhiều lần. Tôi thường không khuyên bạn nên sử dụng MERGEtrực tiếp, vì nó đã chịu quá nhiều lỗi thực hiện và thường có hiệu suất kém hơn.

Là một phần thưởng, bạn có thể thấy rằng việc viết lại bản cập nhật hiện tại của bạn là xác định sẽ dẫn đến vấn đề lỗi thường xuyên của bạn cũng sẽ biến mất. Tất nhiên, lỗi sản phẩm sẽ vẫn tồn tại đối với những người viết các bản cập nhật không xác định.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.