Saya memiliki MERGE
pernyataan 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 OUTPUT
klausa maka kesalahan tidak terjadi. Juga, jika saya menghapus deleted
referensi maka kesalahan tidak terjadi. Jadi saya melihat dokumen MSDN untuk OUTPUT
klausa yang menyatakan:
DIHAPUS tidak dapat digunakan dengan klausa OUTPUT dalam pernyataan INSERT.
Yang masuk akal bagi saya, namun intinya MERGE
adalah 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 OUTPUT
cara 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:
sumber
MERGE
tidak memilikinyaHOLDLOCK
, 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.)deleted.ObjectId
itulah yang menyebabkan masalah.OUTPUT $action, inserted.*, deleted.Name, deleted.LocationId, deleted.Region
bekerja dengan baik.MySchema.PointTable
tipe, dan hanya menggunakanVALUES()
klausa telanjang , atau tabel #temp, atau variabel tabel, di dalamUSING
. Dapat membantu mengisolasi faktor-faktor yang berkontribusi.Jawaban:
Ini adalah bug.
Ini terkait dengan
MERGE
optimisasi 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 :
Penanganan masalah
Ada beberapa cara untuk mengalahkan optimasi ini, dan karenanya hindari bug.
Gunakan bendera jejak yang tidak berdokumen untuk memaksa Perlindungan Halloween secara eksplisit:
Ubah
ON
klausa menjadi:Ubah jenis tabel
PointTable
untuk mengganti kunci utama dengan:Bagian
CHECK
kendala 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
MERGE
menambahkan 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
.sumber