2008-11-03 18 views
14

Digamos que tengo un procedimiento almacenado simple que se parece a esto (nota: esto es sólo un ejemplo, no es un procedimiento práctico):¿La ejecución del procedimiento almacenado de T-SQL es "atómica"?

CREATE PROCEDURE incrementCounter AS 

DECLARE @current int 
SET @current = (select CounterColumn from MyTable) + 1 

UPDATE 
    MyTable 
SET 
    CounterColumn = current 
GO 

Estamos asumiendo que tengo una tabla llamada 'myTable' que contiene una fila, con el 'CounterColumn' que contiene nuestro recuento actual.

¿Se puede ejecutar este procedimiento almacenado varias veces, al mismo tiempo?

es decir, es esto posible:

que llamo 'incrementCounter' dos veces. La llamada A llega al punto donde establece la variable 'actual' (digamos que es 5). La llamada B llega al punto donde establece la variable 'actual' (que también sería 5). La llamada A finaliza la ejecución, luego finaliza la llamada B. Al final, la tabla debe contener el valor de 6, pero en su lugar contiene 5 debido a la superposición de la ejecución

+3

Este fenómeno se denomina "actualización perdida". Es el "aislamiento", no la atomicidad, lo que necesita observar. Debajo de leer por defecto el nivel de aislamiento comprometido ** es ** posible en contra de la implicación en la respuesta aceptada. –

+0

Una respuesta práctica a esto en SQL Server 2005 es: Use una tabla con una columna 'IDENTIDAD', inserte en la tabla y luego vuelva a leer el nuevo valor con' SCOPE_IDENTITY'. Entonces nunca tienes una colisión. –

Respuesta

12

Esto es para SQL Server.

Cada declaración es atómico, pero si desea que el procedimiento almacenado para ser atómica (o cualquier secuencia de sentencias en general), es necesario rodear de forma explícita las declaraciones con

BEGIN TRANSACTION
Declaración ...
Declaración ...
confirmar la transacción

(es común el uso de COMENZAR TRAN TRAN y FIN para abreviar).

por supuesto que hay muchas maneras de se mete en problemas de bloqueo dependiendo de lo que esté sucediendo al mismo tiempo, por lo que es posible que necesite una estrategia para tratar las transacciones fallidas. (Una discusión completa de todas las circunstancias que pueden resultar en bloqueos, sin importar cómo concibas este SP en particular, está más allá del alcance de la pregunta). Pero todavía serán reenviables debido a la atomicidad. Y en mi experiencia probablemente estarás bien, sin saber sobre tus volúmenes de transacciones y las otras actividades en la base de datos. Disculpe por decir lo obvio.

Contrariamente a una idea errónea popular, esto funcionará en su caso con la configuración predeterminada del nivel de transacción.

+2

La atomicidad no es una cura para los problemas de concurrencia y las condiciones de carrera. La modificación de OP muestra el procedimiento almacenado como sugirió NO tolerará la simultaneidad porque los bloqueos se adquieren y escalan con el tiempo. Si dos clientes, A y B ejecutan este procedimiento al mismo tiempo, puede obtener este patrón: A obtiene el bloqueo de lectura y lee la tabla, luego libera el bloqueo. B obtiene el bloqueo de lectura y lee ** el mismo valor de la tabla **, luego libera el bloqueo. A obtiene un bloqueo y actualizaciones de actualizaciones, luego B hace lo mismo. BLAM. Estas muerto. ¡Tienes que adquirir el bloqueo correcto más temprano para bloquearlo correctamente! – ErikE

+0

Como dice OP, es una pregunta teórica, no un procedimiento práctico; entonces respondí en sus propios términos. Hay más mal en este patrón de lo que mencionas, pero esa no era la pregunta. No entendí cómo hacer esto útil porque no deberíamos ir aquí en primer lugar. Si tienes una mejor respuesta, agrégala! – dkretz

+3

Dijo específicamente que quiere una cura. Tu respuesta no cura. Las respuestas incorrectas merecen votos abajo hasta que se vean lo suficientemente mal como para que otros no se engañen. Teórico o no, está incompleto. – ErikE

12

Además de colocar el código entre BEGIN TRANSACTION y END TRANSACTION, debe asegurarse de que su nivel de aislamiento de transacción esté configurado correctamente.

