Tengo el siguiente procedimiento (SQL Server 2008 R2):
create procedure usp_SaveCompanyUserData
@companyId bigint,
@userId bigint,
@dataTable tt_CoUserdata readonly
as
begin
set nocount, xact_abort on;
merge CompanyUser with (holdlock) as r
using (
select
@companyId as CompanyId,
@userId as UserId,
MyKey,
MyValue
from @dataTable) as newData
on r.CompanyId = newData.CompanyId
and r.UserId = newData.UserId
and r.MyKey = newData.MyKey
when not matched then
insert (CompanyId, UserId, MyKey, MyValue) values
(@companyId, @userId, newData.MyKey, newData.MyValue);
end;
CompanyId, UserId, MyKey forman la clave compuesta para la tabla de destino. CompanyId es una clave externa para una tabla principal. Además, hay un índice no agrupado en CompanyId asc, UserId asc
.
Se llama desde muchos subprocesos diferentes, y constantemente recibo puntos muertos entre diferentes procesos que llaman a esta misma declaración. Comprendí que el "con (bloqueo)" era necesario para evitar errores de inserción / actualización de la condición de carrera.
Supongo que dos hilos diferentes están bloqueando filas (o páginas) en diferentes órdenes cuando están validando las restricciones, y por lo tanto están bloqueadas.
¿Es esta una suposición correcta?
¿Cuál es la mejor manera de resolver esta situación (es decir, sin puntos muertos, impacto mínimo en el rendimiento de subprocesos múltiples)?
(Si ve la imagen en una pestaña nueva, es legible. Perdón por el tamaño pequeño).
- Hay como máximo 28 filas en la @datatable.
- He rastreado el código, y no puedo ver en ningún lado que comencemos una transacción aquí.
- La clave externa está configurada para conectarse en cascada solo al eliminar, y no hubo eliminaciones de la tabla primaria.
No habría problema si la variable de tabla solo tuviera un valor. Con varias filas, hay una nueva posibilidad de punto muerto. Suponga que dos procesos concurrentes (A y B) se ejecutan con variables de tabla que contienen (1, 2) y (2, 1) para la misma compañía.
El proceso A lee el destino, no encuentra fila e inserta el valor '1'. Tiene un bloqueo de fila exclusivo en el valor '1'. El proceso B lee el destino, no encuentra fila e inserta el valor '2'. Tiene un bloqueo de fila exclusivo en el valor '2'.
Ahora el proceso A necesita procesar la fila 2, y el proceso B necesita procesar la fila 1. Ninguno de los procesos puede avanzar porque requiere un bloqueo que sea incompatible con el bloqueo exclusivo del otro proceso.
Para evitar puntos muertos con varias filas, las filas deben procesarse (y acceder a las tablas) en el mismo orden cada vez . La variable de tabla en el plan de ejecución que se muestra en la pregunta es un montón, por lo que las filas no tienen un orden intrínseco (es muy probable que se lean en orden de inserción, aunque esto no está garantizado):
La falta de un orden de procesamiento de fila consistente conduce directamente a la oportunidad de punto muerto. Una segunda consideración es que la falta de una garantía de singularidad clave significa que se necesita un carrete de mesa para proporcionar la protección correcta de Halloween. El spool es un spool ansioso, lo que significa que todas las filas se escriben en una tabla de trabajo tempdb antes de volver a leerse y reproducirse para el operador Insertar.
Redefiniendo la
TYPE
variable de la tabla para incluir un clústerPRIMARY KEY
:El plan de ejecución ahora muestra un escaneo del índice agrupado y la garantía de unicidad significa que el optimizador puede eliminar de forma segura el Table Spool:
En las pruebas con 5000 iteraciones de la
MERGE
declaración en 128 subprocesos, no se produjeron puntos muertos con la variable de tabla agrupada. Debo enfatizar que esto es solo en base a la observación; la variable de tabla en clúster también podría ( técnicamente ) producir sus filas en una variedad de órdenes, pero las posibilidades de un orden consistente son mucho mayores. El comportamiento observado tendría que volver a probarse para cada nueva actualización acumulativa, service pack o nueva versión de SQL Server, por supuesto.En caso de que la definición de la variable de la tabla no se pueda cambiar, hay otra alternativa:
Esto también logra la eliminación del carrete (y la consistencia del orden de las filas) a costa de introducir un tipo explícito:
Este plan tampoco produjo puntos muertos utilizando la misma prueba. Guión de reproducción a continuación:
fuente
Creo que SQL_Kiwi proporcionó un muy buen análisis. Si necesita resolver el problema en la base de datos, debe seguir su sugerencia. Por supuesto, debe volver a probar que todavía funciona para usted cada vez que actualice, aplique un paquete de servicio o agregue / cambie un índice o una vista indizada.
Hay otras tres alternativas:
Puede serializar sus insertos para que no choquen: puede invocar sp_getapplock al comienzo de su transacción y adquirir un bloqueo exclusivo antes de ejecutar su MERGE. Por supuesto, aún necesita hacer una prueba de esfuerzo.
Puede hacer que un hilo maneje todas sus inserciones, de modo que su servidor de aplicaciones maneje la concurrencia.
Puede volver a intentarlo automáticamente después de puntos muertos: este puede ser el enfoque más lento si la concurrencia es alta.
De cualquier manera, solo usted puede determinar el impacto de su solución en el rendimiento.
Por lo general, no tenemos puntos muertos en nuestro sistema, aunque tenemos mucho potencial para tenerlos. En 2011 cometimos un error en una implementación y tuvimos media docena de puntos muertos en unas pocas horas, todos siguiendo el mismo escenario. Lo arreglé pronto y eso fue todo el punto muerto del año.
Estamos utilizando principalmente el enfoque 1 en nuestro sistema. Funciona muy bien para nosotros.
fuente
Otro enfoque posible: he descubierto que Merge a veces presenta problemas de bloqueo y rendimiento, puede valer la pena jugar con la opción de consulta Opción (MaxDop x)
En el pasado oscuro y distante, SQL Server tenía una opción de bloqueo de nivel de inserción de fila, pero esto parece haber muerto, sin embargo, un PK agrupado con una identidad debería hacer que las inserciones se ejecuten sin problemas.
fuente