En una de nuestras bases de datos tenemos una tabla a la que se accede de manera simultánea de manera simultánea por múltiples hilos. Los hilos actualizan o insertan filas a través de MERGE. También hay hilos que eliminan filas ocasionalmente, por lo que los datos de la tabla son muy volátiles. Los subprocesos que hacen upserts a veces sufren de interbloqueo. El problema es similar al descrito en esta pregunta. La diferencia, sin embargo, es que en nuestro caso cada hilo actualiza o inserta exactamente una fila .
La configuración simplificada está siguiendo. La tabla es un montón con dos índices únicos no agrupados sobre
CREATE TABLE [Cache]
(
[UID] uniqueidentifier NOT NULL CONSTRAINT DF_Cache_UID DEFAULT (newid()),
[ItemKey] varchar(200) NOT NULL,
[FileName] nvarchar(255) NOT NULL,
[Expires] datetime2(2) NOT NULL,
CONSTRAINT [PK_Cache] PRIMARY KEY NONCLUSTERED ([UID])
)
GO
CREATE UNIQUE INDEX IX_Cache ON [Cache] ([ItemKey]);
GO
y la consulta típica es
DECLARE
@itemKey varchar(200) = 'Item_0F3C43A6A6A14255B2EA977EA730EDF2',
@fileName nvarchar(255) = 'File_0F3C43A6A6A14255B2EA977EA730EDF2.dat';
MERGE INTO [Cache] WITH (HOLDLOCK) T
USING (
VALUES (@itemKey, @fileName, dateadd(minute, 10, sysdatetime()))
) S(ItemKey, FileName, Expires)
ON T.ItemKey = S.ItemKey
WHEN MATCHED THEN
UPDATE
SET
T.FileName = S.FileName,
T.Expires = S.Expires
WHEN NOT MATCHED THEN
INSERT (ItemKey, FileName, Expires)
VALUES (S.ItemKey, S.FileName, S.Expires)
OUTPUT deleted.FileName;
es decir, la coincidencia ocurre por clave de índice única. La sugerencia HOLDLOCKestá aquí, debido a la concurrencia (como se recomienda aquí ).
Hice una pequeña investigación y lo siguiente es lo que encontré.
En la mayoría de los casos, el plan de ejecución de consultas es
con el siguiente patrón de bloqueo
es decir, IXbloquear el objeto seguido de bloqueos más granulares.
A veces, sin embargo, el plan de ejecución de consultas es diferente.
(esta forma de plan se puede forzar agregando una INDEX(0)pista) y su patrón de bloqueo es
observe el Xbloqueo colocado en el objeto después de IXque ya esté colocado.
Como dos IXson compatibles, pero dos Xno, lo que sucede bajo concurrencia es
punto muerto !
Y aquí surge la primera parte de la pregunta . ¿Colocar el Xcandado en el objeto después de ser IXelegible? ¿No es un error?
Documentación declara:
Los bloqueos de intención se denominan bloqueos de intención porque se adquieren antes de un bloqueo en el nivel inferior y, por lo tanto, indican la intención de colocar bloqueos en un nivel inferior .
y tambien
IX significa la intención de actualizar solo algunas de las filas en lugar de todas
Por lo tanto, colocar el Xcandado en el objeto después me IXparece MUY sospechoso.
Primero intenté evitar el bloqueo al intentar agregar sugerencias de bloqueo de tabla
MERGE INTO [Cache] WITH (HOLDLOCK, TABLOCK) T
y
MERGE INTO [Cache] WITH (HOLDLOCK, TABLOCKX) T
con el TABLOCKpatrón de bloqueo en su lugar se convierte
y con el TABLOCKXpatrón de bloqueo es
Dado que dos SIX(así como dos X) no son compatibles, esto evita un punto muerto efectivo, pero, desafortunadamente, también evita la concurrencia (lo cual no es deseable).
Mis próximos intentos fueron agregar PAGLOCKy ROWLOCKhacer bloqueos más granulares y reducir la contención. Ambos no tienen ningún efecto ( Xen el objeto todavía se observó inmediatamente después IX).
Mi intento final fue forzar una "buena" forma de plan de ejecución con un buen bloqueo granular al agregar una FORCESEEKpista
MERGE INTO [Cache] WITH (HOLDLOCK, FORCESEEK(IX_Cache(ItemKey))) T
Y funcionó.
Y aquí surge la segunda parte de la pregunta . ¿Podría suceder que FORCESEEKse ignorará y se utilizará un patrón de bloqueo incorrecto? (Como mencioné, PAGLOCKy ROWLOCKaparentemente fueron ignorados).
Agregar UPDLOCKno tiene ningún efecto ( Xen el objeto aún observable después IX).
Hacer el IX_Cacheíndice agrupado, como se anticipó, funcionó. Condujo al plan con Clustered Index Seek y bloqueo granular. Además, intenté forzar el Análisis de índice agrupado que también mostraba bloqueo granular.
Sin embargo. Observación adicional En la configuración original incluso con el FORCESEEK(IX_Cache(ItemKey)))lugar, si una @itemKeydeclaración de variable de cambio de varchar (200) a nvarchar (200) , el plan de ejecución se convierte en
vea que se utiliza la búsqueda, PERO el patrón de bloqueo en este caso nuevamente muestra el Xbloqueo colocado en el objeto después IX.
Entonces, parece que forzar la búsqueda no necesariamente garantiza bloqueos granulares (y la ausencia de puntos muertos por lo tanto). No estoy seguro de que tener un índice agrupado garantice el bloqueo granular. O lo hace?
Mi comprensión (corríjame si me equivoco) es que el bloqueo es situacional en gran medida, y cierta forma de plan de ejecución no implica cierto patrón de bloqueo.
La pregunta sobre la elegibilidad de colocar un Xcandado en el objeto después de IXabrir. Y si es elegible, ¿hay algo que uno pueda hacer para evitar el bloqueo de objetos?










