Tengo la siguiente MERGE
declaración que se emite contra la base de datos:
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.*;
Sin embargo, esto hace que la sesión finalice con el siguiente error:
Mensaje 0, Nivel 11, Estado 0, Línea 67 Se produjo un error grave en el comando actual. Los resultados, si los hay, deben descartarse.
Mensaje 0, Nivel 20, Estado 0, Línea 67 Se produjo un error grave en el comando actual. Los resultados, si los hay, deben descartarse.
He creado un breve script de prueba que produce el error:
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.*;
Si elimino la OUTPUT
cláusula, no se produce el error. Además, si elimino la deleted
referencia, no se produce el error. Así que miré los documentos de MSDN para la OUTPUT
cláusula que dice:
DELETED no se puede usar con la cláusula OUTPUT en la instrucción INSERT.
Lo que tiene sentido para mí, sin embargo, el punto MERGE
es que quizás no lo sepas de antemano.
Además, el siguiente script funciona perfectamente bien independientemente de la acción que se realice:
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
Además, tengo otras consultas que usan la OUTPUT
misma manera que la que arroja un error y funcionan perfectamente bien: la única diferencia entre ellas son las tablas que participan en el MERGE
.
Esto nos está causando problemas importantes en la producción. He reproducido este error en SQL2014 y SQL2016 en VM y física con 128 GB de RAM, 12 núcleos de 2,2 GHz, Windows Server 2012 R2.
El plan de ejecución estimado generado a partir de la consulta se puede encontrar aquí:
fuente
MERGE
no es asíHOLDLOCK
, por lo que no es inmune a las condiciones de carrera, y aún hay otros errores que considerar incluso después de resolver - o el informe -. lo que está causando este problema)deleted.ObjectId
que está causando el problema.OUTPUT $action, inserted.*, deleted.Name, deleted.LocationId, deleted.Region
funciona bien.MySchema.PointTable
tipo, y simplemente usando unaVALUES()
cláusula desnuda , o #temp table, o table variable, dentro deUSING
. Podría ayudar a aislar los factores contribuyentes.Respuestas:
Esto es un error
Está relacionado con
MERGE
optimizaciones específicas para rellenar huecos que se utilizan para evitar la protección explícita de Halloween y eliminar una unión, y cómo interactúan con otras características del plan de actualización.Hay detalles sobre esas optimizaciones en mi artículo, The Halloween Problem - Part 3 .
El sorteo es la inserción seguida de una combinación en la misma tabla :
Soluciones alternativas
Hay varias formas de vencer esta optimización, y así evitar el error.
Use una marca de rastreo indocumentada para forzar la protección explícita de Halloween:
Cambie la
ON
cláusula a:Cambie el tipo de tabla
PointTable
para reemplazar la clave primaria con:La
CHECK
parte de restricción es opcional, incluida para preservar la propiedad original de rechazo nulo de una clave primaria.El procesamiento de consultas de actualización 'simple' (comprobaciones de clave externa, mantenimiento de índice único y columnas de salida) es lo suficientemente complejo para empezar. El uso
MERGE
agrega varias capas adicionales a eso. Combine eso con la optimización específica mencionada anteriormente, y tiene una excelente manera de encontrar errores de borde de caso como este.Uno más para agregar a la larga línea de errores con los que se ha informado
MERGE
.fuente