Mengapa pernyataan MERGE ini menyebabkan sesi ini terbunuh?

23

Saya memiliki MERGEpernyataan di bawah ini yang dikeluarkan terhadap database:

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.*;

Namun, ini menyebabkan sesi diakhiri dengan kesalahan berikut:

Msg 0, Level 11, Negara 0, Baris 67 Terjadi kesalahan parah pada perintah saat ini. Hasilnya, jika ada, harus dibuang.

Msg 0, Level 20, Negara 0, Baris 67 Terjadi kesalahan parah pada perintah saat ini. Hasilnya, jika ada, harus dibuang.

Saya telah menggabungkan skrip pengujian singkat yang menghasilkan kesalahan:

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.*;

Jika saya menghapus OUTPUTklausa maka kesalahan tidak terjadi. Juga, jika saya menghapus deletedreferensi maka kesalahan tidak terjadi. Jadi saya melihat dokumen MSDN untuk OUTPUTklausa yang menyatakan:

DIHAPUS tidak dapat digunakan dengan klausa OUTPUT dalam pernyataan INSERT.

Yang masuk akal bagi saya, namun intinya MERGEadalah bahwa Anda mungkin tidak tahu sebelumnya.

Selain itu, skrip di bawah ini berfungsi dengan baik terlepas dari tindakan yang diambil:

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 

Juga, saya punya pertanyaan lain yang menggunakan dengan OUTPUTcara yang sama dengan yang melemparkan kesalahan dan mereka bekerja dengan baik - satu-satunya perbedaan di antara mereka adalah tabel yang mengambil bagian dalam MERGE.

Ini menyebabkan masalah besar dalam produksi bagi kami. Saya telah mereproduksi kesalahan ini dalam SQL2014 dan SQL2016 pada VM dan Fisik dengan 128GB RAM, 12 x 2.2GHz Core, Windows Server 2012 R2.

Perkiraan rencana eksekusi yang dihasilkan dari kueri dapat ditemukan di sini:

Perkiraan Rencana Eksekusi

Mr.Brownstone
sumber
1
Bisakah kueri menghasilkan estimasi rencana? (Juga, ini tidak akan mengejutkan banyak orang, tapi saya tetap merekomendasikan metodologi upert yang lama - Anda MERGEtidak memilikinya HOLDLOCK, misalnya, jadi tidak kebal dari kondisi ras, dan masih ada bug lain yang perlu dipertimbangkan bahkan setelah Anda menyelesaikan - atau melaporkan - apa pun yang menyebabkan masalah ini.)
Aaron Bertrand
1
Ini memberikan stack stack dengan pelanggaran akses. Sejauh yang saya bisa lihat ketika membuka tumpukan tumpukan di sini i.stack.imgur.com/f9aWa.png Anda harus meningkatkan ini dengan Microsoft PSS jika ini menyebabkan masalah besar bagi Anda. Secara khusus tampaknya deleted.ObjectIditulah yang menyebabkan masalah. OUTPUT $action, inserted.*, deleted.Name, deleted.LocationId, deleted.Regionbekerja dengan baik.
Martin Smith
1
Setuju dengan Martin. Sementara itu, lihat apakah Anda dapat menghindari masalah dengan tidak menggunakan MySchema.PointTabletipe, dan hanya menggunakan VALUES()klausa telanjang , atau tabel #temp, atau variabel tabel, di dalam USING. Dapat membantu mengisolasi faktor-faktor yang berkontribusi.
Aaron Bertrand
Terima kasih atas bantuan kalian, saya mencoba menggunakan tabel temp dan kesalahan yang sama terjadi. Saya akan meningkatkannya dengan dukungan produk - sementara itu saya menulis ulang kueri untuk tidak menggunakan penggabungan sehingga kami dapat terus menjalankannya.
Mr.Brownstone

Jawaban:

20

Ini adalah bug.

Ini terkait dengan MERGEoptimisasi pengisian lubang spesifik yang digunakan untuk menghindari Proteksi Halloween yang eksplisit dan untuk menghilangkan gabungan, dan bagaimana interaksi ini dengan fitur rencana pembaruan lainnya.

Ada detail tentang optimasi itu di artikel saya, The Halloween Problem - Bagian 3 .

Hadiahnya adalah Sisipan diikuti oleh Gabung di tabel yang sama :

Rencanakan fragmen

Penanganan masalah

Ada beberapa cara untuk mengalahkan optimasi ini, dan karenanya hindari bug.

  1. Gunakan bendera jejak yang tidak berdokumen untuk memaksa Perlindungan Halloween secara eksplisit:

    OPTION (QUERYTRACEON 8692);
  2. Ubah ONklausa menjadi:

    ON s."ObjectId" = t."ObjectId" + 0
  3. Ubah jenis tabel PointTableuntuk mengganti kunci utama dengan:

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

    Bagian CHECKkendala adalah opsional, termasuk untuk mempertahankan properti asli yang menolak null dari kunci primer.

Pemrosesan permintaan pembaruan 'sederhana' (pemeriksaan kunci asing, pemeliharaan indeks unik, dan kolom output) cukup rumit untuk memulai. Menggunakan MERGEmenambahkan beberapa lapisan tambahan untuk itu. Gabungkan dengan optimasi spesifik yang disebutkan di atas, dan Anda memiliki cara yang bagus untuk menemukan bug tepi-kasus seperti ini.

Satu lagi untuk ditambahkan ke garis panjang bug yang telah dilaporkan MERGE.

Paul White mengatakan GoFundMonica
sumber