고유하지 않은 인덱스에 중복 키 행을 삽입 할 수 없습니까?


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) 힌트 또는 세션 추적 플래그를 사용하여 스풀 작업을 사용하지 않도록 설정하면 오류가 사라지지만 이것이 성능에 어떤 영향을 미치는지 확실하지 않습니다.

버전

Windows Server 2016 Standard 10.0의 Microsoft SQL Server 2017 (RTM-CU13) (KB4466404)-14.0.3048.4 (X64) 11 월 30 2018 12:57:58 Copyright (C) 2017 Microsoft Corporation Enterprise Edition (64 비트) (빌드 14393 :)

답변:


10

내 질문은, 후드 아래에서 무슨 일이 일어나고 있습니까? 그리고 해결책은 무엇입니까?

버그입니다. 문제는 가끔 발생하며 재현하기가 어렵다는 것입니다. 여전히 최선의 기회는 Microsoft 지원에 문의하는 것입니다. 업데이트 처리는 매우 복잡하므로 매우 자세한 조사가 필요합니다.

관련된 복잡한 종류의 예를 보려면 필자 가 필터링 한 인덱스가있는 MERGE Bug 인덱스 된 뷰의 잘못된 결과를 . 두 가지 모두 귀하의 문제와 직접 관련이 없지만 맛을냅니다.

결정적 업데이트 작성

그것은 물론 다소 일반적인 것입니다. 아마도 더 유용하게, 나는 당신이 당신의 현재 UPDATE진술 을 다시 작성해야한다고 말할 수 있습니다 . 현상태대로 설명서를 말한다 :

FROM 절을 지정할 때 업데이트 조작의 기준을 제공 할 때주의하십시오. 명령문에 업데이트 된 각 열 발생에 대해 하나의 값만 사용할 수있는 방식으로 지정되지 않은 FROM 절이 명령문에 포함 된 경우 (즉, UPDATE 문이 결정적이지 않은 경우) 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');

네 개의 소스 행이 모두 on의 목표와 일치 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 대상 테이블이 모든 열에서 널을 허용하지 않더라도 오류없이 완료됩니다 (모든 후보 행에 널이 포함됨).

중요한 점은 결과가 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 ║
╚═══════════════╩══════════╩════════════════════════╝

db <> 바이올린 데모

자세한 내용 : 모든 집계가 손상됨

MERGE동일한 대상 행으로 두 번 이상 업데이트 시도를 확인 하는 동등한 명령문 으로 작성된 경우 성공하도록 업데이트를 작성해야합니다 . 나는 일반적으로 사용하지 않는 것이 좋습니다MERGE 직접 구현하는 것은 구현 버그가 너무 많아 일반적으로 성능이 저하되기 때문입니다.

보너스로, 현재 업데이트를 결정 론적으로 다시 작성하면 가끔 버그 문제도 사라질 수 있습니다. 비 결정적 업데이트를 작성하는 사람들에게는 여전히 제품 버그가 존재합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.