Ir al contenido


Foto

[RESUELTO] Deadlock actualizando con Firebird


  • Por favor identifícate para responder
4 respuestas en este tema

#1 enecumene

enecumene

    Webmaster

  • Administrador
  • 7.419 mensajes
  • LocationRepública Dominicana

Escrito 13 marzo 2010 - 05:00

Pues eso estoy obteniendo un error al intentar actualizar datos con Firebird + ZEOS, aquí el code completo:



delphi
  1. procedure TFAstGen.btn1Click(Sender: TObject);
  2. var
  3.   i,e,j,k: integer;
  4.   FechaIni,FechaFin,EventIni,EventFin: Tdatetime;
  5.   Fecha: string;
  6. begin
  7.  
  8. //Obtenemos el log completo
  9. QGetLogs.Close;
  10. QGetLogs.SQL.Clear;
  11. QGetLogs.SQL.Text := 'SELECT * FROM LOG_ASISTENCIA';
  12. QGetLogs.Open;
  13.  
  14. if QGetLogs.RecordCount > 0 then begin
  15.  
  16.   {*** REGISTRAMOS TODAS LAS FECHAS A PARTIR DE UN RANGO DE FECHA ***}
  17.   ShortDateFormat := 'dd/mm/yyyy';
  18.   fechaIni := Primero;
  19.   fechaFin := Ultimo;
  20.  
  21.   //Obtenemos los empleados activos
  22.   QGetEmp.Close;
  23.   QGetEmp.SQL.Clear;
  24.   QGetEmp.SQL.Text := 'SELECT * FROM EMPLEADOS WHERE EMP_STATUS = 3';
  25.   QGetEmp.Open;
  26.  
  27.   while not QGetEmp.eof do
  28.   begin
  29.     e := QGetEmp.fieldbyname('EMP_ID').Value;
  30.     for i := trunc(fechaIni) to trunc(FechaFin) do
  31.     begin
  32.       QGetDates.Close;
  33.       QGetDates.SQL.Clear;
  34.       QGetDates.SQL.Text := 'INSERT INTO ASISTENCIA(EMP_ID, AST_FECHA) VALUES(:EMP,:FECHA)';
  35.       QGetDates.ParamByName('EMP').AsInteger := e;
  36.       QGetDates.ParamByName('FECHA').AsDate := i;
  37.       QGetDates.ExecSQL;
  38.     end;
  39.       QGetEmp.Next;
  40.   end;
  41. {*** FIN ***}
  42.  
  43. {*** LLENAMOS LAS ENTRADAS Y SALIDAS ***}
  44. while not QGetLogs.Eof do begin
  45.     if QGetLogs.FieldByName('LOG_STATUS').AsInteger = 1 then begin
  46.       QGetLogData.Close;
  47.       QGetLogData.SQL.Clear;
  48.       QGetLogData.SQL.Text := 'UPDATE ASISTENCIA SET AST_ENT = :ENT WHERE EMP_ID = :EMP AND AST_FECHA = :FECHA';
  49.       QGetLogData.ParamByName('ENT').Value := QGetLogs.FieldByName('LOG_TIME').Value;
  50.       QGetLogData.ParamByName('EMP').Value := QGetLogs.FieldByName('EMP_ID').AsInteger;
  51.       QGetLogData.ParamByName('FECHA').Value := QGetLogs.FieldByName('LOG_FECHA').Value;
  52.       QGetLogData.ExecSQL;
  53.     end else if QGetLogs.FieldByName('LOG_STATUS').AsInteger <> 1 then begin
  54.       //Obtenemos el último registro de salida
  55.       QGetRows.Close;
  56.       QGetRows.SQL.Clear;
  57.       QGetRows.SQL.Text := 'SELECT MAX(LOG_TIME) FROM LOG_ASISTENCIA WHERE EMP_ID = :EMP AND LOG_FECHA = :FECHA';
  58.       QGetRows.ParamByName('EMP').Value := QGetLogs.FieldByName('EMP_ID').AsInteger;
  59.       QGetRows.ParamByName('FECHA').Value := QGetLogs.FieldByName('LOG_FECHA').Value;
  60.       QGetRows.Open;
  61.  
  62.       //registramos las salidas
  63.       QGetLogData.Close;
  64.       QGetLogData.SQL.Clear;
  65.       QGetLogData.SQL.Text := 'UPDATE ASISTENCIA SET AST_SAL = :SAL WHERE EMP_ID = :EMP AND AST_FECHA = :FECHA';
  66.       QGetLogData.ParamByName('SAL').Value := QGetRows.Fields[0].Value;
  67.       QGetLogData.ParamByName('EMP').Value := QGetLogs.FieldByName('EMP_ID').AsInteger;
  68.       QGetLogData.ParamByName('FECHA').Value := QGetLogs.FieldByName('LOG_FECHA').Value;
  69.       QGetLogData.ExecSQL;
  70.     end;
  71.     QGetLogs.Next;
  72. end;
  73. {*** FIN ***}
  74.  
  75. {*** CALCULAMOS LAS AUSENCIAS Y LOS QUE NO SALIERON ***}
  76. QGetRows.Close;
  77. QGetRows.SQL.Clear;
  78. QGetRows.SQL.Text := 'SELECT AST_ENT, AST_SAL FROM ASISTENCIA';
  79. QGetRows.Open;
  80.  
  81. while not QGetRows.Eof do begin
  82.   if (QGetRows.Fields[0].IsNull) and (QGetRows.Fields[1].IsNull) then
  83.   begin
  84.         QGetLogUpdate.Close;
  85.         QGetLogUpdate.SQL.Clear;
  86.         QGetLogUpdate.SQL.Text := 'UPDATE ASISTENCIA SET CA_ID = 5 WHERE AST_ENT IS NULL AND AST_SAL IS NULL';
  87.         QGetLogUpdate.ExecSQL;
  88.   end else if (QGetRows.Fields[0].Value <> null ) and (QGetRows.Fields[1].IsNull) then
  89.       begin
  90.         QGetLogUpdate.Close;
  91.         QGetLogUpdate.SQL.Clear;
  92.         QGetLogUpdate.SQL.Text := 'UPDATE ASISTENCIA SET CA_ID = 6 WHERE AST_ENT IS NOT NULL AND AST_SAL IS NULL';
  93.         QGetLogUpdate.ExecSQL;
  94.       end;
  95.     QGetRows.Next; 
  96. end;
  97. {*** FIN ***}
  98.  
  99. {*** GENERAMOS LOS EVENTOS PROGRAMADOS ***}
  100.   //Obtenemos los empleados activos
  101.   QGetEmp.Close;
  102.   QGetEmp.SQL.Clear;
  103.   QGetEmp.SQL.Text := 'SELECT * FROM EMPLEADOS WHERE EMP_STATUS = 3';
  104.   QGetEmp.Open;
  105.  
  106.   while not QGetEmp.Eof do begin
  107.     e := QGetRows.FieldByName('EMP_ID').AsInteger;
  108.  
  109.     //Buscamos causas de este empleado
  110.     QGetRows.Close;
  111.     QGetRows.SQL.Clear;
  112.     QGetRows.SQL.Text := 'SELECT EMP_ID, CA_ID, CA_DESDE, CA_HASTA FROM LOG_CAUSAS WHERE EMP_ID = :EMP';
  113.     QGetRows.ParamByName('EMP').Value := e;
  114.     QGetRows.Open;
  115.  
  116.     if QGetRows.RecordCount > 0 then begin
  117.       EventIni := QGetRows.Fields[2].AsDateTime;
  118.       EventFin := QGetRows.Fields[3].AsDateTime;
  119.  
  120.       for j := Trunc(EventIni) to Trunc(EventFin) do
  121.       begin
  122.         QGetDates.Close;
  123.         QGetDates.SQL.Clear;
  124.         QGetDates.SQL.Text := 'SELECT * FROM ASISTENCIA WHERE EMP_ID = :EMP AND AST_FECHA = :FECHA';
  125.         QGetDates.ParamByName('EMP').Value := e;
  126.         QGetDates.ParamByName('FECHA').Value := j;
  127.         QGetDates.Open;
  128.  
  129.         if QGetDates.RecordCount > 0 then begin
  130.           QGetLogUpdate.Close;
  131.           QGetLogUpdate.SQL.Clear;
  132.           QGetLogUpdate.SQL.Text := 'UPDATE ASISTENCIA SET CA_ID = :CA WHERE EMP_ID = :EMP AND AST_FECHA = :FECHA';
  133.           QGetLogUpdate.ParamByName('CA').Value := QGetDates.FieldByName('CA_ID').AsInteger;
  134.           QGetLogUpdate.ParamByName('EMP').Value := e;
  135.           QGetLogUpdate.ParamByName('fecha').Value := j;
  136.           QGetLogUpdate.ExecSQL;
  137.         end;
  138.       end;
  139.     end;
  140.     QGetEmp.Next;
  141.   end;
  142.   {*** FIN ***}
  143. end; //primer if
  144.  
  145.   DM.ZData.Commit;
  146. end;



