7

Meine Anfrage:Checkeinschränkung funktioniert nicht auf Masseneinsatz für mehr als 250 Datensätze

INSERT into PriceListRows (PriceListChapterId,[No]) 
    SELECT TOP 250 100943 ,N'2' 
    FROM #AnyTable 

Diese Abfrage funktioniert gut und die folgende Ausnahme auslöst, wie gewünscht:

Die INSERT-Anweisung in Konflikt mit der CHECK constraint "CK_PriceListRows_RowNo_Is_Not_Unqiue_In_PriceList". Der Konflikt trat in der Datenbank "TadkarWeb", Tabelle "dbo.PriceListRows" auf.

aber mit wechselnden SELECT TOP 250-SELECT TOP 251 (ja! Nur 250-251 ändern!) Die Abfrage erfolgreich ohne Kontrolle constrain Ausnahme läuft!

Warum dieses seltsame Verhalten?

HINWEISE:

  1. ist meine Check-Bedingung eine Funktion, die eine Art von Eindeutigkeit überprüft. Es fragt nach 4 Tabellen.

  2. ich sowohl auf SQL Server überprüft 2012 SP2 und SQL Server 2014 SP1

** EDIT 1 **

Checkeinschränkung Funktion:

ALTER FUNCTION [dbo].[CheckPriceListRows_UniqueNo] (
    @rowNo nvarchar(50), 
    @rowId int, 
    @priceListChapterId int, 
    @projectId int) 
RETURNS bit 
AS 
BEGIN 
    IF EXISTS (SELECT 1 
       FROM RowInfsView 
       WHERE PriceListId = (SELECT PriceListId 
            FROM ChapterInfoView 
            WHERE Id = @priceListChapterId) 
       AND (@rowID IS NULL OR Id <> @rowId) 
       AND No = @rowNo 
       AND (@projectId IS NULL OR 
         (ProjectId IS NULL OR ProjectId = @projectId))) 
     RETURN 0 -- Error 

    --It is ok! 
    RETURN 1 
END 

** BEARBEITEN 2 ** Überprüfen Sie den Einschränkungscode (welcher SQL Server 2012 prod uces):

ALTER TABLE [dbo].[PriceListRows] WITH NOCHECK ADD CONSTRAINT [CK_PriceListRows_RowNo_Is_Not_Unqiue_In_PriceList] CHECK (([dbo].[tfn_CheckPriceListRows_UniqueNo]([No],[Id],[PriceListChapterId],[ProjectId])=(1))) 
GO 

ALTER TABLE [dbo].[PriceListRows] CHECK CONSTRAINT [CK_PriceListRows_RowNo_Is_Not_Unqiue_In_PriceList] 
GO 

** EDIT 3 **

Ausführungspläne sind hier: https://www.dropbox.com/s/as2r92xr14cfq5i/execution%20plans.zip?dl=0

** EDIT 4 ** RowInfsView Definition lautet:

SELECT  dbo.PriceListRows.Id, dbo.PriceListRows.No, dbo.PriceListRows.Title, dbo.PriceListRows.UnitCode, dbo.PriceListRows.UnitPrice, dbo.PriceListRows.RowStateCode, dbo.PriceListRows.PriceListChapterId, 
         dbo.PriceListChapters.Title AS PriceListChapterTitle, dbo.PriceListChapters.No AS PriceListChapterNo, dbo.PriceListChapters.PriceListCategoryId, dbo.PriceListCategories.No AS PriceListCategoryNo, 
         dbo.PriceListCategories.Title AS PriceListCategoryTitle, dbo.PriceListCategories.PriceListClassId, dbo.PriceListClasses.No AS PriceListClassNo, dbo.PriceListClasses.Title AS PriceListClassTitle, 
         dbo.PriceListClasses.PriceListId, dbo.PriceLists.Title AS PriceListTitle, dbo.PriceLists.Year, dbo.PriceListRows.ProjectId, dbo.PriceListRows.IsTemplate 
FROM   dbo.PriceListRows INNER JOIN 
         dbo.PriceListChapters ON dbo.PriceListRows.PriceListChapterId = dbo.PriceListChapters.Id INNER JOIN 
         dbo.PriceListCategories ON dbo.PriceListChapters.PriceListCategoryId = dbo.PriceListCategories.Id INNER JOIN 
         dbo.PriceListClasses ON dbo.PriceListCategories.PriceListClassId = dbo.PriceListClasses.Id INNER JOIN 
         dbo.PriceLists ON dbo.PriceListClasses.PriceListId = dbo.PriceLists.Id 
+0

Sie sind die gleichen zwei Werte Einfügen (100.943 und 2) 250-mal? Klingt wie du solltest die Ausnahme schon in der 2. Reihe –

+0

@jamez genau bekommen! es ist für den Test. Ich sollte Fehler bekommen, aber ich weiß nicht, warum ich es nicht verstehe. (In der Tat bekomme ich es für 250 Datensätze einfügen, aber nicht für 251 Einsen!) –

+0

ja seltsam. Ich habe den Beitrag bearbeitet. –

Antwort

-1

