Digamos que tiene el siguiente código (ignore que es horrible):
BEGIN TRAN;
DECLARE @id int
SELECT @id = id + 1 FROM TableA;
UPDATE TableA SET id = @id; --TableA must have only one row, apparently!
COMMIT TRAN;
-- @id is returned to the client or used somewhere else
En mi opinión, esto NO está gestionando la concurrencia correctamente. El hecho de que tenga una transacción no significa que otra persona no leerá el mismo valor que usted antes de llegar a su estado de actualización.
Ahora, dejando el código como está (me doy cuenta de que esto se maneja mejor como una sola declaración o incluso mejor usando una columna de autoincremento / identidad), ¿cuáles son formas seguras de hacer que maneje la concurrencia correctamente y evite las condiciones de carrera que permiten que dos clientes obtengan lo mismo? valor de identificación?
Estoy bastante seguro de que agregar un WITH (UPDLOCK, HOLDLOCK)
a SELECT hará el truco. El nivel de aislamiento de la transacción SERIALIZABLE parece funcionar también, ya que niega a otra persona que lea lo que hizo hasta que termine el tran ( ACTUALIZACIÓN : esto es falso. Vea la respuesta de Martin). ¿Es eso cierto? ¿Funcionarán ambos igualmente bien? ¿Se prefiere uno sobre el otro?
Imagine hacer algo más legítimo que una actualización de ID: algunos cálculos basados en una lectura que necesita actualizar. Podría haber muchas tablas involucradas, algunas de las cuales escribirás y otras no. ¿Cuál es la mejor práctica aquí?
Después de escribir esta pregunta, creo que las sugerencias de bloqueo son mejores porque entonces solo está bloqueando las tablas que necesita, pero agradecería el aporte de cualquiera.
PD: ¡No, no sé la mejor respuesta y realmente quiero entenderlo mejor! :)
update
que pueden estar basados en datos obsoletos? Si es esto último, puede usar larowversion
columna para verificar si la fila a actualizar no ha cambiado desde que se leyó.Respuestas:
Solo aborda el
SERIALIZABLE
aspecto del nivel de aislamiento. Sí, esto funcionará pero con un riesgo de punto muerto.Dos transacciones podrán leer la fila simultáneamente. No se bloquearán entre sí, ya que tomarán un
S
bloqueo de objeto oRangeS-S
bloqueos de índice que dependen de la estructura de la tabla y estos bloqueos son compatibles . Pero se bloquearán entre sí cuando intenten adquirir los bloqueos necesarios para la actualización (IX
bloqueo de objetos o índice,RangeS-U
respectivamente), lo que conducirá a un punto muerto.El uso de una
UPDLOCK
sugerencia explícita en su lugar serializará las lecturas evitando así el riesgo de punto muerto.fuente
IX
enX
en el montón mismo. Curiosamente, ninguna fila califica, por lo que nunca se eliminan los bloqueos de fila. No estoy seguro de por qué toma laX
cerradura en absoluto.Creo que el mejor enfoque para usted sería exponer su módulo a una alta concurrencia y verlo por usted mismo. Algunas veces UPDLOCK solo es suficiente, y no hay necesidad de HOLDLOCK. A veces sp_getapplock funciona muy bien. No haría ninguna declaración general aquí; a veces, agregar un índice, desencadenador o vista indizada más cambia el resultado. Necesitamos enfatizar el código de prueba y ver por nosotros mismos caso por caso.
He escrito varios ejemplos de pruebas de estrés aquí.
Editar: para un mejor conocimiento de los aspectos internos, puede leer los libros de Kalen Delaney. Sin embargo, los libros pueden perder la sincronización al igual que cualquier otra documentación. Además, hay demasiadas combinaciones a considerar: seis niveles de aislamiento, muchos tipos de bloqueos, índices agrupados / no agrupados y quién sabe qué más. Eso es muchas combinaciones. Además de eso, SQL Server es de código cerrado, por lo que no podemos descargar el código fuente, depurarlo y demás, esa sería la última fuente de conocimiento. Cualquier otra cosa puede estar incompleta o desactualizada después de la próxima versión o paquete de servicio.
Por lo tanto, no debe decidir qué funciona para su sistema sin su propia prueba de esfuerzo. Lo que sea que haya leído, puede ayudarlo a comprender lo que está sucediendo, pero debe probar que el consejo que ha leído funciona para usted. No creo que nadie pueda hacerlo por ti.
fuente
En este caso particular, la adición de una
UPDLOCK
cerradura alSELECT
evitaría anomalías. La adición deHOLDLOCK
no es necesaria ya que se mantiene un bloqueo de actualización durante la transacción, pero confieso haberlo incluido yo mismo como un hábito (posiblemente malo) en el pasado.No hay mejores prácticas. Su elección del control de concurrencia debe basarse en los requisitos de la aplicación. Algunas aplicaciones / transacciones deben ejecutarse como si tuvieran la propiedad exclusiva de la base de datos, evitando a toda costa anomalías e inexactitudes. Otras aplicaciones / transacciones pueden tolerar cierto grado de interferencia entre sí.
Editar: el comentario de @ AlexKuznetsov me llevó a releer la pregunta y eliminar el error muy obvio en mi respuesta. Nota personal sobre la publicación nocturna.
fuente