Benzersiz olmayan bir dizine yinelenen anahtar satırı ekleyemiyor musunuz?


14

Bu garip hatayı, son birkaç gün boyunca, 8 hafta boyunca hatasız olduktan sonra üç kez gördük ve güdük oldum.

Bu hata mesajıdır:

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

Elimizdeki dizin benzersiz değil . Dikkat ederseniz, hata iletisindeki yinelenen anahtar değeri dizinle aynı hizaya gelmez. Garip bir şey, eğer süreci tekrar çalıştırırsam başarılı olur.

Bu benim sorunlarım var bulabildiğim en son bağlantı ama bir çözüm görmüyorum.

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

Senaryom hakkında birkaç şey:

  • Proc TransactionID (birincil anahtarın bir parçası) güncelleştiriyor - bu hata neden olduğunu düşünüyorum ama neden bilmiyorum? Bu mantığı kaldıracağız.
  • Değişiklik izleme tabloda etkin
  • İşlem okunmamış gerçekleştiriliyor

Her tablo için 45 alan var, ben esas olarak dizinlerde kullanılanları listeledim. Güncelleme deyiminde (gereksiz) TransactionID (kümelenmiş anahtar) güncelleştiriyorum. Geçen haftaya kadar aylardır herhangi bir sorun yaşamadığımız garip. Ve bu sadece SSIS aracılığıyla ara sıra gerçekleşiyor.

tablo

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

geçici masa

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

Birincil anahtar

 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]

Kümelenmemiş dizin

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)

örnek güncelleme bildirimi

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

Sorum şu: Kaputun altında neler oluyor? Peki çözüm nedir? Referans olarak, yukarıdaki bağlantı bundan bahseder:

Bu noktada birkaç teorim var:

  • Bellek baskısı veya büyük paralel güncelleme planı ile ilgili hata, ancak farklı bir hata türü beklerdim ve şimdiye kadar düşük kaynakları ilişkilendiremiyorum bu izole ve düzensiz hataların zaman dilimi olacaktır.
  • UPDATE deyimindeki veya verilerindeki bir hata birincil anahtarda gerçek bir yinelenen ihlale neden oluyor, ancak bazı belirsiz SQL Server hatalarına neden oluyor ve yanlış dizin adını gösteren hata mesajı çıkıyor.
  • Okuma tamamlanmamış izolasyondan kaynaklanan kirli okumalar, çift insert için büyük bir paralel güncellemeye neden olur. Ancak ETL geliştiricileri varsayılan okumanın kullanıldığını iddia ediyor ve sürecin gerçekte hangi izolasyon seviyesinin gerçek zamanlı olarak kullanıldığını tam olarak belirlemek zor.

Yürütme planını bir çözüm olarak, belki de MAXDOP (1) ipucu olarak veya biriktirme işlemini devre dışı bırakmak için oturum izleme bayrağını kullanırsam, hatanın ortadan kalkacağından şüpheleniyorum, ancak bunun performansı nasıl etkileyeceği belirsiz

versiyon

Microsoft SQL Server 2017 (RTM-CU13) (KB4466404) - 14.0.3048.4 (X64) 30 Kasım 2018 12:57:58 Telif Hakkı (C) 2017 Windows Server 2016 Standard 10.0'da (Derleme 14393) Microsoft Corporation Enterprise Edition (64 bit) :)

Yanıtlar:


10

Sorum şu: Kaputun altında neler oluyor? Peki çözüm nedir?

Bu bir hatadır. Sorun şu ki, sadece ara sıra gerçekleşiyor ve çoğalması zor olacak. Yine de, en iyi şansınız Microsoft desteğine başvurmaktır. Güncelleme işlemi akıl almaz derecede karmaşıktır, bu nedenle bu çok ayrıntılı bir araştırma gerektirir.

İlgili karmaşıklıkların bir örneği için, Filtrelenmiş Dizinlere sahip MERGE Hatası ve Dizinlenmiş Görünümlerle Yanlış Sonuçlar'a bakın . Bunların hiçbiri doğrudan sorununuzla ilgili değil, ama bir lezzet veriyorlar.

Deterministik bir güncelleme yazın

Tabii ki hepsi oldukça genel. Belki daha faydalı bir şekilde, mevcut ifadenizi yeniden yazmayı denemeniz gerektiğini söyleyebilirim UPDATE. Gibi belgeler diyor ki:

Güncelleme işlemi için kriterleri sağlamak üzere FROM yan tümcesini belirtirken dikkatli olun. İfade, güncellenen her sütun oluşumu için yalnızca bir değer kullanılabilecek şekilde, yani UPDATE deyimi deterministik değilse, belirtilmeyen bir FROM yan tümcesi içeriyorsa, UPDATE deyiminin sonuçları tanımsızdır.

Sizin UPDATEise deterministik değildir ve sonuçlar bu nedenle edilir tanımsız . Bunu, her hedef satır için en fazla bir kaynak satırı tanımlanacak şekilde değiştirmelisiniz. Bu değişiklik olmadan, güncellemenin sonucu herhangi bir kaynak satırını yansıtmayabilir .

Misal

Soruda verilenlere gevşek bir şekilde modellenmiş tabloları kullanarak size bir örnek göstereyim:

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

İşleri basitleştirmek için, hedef tabloya bir satır ve kaynağa dört satır koyun:

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

Dört kaynak satırın tümü hedefle eşleşir TransactionID, bu yüzden tek başına bir güncelleme (sorudaki gibi) çalıştırırsak hangisi kullanılır 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;

( TransactionIDSütunun güncellenmesi demo için önemli değildir, isterseniz yorum yapabilirsiniz.)

İlk sürpriz, UPDATEhedef tablonun herhangi bir sütunda null değerlere izin vermemesine rağmen hatasız olarak tamamlanmasıdır (tüm aday satırlar null içerir).

Önemli olan, sonucun tanımlanmamış olmasıdır ve bu durumda kaynak satırların hiçbiriyle eşleşmeyen bir sonuç üretir:

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 <> keman demosu

Daha fazla detay: HERHANGİ BİR Toplam Kırıldı

MERGEGüncelleme, aynı hedef satırı bir kereden fazla güncelleme girişimlerini kontrol eden eşdeğer ifade olarak yazılırsa başarılı olacak şekilde yazılmalıdır . Genellikle MERGEdoğrudan kullanılmasını önermiyorum , çünkü çok fazla uygulama hatasına maruz kaldı ve normalde daha kötü performansa sahip.

Bonus olarak, mevcut güncellemenizi belirleyici olarak yeniden yazmanın, ara sıra oluşan hata sorununuzun ortadan kalkmasına neden olacağını görebilirsiniz. Elbette determinstik olmayan güncellemeler yazan kişiler için ürün hatası var olmaya devam edecek.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.