对于超过250条记录

时间:2015-10-10 13:56:31

标签: sql-server tsql user-defined-functions check-constraints

我的查询:

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

此查询正常工作,并且根据需要引发以下异常:

  

INSERT语句与CHECK约束冲突   " CK_PriceListRows_RowNo_Is_Not_Unqiue_In_PriceList&#34 ;.冲突   发生在数据库" TadkarWeb",table" dbo.PriceListRows"。

但是将SELECT TOP 250更改为SELECT TOP 251(是的!只需将250更改为251!),查询成功运行,没有任何检查约束异常!

为什么这种奇怪的行为?

注意:

  1. 我的检查约束是一种检查某种唯一性的函数。它查询约4个表。

  2. 我检查了SQL Server 2012 SP2和SQL Server 2014 SP1

  3. ** 编辑1 **

    检查约束函数:

    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
    

    ** 编辑2 ** 检查约束代码(SQL Server 2012生成的内容):

    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
    

    ** 编辑3 **

    执行计划在这里:https://www.dropbox.com/s/as2r92xr14cfq5i/execution%20plans.zip?dl=0

    ** 编辑4 ** RowInfsView定义是:

    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
    

2 个答案:

答案 0 :(得分:3)

解释是您的执行计划使用"wide"(索引索引)更新计划。

在计划的步骤1中将行插入聚集索引中。并且在步骤2中对每行验证检查约束。

在将所有行都插入到聚簇索引中之前,不会将任何行插入到非聚簇索引中。

这是因为聚簇索引插入/约束检查和非聚簇索引插入之间有两个blocking operators。急切的线轴(步骤3)和排序(步骤4)。除非消耗了所有输入行,否则它们都不会产生输出行。

enter image description here

标量UDF的计划使用非聚集索引来尝试查找匹配的行。

enter image description here

在检查约束运行时,尚未将任何行插入到非聚集索引中,因此此检查为空。

当您插入较少的行时,您会获得“缩小”(逐行)更新计划并避免此问题。

我的建议是在检查约束中避免这种验证。很难确保代码在所有情况下(例如不同的执行计划和隔离级别)都能正常工作,并且在针对表的查询中还需要block parellelism。尝试以声明方式执行此操作(通常可以使用索引视图实现需要连接到其他表的唯一约束)。

简化的复制品是

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; 

答案 1 :(得分:-1)

您可能遇到错误的查询优化,但如果没有所有相关表中的数据,我们就无法重现该错误。

但是,对于这种检查,我建议使用触发器而不是基于功能的检查约束。在触发器中,您可以使用SELECT语句来调试它无法按预期工作的原因。例如:

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

通过这种方式,您可以查看正在检查的内容并更正您的观看次数和/或现有数据。