![]() |
Problema concurrencia transacciones SQL SERVER
Buenas tardes,
Escribo este hilo, porque después de muchas horas investigando y leer bastantes hilos del foro, no doy con la solución. Os explico el problema: tengo una función que reserva el código de una tabla (mira el más alto y devuelve el siguiente), para después yo poder hacer una serie de operaciones y luego insertar un registro en esa tabla con ese código de reserva. Para reservar ese código, lo que hago es un INSERT en esa tabla con el resto de campos vacíos que después rellenaré, para tener la seguridad de tener ese código reservado. La función devuelve True si ha podido realizar la reserva correctamente o False si ha dado algún error. Y el problema viene ahora, y es que varias veces me ha pasado que la función me ha devuelto el mismo código de reserva que otra reserva realizada previamente. No lo entiendo, porque si hago la reserva con un INSERT, ¿cómo es que no ha dado una excepción? En cambio me ha devuelto un código repetido y a True. Es como si el SQL SERVER no se hubiese enterado del INSERT. También lo he probado con los diferentes IsolationLevel de la conexión (ilCursorStability, ilReadUncommitted, ilReadCommitted, etc...) pero con el mismo resultado. Os pongo el código por si veis algo que está mal. Agradecería cualquier ayuda, sugerencia, lo que sea... Muchas gracias!
|
No lo comentas (y es muy importante), pero imagino que tienes varios procesos/máquinas/aplicaciones pasando por ese punto. Si no es así lo siguiente olvídalo.
Yo creo que entre en SELECT y el INSERT, se te está colando un segundo proceso, de forma que antes de que el primer proceso haga el INSERT el segundo hace el SELECT (la consecuencia es que ambos obtienen el mismo número). Haría unas pruebas con una simple aplicación que haga muchas peticiones y que puedas lanzar manualmente muchas veces para sobrecargar el sistema. Si tienes ese problema, al hacer uso intensivo obtendrás ese error de forma más clara. En cuando al código de SELECT+INSERT yo usuaría IsolationLevel serializable (evita ejecuciones en paralelo, es decir es secuencial) ya que es la más restrictiva. Sólo debería usarse en casos muy puntuales y con sentencias SQL que tarden muy, muy poco tiempo (*NOTA1*). Utiliza el mismo componente ADOQuery para ambas sentencias y asegúrate de que están dentro de la misma transacción. *NOTA1*: Imagino que esa tabla tiene un índice único y rápido (clustered) por el campo CODIGO. Si ese SELECT tarda mucho vas a tener problemas de bloqueos. Si no es así busca otra manera, porque de siempre el MAX no ha sido la opción más rápida. |
Primero, esos rollback que tienes infiltrados muy sospechosos. Eso es definitivamente un error. Lo normal es que una transaccion encapsula las internas, en vez de retroceder en CADA intento.
Segundo, lo que describes es fácil de explicar:
La BD esta andando correcto, y este es tu algoritmo. No hay como decir que el "código" que usas es el ULTIMO código entre TODOS los participantes. Lo que tienes es "el ultimo código en ESTE INSTANTE". Es distinto! La forma correcta es que al MOMENTO de solicitar el código te "adueñas de el" y le avanzas el contador. Maso así: Cita:
La diferencia: Tu algoritmo Código:
max([1, 2, 3]) Código:
Actual = 1 |
Hola,
Yo también creo que lo mejor es tener un tabla de contadores separado para esta situación y para ejecutarlo podrias crear un stored procedure que haria todo el proceso en una sola llamada (sin transacciones):
y desde Delphi podrias hacer una llamada al store procedure parecido a esto
|
Muchas gracias por vuestra ayuda!! Le doy una vuelta a ver cómo lo enfoco, aunque la idea del StoredProcedure me parece interesante y puedo adaptarla fácilmente al código actual.
De todas formas, lo que no acabo de entender es por qué no da error (no salta la excepción) al intentar insertar un registro con la clave primaria duplicada. Gracias y saludos! |
Cita:
Ejemplo simplón: ID=1; Transacción 1: select ID -> 1 ; ID=5 Transacción 2: select ID -> 1 ; ID=3 Transacción 3: select ID -> 1 ; ID=28 ID sigue siendo 1 hasta que alguna transacción haga el commit; |
Cita:
|
Tienes que pensar en TIEMPO. Las operaciones se ejecutan en tiempos distintos. Aun una diferencia de nanosegundos es suficiente para que la cosa sea como te pasa.
En otras palabras: Las transacciones son confirmaciones DEL PASADO. Solo veras EL PASADO de forma CONSISTENTE. En el PRESENTE, todo esta asincrónico. Lo que hacen las transacciones es "converger" en un estado consistente al hacer "merge" de los diferentes estados de la bd hasta el momento de la transacción. |
La franja horaria es GMT +2. Ahora son las 09:03:48. |
Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi