Cuando estemos trabajando con la actualización de datos, es necesario que el sistema realice una serie de bloqueos de los registros con los que estamos trabajando para asegurar que las transacciones se procesan de forma precisa y con un alto nivel de concurrencia, pero cuantos más registros y transacciones se encuentren bloqueadas, peor será el rendimiento de la base de datos.
Con Dynamics AX y SQL Server tenemos dos modelos de concurrencia para controlar los bloqueos de los registros.
- Control de concurrencia pesimista: Bloquea los registros desde el momento en el que los obtiene de la base de datos, hasta el momento de la actualización
- Control de concurrencia optimista: Solo bloquea los registros en el momento en el que se realiza la actualización real.
- Las ventajas de utilizar este segundo modelo son que, los registros permanecen bloqueados menos tiempo, lo que nos lleva a necesitar menos recursos para mantener los bloqueos durante el proceso de actualización, y que los registros permanezcan disponibles para otros procesos si estos han sido seleccionados, pero todavía no actualizados. Todo esto hace que mejore el rendimiento de la base de datos.
La desventaja que tiene este modelo de concurrencia, es claramente, que un usuario puede lanzar una actualización de un registro que había sido previamente seleccionado por otro usuario, lo que puede causar inconsistencia en los datos, porque, imaginemos:
El usuario 1, selecciona el registro del cliente 1, seguidamente, el usuario 2 selecciona el mismo registro. Esto nos lo permite el sistema el sistema puesto que todavía no estaría bloqueado. Ahora, el usuario 1, cambia un dato de ese registro, y actualiza, y seguidamente, el usuario 2 cambia otro dato del mismo registro y actualiza. Lo que tendríamos es que el usuario 2 está machacando las modificaciones que ha realizado el usuario 1.
Para evitar esta inconsistencia, lo que ocurre es que, el sistema lanzará cuando detecte este tipo de cambio, una excepción de conflicto de actualización.
Para ello lo que hace el sistema, es comparar el valor del campo RecVersion, que como sabemos, cambia su valor automáticamente siempre que se ejecuta un update.
De este modo, cuando va a actualizar, si el valor del RecVersion que tenemos en el registro difiere del valor real que tiene el RecVersion dentro de la base de datos, significa que ha habido una actualización desde que obtuvimos el registro de la base de datos hasta el momento en el que intentamos actualizar, por lo que el sistema lanzará una excepción de conflicto de actualización, y lo que hay que hacer es reintentar la ejecución del código o informar al usuario de los pasos a seguir.
Para que una tabla trabaje con el modelo de concurrencia optimista, la tabla debe tener la siguiente propiedad activada:
O bien indicárselo por código eligiendo una de las dos formas posibles:
1 2 3 4 5 |
// Método concurrencyModel desde el buffer tabla.concurrencyModel(ConcurrencyModel::Optimistic); // Propiedad optimisticLock en la sentencia select select optimisticLock firstOnly tabla; |
Aquí podemos ver un ejemplo de manejo de excepciones de conflicto de actualización desde X++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#OCCRetryCount CustTable custTable; try { ttsbegin; // Ejecución de código ttsCommit; } catch (Exception::Deadlock) { // retry si ocurre un deadlock retry; } catch (Exception::UpdateConflict) { // intentar resolver el conflicto if (appl.ttsLevel() == 0) { if (xSession::currentRetryCount() >= #RetryNum) { throw Exception::UpdateConflictNotRecovered; } else { retry; } } else { throw Exception::UpdateConflict; } } |
Happy DAXing!!
Muy bueno tu artículo, es algo que desconocía completamente.
Sin embargo, tengo una duda, si modifico el valor de ese registro usando el examinador de tablas y luego cierro y me voy a realizar un proceso con ese registro y hago select forUpdate ¿por que me sale el error de «Ha ocurrido un error de actualización»? y se que nadie está tocando el registro
Buenas Eduardo, muchas gracias por el comentario :).
Esto que me dices, es algo que te ocurre siempre que haces esa operación? En principio no tiene sentido que ocurra puesto que se está actualizado en transacciones separadas en el tiempo…
Has probado a incluir esa operación dentro de un try/catch para intentar recibir más información acerca del origen?
Un saludo!