Tại sao tuyên bố MERGE này khiến phiên bị giết?


23

Tôi có MERGEtuyên bố dưới đây được ban hành đối với cơ sở dữ liệu:

MERGE "MySchema"."Point" AS t
USING (
       SELECT "ObjectId", "PointName", z."Id" AS "LocationId", i."Id" AS "Region"
         FROM @p1 AS d
         JOIN "MySchema"."Region" AS i ON i."Name" = d."Region"
    LEFT JOIN "MySchema"."Location" AS z ON z."Name" = d."Location" AND z."Region" = i."Id"
       ) AS s
   ON s."ObjectId" = t."ObjectId"
 WHEN NOT MATCHED BY TARGET 
    THEN INSERT ("ObjectId", "Name", "LocationId", "Region") VALUES (s."ObjectId", s."PointName", s."LocationId", s."Region")
 WHEN MATCHED 
    THEN UPDATE 
     SET "Name" = s."PointName"
       , "LocationId" = s."LocationId"
       , "Region" = s."Region"
OUTPUT $action, inserted.*, deleted.*;

Tuy nhiên, điều này khiến phiên bị chấm dứt với lỗi sau:

Msg 0, Level 11, State 0, Line 67 Đã xảy ra lỗi nghiêm trọng trên lệnh hiện tại. Các kết quả, nếu có, cần được loại bỏ.

Msg 0, Level 20, State 0, Line 67 Đã xảy ra lỗi nghiêm trọng trên lệnh hiện tại. Các kết quả, nếu có, cần được loại bỏ.

Tôi đã đặt một đoạn mã kiểm tra ngắn cùng nhau tạo ra lỗi:

USE master;
GO
IF DB_ID('TEST') IS NOT NULL
DROP DATABASE "TEST";
GO
CREATE DATABASE "TEST";
GO
USE "TEST";
GO

SET NOCOUNT ON;

IF SCHEMA_ID('MySchema') IS NULL
EXECUTE('CREATE SCHEMA "MySchema"');
GO

IF OBJECT_ID('MySchema.Region', 'U') IS NULL
CREATE TABLE "MySchema"."Region" (
"Id" TINYINT IDENTITY NOT NULL CONSTRAINT "PK_MySchema_Region" PRIMARY KEY,
"Name" VARCHAR(8) NOT NULL CONSTRAINT "UK_MySchema_Region" UNIQUE
);
GO

INSERT [MySchema].[Region] ([Name]) 
VALUES (N'A'), (N'B'), (N'C'), (N'D'), (N'E'), ( N'F'), (N'G');

IF OBJECT_ID('MySchema.Location', 'U') IS NULL
CREATE TABLE "MySchema"."Location" (
"Id" SMALLINT IDENTITY NOT NULL CONSTRAINT "PK_MySchema_Location" PRIMARY KEY,
"Region" TINYINT NOT NULL CONSTRAINT "FK_MySchema_Location_Region" FOREIGN KEY REFERENCES "MySchema"."Region" ("Id"),
"Name" VARCHAR(128) NOT NULL,
CONSTRAINT "UK_MySchema_Location" UNIQUE ("Region", "Name") 
);
GO

IF OBJECT_ID('MySchema.Point', 'U') IS NULL
CREATE TABLE "MySchema"."Point" (
"ObjectId" BIGINT NOT NULL CONSTRAINT "PK_MySchema_Point" PRIMARY KEY,
"Name" VARCHAR(64) NOT NULL,
"LocationId" SMALLINT NULL CONSTRAINT "FK_MySchema_Point_Location" FOREIGN KEY REFERENCES "MySchema"."Location"("Id"),
"Region" TINYINT NOT NULL CONSTRAINT "FK_MySchema_Point_Region" FOREIGN KEY REFERENCES "MySchema"."Region" ("Id"),
CONSTRAINT "UK_MySchema_Point" UNIQUE ("Name", "Region", "LocationId")
);
GO

-- CONTAINS HISTORIC Point DATA
IF OBJECT_ID('MySchema.PointHistory', 'U') IS NULL
CREATE TABLE "MySchema"."PointHistory" (
"Id" BIGINT IDENTITY NOT NULL CONSTRAINT "PK_MySchema_PointHistory" PRIMARY KEY,
"ObjectId" BIGINT NOT NULL,
"Name" VARCHAR(64) NOT NULL,
"LocationId" SMALLINT NULL,
"Region" TINYINT NOT NULL
);
GO