Éste es el error que me da:



delphi
  1. ---------------------------
  2. Debugger Exception Notification
  3. ---------------------------
  4. Project TimerPunch.exe raised exception class EZSQLException with message 'SQL Error:  deadlock update conflicts with concurrent update concurrent transaction number is 3231. Error Code: -913. deadlock The SQL: UPDATE ASISTENCIA SET AST_ENT = ? WHERE EMP_ID = ? AND AST_FECHA = ?; '. Process stopped. Use Step or Run to continue.
  5. ---------------------------
  6. OK  Help 
  7. ---------------------------



EL error me lo causa en el último bloque, así que no sé a que se debe. ¿Una manita?  *-)

  • 0

#2 Alfredo

Alfredo

    Advanced Member

  • Miembros
  • PipPipPip
  • 91 mensajes
  • LocationMéxico

Escrito 13 marzo 2010 - 08:28

no se el grado de atomicidad que necesites, pero, después del while donde llenas entradas y salidad, ¿te valdria poner un commit?, tengo la impresión que el ultimo bloque esta intentando hacer una actualización a un registro que se actualizó en el bucle llenar entradas y salidas, y por eso te esta arrojando el deadlock
  • 0

#3 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 13 marzo 2010 - 09:31

Saludos.

Como bien indica Alfredo puede que necesites realizar un Commit (Commitretaining) después de actualizar los primeros registros.