Es ist möglich, dass Sie begegnen Inkorrekte Optimierung einer Abfrage, aber ohne die Daten in allen beteiligten Tabellen, können wir den Fehler nicht reproduzieren.

Für diese Art von Überprüfungen empfehle ich jedoch die Verwendung von Triggern anstelle von Check-Einschränkungen basierend auf Funktionen. In einem Trigger könnten Sie eine SELECT-Anweisung verwenden, um zu debuggen, warum sie nicht wie erwartet funktioniert. Zum Beispiel:

CREATE TRIGGER trg_PriceListRows_CheckUnicity ON PriceListRows 
FOR INSERT, UPDATE 
AS 
IF @@ROWCOUNT>0 BEGIN 
    /* 
    SELECT * FROM inserted i 
    INNER JOIN RowInfsView r 
    ON r.PriceListId = (
     SELECT c.PriceListId 
     FROM ChapterInfoView c 
     WHERE c.Id = i.priceListChapterId 
    ) 
    AND r.Id <> i.Id 
    AND r.No = i.No 
    AND (r.ProjectId=i.ProjectId OR r.ProjectId IS NULL AND i.ProjectId IS NULL) 
    */ 
    IF EXISTS (
     SELECT * FROM inserted i 
     WHERE EXISTS (
      SELECT * FROM RowInfsView r 
      WHERE r.PriceListId = (
       SELECT c.PriceListId 
       FROM ChapterInfoView c 
       WHERE c.Id = i.priceListChapterId 
      ) 
      AND r.Id <> i.Id 
      AND r.No = i.No 
      AND (r.ProjectId=i.ProjectId OR r.ProjectId IS NULL AND i.ProjectId IS NULL) 
     ) 
    ) BEGIN 
     RAISERROR ('Duplicate rows!',16,1) 
     ROLLBACK 
     RETURN 
    END 
END 

Auf diese Weise können Sie sehen, was überprüft wird und korrigieren Sie Ihre Ansichten und/oder vorhandenen Daten.

3

Die Erklärung ist, dass Ihr Ausführungsplan einen Aktualisierungsplan "wide" (Index für Index) verwendet.

Die Zeilen werden in Schritt 1 im Plan in den gruppierten Index eingefügt. Die Prüfbedingungen werden für jede Zeile in Schritt 2 validiert.

In die nicht gruppierten Indizes werden keine Zeilen eingefügt, bis alle Zeilen in den gruppierten Index eingefügt wurden.

Dies liegt daran, dass es zwei blocking operators zwischen dem clustered index insert/constraintchecking und den nicht gruppierten Indexeinfügungen gibt. Die eifrige Spule (Schritt 3) und die Art (Schritt 4). Beide erzeugen keine Ausgabezeilen, bis sie alle Eingabezeilen verbraucht haben.

enter image description here

Der Plan für die skalare UDF verwendet den nicht gruppierten Index passende Zeilen, um zu versuchen und zu finden.

enter image description here

An der Stelle, die Check-Bedingung haben keine Zeilen läuft noch in den nicht gruppierten Index so kommt diese Prüfung leer aus eingesetzt worden ist.

Wenn Sie weniger Zeilen einfügen, erhalten Sie einen "schmalen" (zeilenweisen) Aktualisierungsplan und vermeiden das Problem.

Mein Rat ist, diese Art von Validierung in Prüfbedingungen zu vermeiden. Es ist schwierig, sicher zu sein, dass der Code unter allen Umständen korrekt funktioniert (z. B. unterschiedliche Ausführungspläne und Isolationsstufen) und zusätzlich block parellelism in Abfragen gegen die Tabelle. Versuchen Sie, dies deklarativ zu tun (eine eindeutige Einschränkung, die an andere Tabellen angefügt werden muss, kann oft mit einer indizierten Sicht erreicht werden).


Eine vereinfachte repro ist

CREATE FUNCTION dbo.F(@Z INT) 
RETURNS BIT 
AS 
    BEGIN 
     RETURN CASE WHEN EXISTS (SELECT * FROM dbo.T1 WHERE Z = @Z) THEN 0 ELSE 1 END 
    END 

GO 

CREATE TABLE dbo.T1 
    (
    ID INT IDENTITY PRIMARY KEY, 
    X INT, 
    Y CHAR(8000) DEFAULT '', 
    Z INT, 
    CHECK (dbo.F(Z) = 1), 
    CONSTRAINT IX_X UNIQUE (X, ID), 
    CONSTRAINT IX_Z UNIQUE (Z, ID) 
) 

--Fails with check constraint error 
INSERT INTO dbo.T1 (Z) 
SELECT TOP (10) 1 FROM master..spt_values; 

/*I get a wide update plan for TOP (2000) but this may not be reliable 
    across instances so using trace flag 8790 to get a wide plan. */ 
INSERT INTO dbo.T1 (Z) 
SELECT TOP (10) 2 FROM master..spt_values 
OPTION (QUERYTRACEON 8790); 

GO 

/*Confirm only the second insert succceed (Z=2)*/ 
SELECT * FROM dbo.T1; 

DROP TABLE dbo.T1;  
DROP FUNCTION dbo.F;