Por ejemplo, el nivel de aislamiento SERIALIZABLE evitará la pérdida de actualizaciones cuando el código se ejecute simultáneamente, pero READ COMMITTED (el predeterminado en SQL Server Management Studio) no lo hará.

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

Como otros ya han mencionado, y garantizar la coherencia, esto puede causar el bloqueo y bloqueo de puertas y así puede no ser la mejor solución en la práctica.

+0

'SERIALIZABLE' y' REPEATABLE READ' evitará esto, pero su explicación de por qué [no es correcto] (http: // dba.stackexchange.com/a/12718/3690) –

+2

@Dave, hice un poco más de investigación y encontré que su descripción es incorrecta. El nivel de aislamiento SERIALIZABLE no evitará que otros clientes lean el mismo valor. Lo que HARÁ si dos clientes leen en SERIALIZABLE, luego uno intenta actualizar es causa de un punto muerto y una conexión de cliente terminará con prejuicio. – ErikE

+0

También csn ralentiza las cosas, mucho. – dkretz

0

Tal vez estoy leyendo demasiado en su ejemplo (y su situación real puede ser significativamente más complicada), pero ¿por qué no haría esto en una sola declaración?

CREATE PROCEDURE incrementCounter AS 

UPDATE 
    MyTable 
SET 
    CounterColumn = CounterColumn + 1 

GO 

De esa manera, es automáticamente atómica y si dos actualizaciones se executued al mismo tiempo, siempre serán clasificadas por SQL Server a fin de evitar el conflicto que describes.Sin embargo, si su situación real es mucho más complicada, envolverla en una transacción es la mejor manera de hacerlo.

Sin embargo, si otro proceso ha habilitado un nivel de aislamiento "menos seguro" (como uno que permite lecturas sucias o no repetibles), entonces no creo que una transacción lo proteja, como puede ver otro proceso en los datos parcialmente actualizados si se elige para permitir lecturas inseguras.

+1

Es posible que necesite el valor anterior de la columna del contador, en cuyo caso todavía podría tomar este enfoque y obtenerlo utilizando la cláusula OUTPUT. –

+0

Algo sobre la necesidad del valor recién actualizado ... – ErikE

1

que utiliza este método

CREATE PROCEDURE incrementCounter 
AS 

DECLARE @current int 

UPDATE MyTable 
SET 
    @current = CounterColumn = CounterColumn + 1 

Return @current 

este procedimiento lo hacen todos los dos comandos a la vez y es aislar de otra transacción.

+0

No estoy seguro de que funcione. Quizás sí. Ser claramente un solo enunciado no es suficiente: http://sqlperformance.com/2014/07/t-sql-queries/isolation-levels –

0

La respuesta breve a su pregunta es SÍ, puede y va a faltar. Si desea bloquear la ejecución simultánea de procedimientos almacenados, inicie una transacción y actualice los mismos datos en cada ejecución del procedimiento almacenado antes de continuar haciendo cualquier trabajo dentro del procedimiento.

CREATE PROCEDURE .. 
BEGIN TRANSACTION 
UPDATE mylock SET ref = ref + 1 
... 

Esto obligará otras ejecuciones simultáneas que esperar su turno, ya que no será capaz de cambiar el valor 'ref' hasta que se levante la otra transacción (s) completo y bloqueo de actualización asociada.

En general, es una buena idea suponer que el resultado de cualquiera y todas las consultas SELECT están obsoletas antes de, incluso se ejecutan. Usar niveles de aislamiento "pesados" para solucionar esta desafortunada realidad limita seriamente la escalabilidad. Es mucho mejor estructurar los cambios de una manera que haga suposiciones optimistas sobre el estado del sistema que espera que exista durante la actualización, de modo que cuando su suposición falla, puede volver a intentarlo más tarde y esperar un mejor resultado. Por ejemplo:

UPDATE 
    MyTable 
SET 
    CounterColumn = current 
WHERE CounterColumn = current - 1 

Usando su ejemplo, con cláusula WHERE añadió esta actualización no afecta a ninguna fila si suposición acerca de su estado actual falla. Marque @@ ROWCOUNT para probar el número de filas y la reversión o alguna otra acción según corresponda mientras difiera del resultado esperado.

Cuestiones relacionadas