Una sugerencia, trata de crearte un Stored Procedure en Firebird, es más rápido y mejor e incluso lo puedes utilizar en cualquier lugar que necesites incluyendo si cambias de lenguaje (espero que no suceda :)) .  Para tus bucles solo debes de utilizar FOR SELECT.
  • 0

#4 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.484 mensajes
  • LocationMallorca

Escrito 14 marzo 2010 - 08:47

Hola.

Un deadlock ocurre cuando dos transacciones en curso intentan modificar un mismo registro.

Aquí es donde no entiendo porqué te ocurre.

a) ¿ Tienes más usuarios conectados o conexiones abiertas y alguna de ellas tiene cambios en ejecución, pendientes de confirmar la transacción ?. En ese caso debes finalizar esas transacciones antes de poder ejecutar este procedimiento que va a modificar los mismos registros. NOTA: Como regla general, no dejes nunca transacciones abiertas, cualquier transacción que quede abierta con cambios pendientes de confirmar es una fuente segura de problemas por deadlocks. Las transacciones que modifiquen datos tienen que ser lo más cortas posibles: se abre la transacción, se modifican los datos y se confirma la transacción, no la dejes abierta pendiente de que el usuario cierre el formulario o cualquier cosa por el estilo.

b) Si no tienes más conexiones abiertas, entonces ¿ los querys de actualización de este procedimiento pertenecen a distintas transacciones ?. Tienes que ejecutarlos todos en la misma transacción, de forma que no puedan bloquearse entre ellos en un deadlock.

Como dice Rolphy, para este tipo de acciones es muchísimo mejor un procedimiento almacenado. Sus principales ventajas son :

a) Se ejecuta mucho más rápido. Ya que en tu programa Delphi, vas lanzando consultas que piden datos al servidor, que los sirve a tu programa, el cual los recorre y va lanzando nuevas consultas contra el servidor. En cambio en un procedimiento almacenado no se transporta ninguna información entre el cliente (tu programa) y el servidor, tu solo lanzas el procedimiento almacenado, y todo el procedimiento almacenado se ejecuta en el mismo Servidor Firebird. El hecho de que no se tenga que trasladar continuamente información entre el cliente y el servidor, hace que todo el proceso se ejecuta 10, 20 veces más rápido.

b) Tardas mucho menos en programarlo y se cometen menos errores. Es mucho más fácil y sencillo programar este proceso en un procedimiento almacenado que no hacerlo en un programa Delphi. NOTA: Aunque al principio es conveniente tener la versión completa de IB-Expert, para poder depurar los procedimientos almacenados que escribas.

En http://www.firebird.com.mx hay varios artículos referentes a la programación con prodemientos almacenados, como dice Rolphy, te interesa especialmente usar el bucle for select.

Saludos.
  • 0

#5 enecumene

enecumene

    Webmaster

  • Administrador
  • 7.419 mensajes
  • LocationRepública Dominicana

Escrito 14 marzo 2010 - 09:08

Gracias, Alfredo, Rolphy y Marc, me costará dominar el asunto de los SP, pero como estoy corto de tiempo no podré hacer los cambios pertinentes. Ya hice los arreglos para que no vuelva ocurrir mientras tanto hasta corregir el asunto, que será en una segunda entrega y si logro dominar y entender los SP pues lo haré con ellos. Gracias a todos.

Saludos.
  • 0




IP.Board spam blocked by CleanTalk.