Transacciones y bloqueos

Anuncio
www.trucosaxapta.com
Transacciones y bloqueos en SQL-Server
(Información para el uso desde Axapta)
Introducción
En este documento vamos a intentar explicar cuatro conceptos básicos acerca de las
transacciones y los bloqueos en SQL-Server y lo más importante: como los
evita/gestiona Axapta cuando puede. Y cuando no puede… algún truquillo para
ayudarle.
Transacciones
Si a mitad de ejecutar un proceso se produce un error, no podemos asegurar la
integridad de los datos actualizados por nuestra aplicación. Es decir, si teníamos que
actualizar 500 registros de la Base de datos y se ha producido un error cuando habíamos
procesado tan solo 200… los otros 300 quedan sin actualizar. En muchos ocasiones esto
no es para nada deseable (imaginemos una facturación, se nos produce un error a mitad
de procesar los pedidos incluidos en una factura y quedan pedidos marcados como
facturados y otros que no y la factura podría no haberse completado).
Para evitar este problema existen las transacciones.
Una transacción es un sistema que utilizan las BBDD para saber cuando un proceso
termina satisfactoriamente. Podríamos decir que es un todo o nada. El tema consiste en
que nuestro proceso realiza las actualizaciones que necesita en la base de datos y cuando
ha terminado todo el proceso le indica al SQL que todo lo que ha realizado puede “darlo
por bueno”. Por el contrario si hay algún problema, la base de datos dispone de un
mecanismo que le permite “anular” todos los cambios realizados en los datos durante
ese proceso.
En Axapta, para saber que actualizaciones de la BBDD pertenecen al mismo “lote”
deberemos indicar donde empieza y termina cada transacción:
ttsbegin; // principio de la transacción
// instrucciones de actualización
ttscommit; // final de la transacción
www.trucosaxapta.com
Si no se produce ningún error, se ejecutará la instrucción de fin de transacción y la
BBDD guardará las actualizaciones que se han realizado dentro de la transacción. En
cambio, si existiese un error se produciría un RollBack que consiste en anular todas las
actualizaciones realizadas desde el inicio de la transacción. El famoso RollBack se
ejecuta automáticamente en caso de producirse una excepción, pero también podemos
ejecutarlo de forma intencionada mediante la instrucción ttsabort.
Por supuesto podemos anidar transacciones de manera que se realizará el commit real
en la base de datos cuando lleguemos al ttscommit de la transacción de nivel superior.
ttsbegin;
……..
…….
ttsbegin;
……
……
ttscommit; // Aquí aun no se realiza el commit realmente
…..
….
ttscommit; // Ahora si :)
A medida que el interprete de Axapta va encontrando instrucciones ttsbegin va
incrementando la variable global ttslevel y por cada ttscommit lo decrementa. Axapta
comunica a la base de datos el commit real cuando ttslevel llega a 0.
Para los más curiosos tan solo decir que además podéis echar un vistazo a la clase
Application, métodos ttsNotifyBegin, ttsNotifyAbort y ttsNotifyCommit. Estas se
llaman cada vez que ejecutamos un ttsbegin, ttsabort o ttscommit respectivamente para
que Axapta realice una serie de “notificaciones” a dos sistemas de transacciones
especiales de que dispone este ERP.
Bloqueos
Cuando se esta ejecutando simultáneamente mas de un proceso, a veces sucede que los
dos intentan modificar el mismo registro.
www.trucosaxapta.com
Debido al uso de transacciones el sistema puede encontrarse con el problema de que un
proceso (A) intenta modificar o leer un registro que ha sido modificado por otro proceso
(B) cuya transacción no ha finalizado. El problema está en que podría darse el caso de
que esa transacción no terminara de forma satisfactoria (commit) sino que tuvieran que
anularse los cambios producidos en ese registro. ¿Que se supone que debería hacer SQL
cuando intentamos leer ese registro?
¿Mostrar el valor anterior a la actualización producida por el proceso que está en curso
(B)?
¿Mostrar el valor modificado por el proceso (B)?
En cualquiera de los 2 casos, y si falla el proceso (B), ¿que valor dejamos en el registro
cuando realicemos el rollback?
¿Dejamos el valor anterior a la actualización producida por el proceso (B) a riesgo de
que el proceso (A) haya modificado también este registro y machaquemos sus cambios?
Imaginemos que se trata de un acumulado (del stock por ejemplo): El stock era 5, el
proceso (B) ha restado una unidad y lo ha dejado a 4. El proceso (A) quería restar 2
unidades y basándose en el 4 lo ha dejado a 2. Si falla el proceso (B) y restaura el valor
a 5… acabamos de destrozar el acumulado.
Para solucionar este problema Sql-Server generará un bloqueo en el momento en que el
proceso (B) modifique el registro de manera que el proceso (A) esperará a que (B)
termine (bien o mal) para poder continuar. De esta forma nos aseguramos de que el
proceso (A) se basará en datos correctos y no pondremos en peligro la consistencia de
los mismos.
DeadLocks
Imaginemos que el proceso (A) bloquea el registro nº1, el proceso (B) bloquea el
registro nº2. Ahora el proceso (A) que prosigue su trabajo se dispone a modificar el
registro nº2, pero encuentra que esta bloqueado por el proceso (B). A su vez, el proceso
(B) (por aquellas cosas del destino y de Murphy) se dispone a proseguir su trabajo pero
necesita modificar el registro nº1, que… ah! Sorpresa! Está bloqueado por el proceso
(A). En resumen : El proceso (A) está esperando a que finalice el proceso (B) para
poder modificar el registro nº 2 y el proceso (B) está esperando a que termine el proceso
(A) para poder modificar el registro nº1. Estamos frente a lo que se denomina
DeadLock o lo que es lo mismo… un bloqueo sin solución.
Normalmente Sql-Server detectará el problema, elegirá uno de los dos procesos y lo
cancelará. A veces no es capaz de detectarlo y se quedan bloqueados hasta que el
usuario, el administrador o alguna otra circunstancia cancele uno de los procesos.
Una posible solución sería limitar el tiempo de espera para un bloqueo mediante el
parámetro de SQL Lock_Timeout. El problema que nos podría ocasionar es que en el
www.trucosaxapta.com
caso que no se tratase de un DeadLock si no de una espera un poco larga pero con final
feliz, también generaría un error y cancelaría la transacción.
Si nos fijamos, veremos que para evitar que dos procesos tengan que modificar el
mismo registro, en Axapta casi no existen acumulados y contadores (secuencias
numéricas), y decimos casi porque hay casos en los que no tiene mas remedio que
usarlos, pero en estos casos tienen una gestión especial para evitar DeadLocks.
Stocks
Los stocks de inventario son un caso claro de acumulados. A partir de la Versión 3.0 de
Axapta se ha añadido una funcionalidad llamada "transacciones múltiples de
Inventario". Cuando está activada en lugar de acumular los movimientos de inventario
directamente en el stock (tabla InventSum), los guarda en una tabla como operaciones
pendientes de aplicar. Si la transacción termina correctamente realiza la actualización
del stock en los acumulados correspondientes.
ilustración 1: En el menú “Administración”,” Configurar”,” Sistema” se encuentra esta opción que
permite activar o desactivar esta funcionalidad.
www.trucosaxapta.com
Secuencias Numéricas
Para evitar los bloqueos, Axapta actualiza los contadores creando una transacción en
una conexión independiente, de esta forma no bloquea la secuencia numérica hasta que
termina todo el proceso (sería un caos). Por este motivo si una secuencia numérica es de
tipo continua (que no debe “perder” números) el sistema almacena en una tabla a parte
una lista de números pendientes de determinar si se han asignado definitivamente o no.
Si la transacción finaliza correctamente estos números quedarán marcados como
asignados.
Escalado de Bloqueos
SQL-Server mantiene una lista de los registros que se encuentran bloqueados en
cualquier instante por todos los procesos que hay en ejecución. Esto puede consumir
bastante memoria.
Para evitar el consumo excesivo de memoria, Sql-Server dispone de un mecanismo
llamado “Escalado de bloqueos”. Este consiste en que en un momento determinado,
SQL decide que si hay muchos registros bloqueados de una tabla, puede cambiar la lista
de referencias a cada uno de esos registros por una sola referencia a nivel de página o
bien a nivel de tabla según el caso. Realmente puede ir escalando esta referencia incluso
hasta llegar a nivel de Base de datos. Esto supone que puede bloquear toda una página o
toda una tabla o toda la base de datos, lo que implica que por ahorrar consumo de
memoria puede bloquear registros que no necesitaban ser bloqueados ya que no se han
actualizado pero por desgracia se encontraban en una de las páginas que ha decidido
www.trucosaxapta.com
bloquear o en la tabla… etc.(Hay que señalar que el caso de bloqueo de Base de datos es
un caso muy extremo y no suele darse)
Este sistema a veces produce resultados indeseados. Por ejemplo en Axapta la tabla de
pedidos de venta es común para todas las empresas. Por lo tanto si una de ellas estuviese
realizando un proceso de facturación, sería posible que las demás no pudiesen modificar
pedidos. Esto puede suceder si se tratan de facturas con muchas líneas, ya que Axapta
crea una transacción para cada factura.
Para evitar este tipo de bloqueos lo mejor es realizar transacciones pequeñas. Siempre y
cuando sea posible (que en muchas ocasiones no lo es).
Esto tiene truco
Podemos desactivar el escalado de bloqueos en SQL-Server, existe un parámetro para
hacerlo. Para introducirlo iremos a las propiedades de Sql-Server, solapa "General",
Botón "Parámetros de Inicio", y añadimos el parámetro "–T1211".
De todas formas debemos ser conscientes de lo que esto supone, estamos privando al
servidor de esta funcionalidad, mediante la cual consigue liberar memoria cuando la
necesita, lo que podría degradar el rendimiento. Aunque en sistemas con muchos
usuarios y transacciones simultaneas… puede acabar siendo necesario.
También parece ser que es mejor disponer de un índice con clave única para que SqlServer pueda hacer referencia a los registros bloqueados. Si no es posible definir una
clave única, sería buena solución crear un índice por el "RecId".
Descargar