CREATE TYPE "MySchema"."PointTable" AS TABLE (
"ObjectId"      BIGINT          NOT NULL PRIMARY KEY,
"PointName"     VARCHAR(64)     NOT NULL,
"Location"      VARCHAR(16)     NULL,
"Region"        VARCHAR(8)      NOT NULL,
UNIQUE ("PointName", "Region", "Location")
);
GO

DECLARE @p1 "MySchema"."PointTable";

insert into @p1 values(10001769996,N'ABCDEFGH',N'N/A',N'E')

MERGE "MySchema"."Point" AS t
USING (
       SELECT "ObjectId", "PointName", z."Id" AS "LocationId", i."Id" AS "Region"
         FROM @p1 AS d
         JOIN "MySchema"."Region" AS i ON i."Name" = d."Region"
    LEFT JOIN "MySchema"."Location" AS z ON z."Name" = d."Location" AND z."Region" = i."Id"
       ) AS s
   ON s."ObjectId" = t."ObjectId"
 WHEN NOT MATCHED BY TARGET 
    THEN INSERT ("ObjectId", "Name", "LocationId", "Region") VALUES (s."ObjectId", s."PointName", s."LocationId", s."Region")
 WHEN MATCHED 
    THEN UPDATE 
     SET "Name" = s."PointName"
       , "LocationId" = s."LocationId"
       , "Region" = s."Region"
OUTPUT $action, inserted.*, deleted.*;

Nếu tôi loại bỏ OUTPUTmệnh đề thì lỗi không xảy ra. Ngoài ra, nếu tôi xóa deletedtham chiếu thì lỗi không xảy ra. Vì vậy, tôi đã xem các tài liệu MSDN cho OUTPUTmệnh đề trạng thái:

Đã xóa không thể được sử dụng với mệnh đề OUTPUT trong câu lệnh INSERT.

Điều này có ý nghĩa với tôi, tuy nhiên toàn bộ vấn đề MERGElà bạn có thể không biết trước.

Ngoài ra, tập lệnh bên dưới hoạt động hoàn toàn tốt bất kể hành động được thực hiện:

USE tempdb;
GO
CREATE TABLE dbo.Target(EmployeeID int, EmployeeName varchar(10), 
     CONSTRAINT Target_PK PRIMARY KEY(EmployeeID));
CREATE TABLE dbo.Source(EmployeeID int, EmployeeName varchar(10), 
     CONSTRAINT Source_PK PRIMARY KEY(EmployeeID));
GO
INSERT dbo.Target(EmployeeID, EmployeeName) VALUES(100, 'Mary');
INSERT dbo.Target(EmployeeID, EmployeeName) VALUES(101, 'Sara');
INSERT dbo.Target(EmployeeID, EmployeeName) VALUES(102, 'Stefano');

GO
INSERT dbo.Source(EmployeeID, EmployeeName) Values(103, 'Bob');
INSERT dbo.Source(EmployeeID, EmployeeName) Values(104, 'Steve');
GO
-- MERGE statement with the join conditions specified correctly.
USE tempdb;
GO
BEGIN TRAN;
MERGE Target AS T
USING Source AS S
ON (T.EmployeeID = S.EmployeeID) 
WHEN NOT MATCHED BY TARGET AND S.EmployeeName LIKE 'S%' 
    THEN INSERT(EmployeeID, EmployeeName) VALUES(S.EmployeeID, S.EmployeeName)
WHEN MATCHED 
    THEN UPDATE SET T.EmployeeName = S.EmployeeName
WHEN NOT MATCHED BY SOURCE AND T.EmployeeName LIKE 'S%'
    THEN DELETE 
OUTPUT $action, inserted.*, deleted.*;
ROLLBACK TRAN;
GO 

Ngoài ra, tôi có các truy vấn khác sử dụng OUTPUTcùng một kiểu với câu hỏi đang gây ra lỗi và chúng hoạt động hoàn toàn tốt - sự khác biệt duy nhất giữa chúng là các bảng tham gia MERGE.

Điều này đang gây ra vấn đề lớn trong sản xuất cho chúng tôi. Tôi đã tái tạo lỗi này trong SQL2014 và SQL2016 trên cả VM và Vật lý với RAM 128 GB, Lõi 12 x 2.2GHZ, Windows Server 2012 R2.

Kế hoạch thực hiện ước tính được tạo từ truy vấn có thể được tìm thấy ở đây:

Kế hoạch thực hiện dự kiến


1
Truy vấn có thể tạo ra một kế hoạch ước tính? (Ngoài ra, điều này sẽ không gây sốc cho nhiều người, nhưng dù sao tôi cũng khuyên bạn nên sử dụng phương pháp nâng cấp cũ - vì vậy, bạn MERGEkhông có HOLDLOCK, vì vậy nó không tránh khỏi các điều kiện chủng tộc, và vẫn còn những lỗi khác cần xem xét sau khi bạn giải quyết - hoặc báo cáo - bất cứ điều gì gây ra vấn đề này.)
Aaron Bertrand

1
Nó cung cấp một bãi chứa ngăn xếp với một vi phạm truy cập. Theo như tôi có thể thấy khi mở khóa ngăn xếp ở đây i.stack.imgur.com/f9aWa.png Bạn nên nâng cao điều này với Microsoft PSS nếu điều này gây ra vấn đề lớn cho bạn. Cụ thể có vẻ như deleted.ObjectIdđó là nguyên nhân gây ra vấn đề. OUTPUT $action, inserted.*, deleted.Name, deleted.LocationId, deleted.Regionhoạt động tốt
Martin Smith

1
Đồng tình với Martin. Trong thời gian chờ đợi, hãy xem liệu bạn có thể tránh được vấn đề không bằng cách sử dụng MySchema.PointTableloại và chỉ sử dụng VALUES()mệnh đề trần trụi hoặc bảng #temp hoặc biến bảng bên trong USING. Có thể giúp cô lập các yếu tố đóng góp.
Aaron Bertrand

Cảm ơn sự giúp đỡ của bạn, tôi đã thử sử dụng bảng tạm thời và xảy ra lỗi tương tự. Tôi sẽ nâng nó lên với sự hỗ trợ của sản phẩm - trong khi đó tôi viết lại truy vấn để không sử dụng hợp nhất để chúng tôi có thể tiếp tục chạy prod.
Mr.Brownstone

Câu trả lời:


20

Đây là một lỗi.

Nó có liên quan đến MERGEtối ưu hóa lấp đầy lỗ cụ thể được sử dụng để tránh Bảo vệ Halloween rõ ràng và loại bỏ sự tham gia cũng như cách các tương tác này với các tính năng kế hoạch cập nhật khác.

Có nhiều chi tiết về những tối ưu hóa trong bài viết của tôi, Vấn đề Halloween - Phần 3 .

Giveaway là Chèn theo sau là Hợp nhất trên cùng một bảng :

Kế hoạch mảnh

Cách giải quyết

Có một số cách để đánh bại tối ưu hóa này, và vì vậy tránh lỗi.

  1. Sử dụng cờ theo dõi không có giấy tờ để buộc Bảo vệ Halloween rõ ràng:

    OPTION (QUERYTRACEON 8692);
  2. Thay đổi ONmệnh đề thành:

    ON s."ObjectId" = t."ObjectId" + 0
  3. Thay đổi loại bảng PointTableđể thay thế khóa chính bằng:

    ObjectID bigint NULL UNIQUE CLUSTERED CHECK (ObjectId IS NOT NULL)

    Phần CHECKràng buộc là tùy chọn, được bao gồm để bảo toàn thuộc tính từ chối null gốc của khóa chính.

Xử lý truy vấn cập nhật 'Đơn giản' (kiểm tra khóa ngoài, bảo trì chỉ mục duy nhất và các cột đầu ra) đủ phức tạp để bắt đầu. Sử dụng MERGEthêm một số lớp bổ sung vào đó. Kết hợp điều đó với tối ưu hóa cụ thể được đề cập ở trên, và bạn có một cách tuyệt vời để gặp phải các lỗi trường hợp cạnh như thế này.

Thêm một để thêm vào hàng dài các lỗi đã được báo cáo MERGE.

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.