Como ya seguramente sabes, todas las operaciones (INSERT, UPDATE, DELETE, SELECT, FETCH, EXECUTE PROCEDURE, etc.) que puedes realizar con las bases de datos de Firebird requieren ser realizadas dentro de transacciones, sí o sí, en el 100% de los casos.

Esas transacciones deben tener un comienzo y un fin.

Antes de comenzar una transacción puedes (si quieres) establecer cuales serán los parámetros que se usarán en esa transacción. Si tú no los estableces entonces el Cliente del Firebird lo hará por tí.

Parámetros por defecto de las transacciones

Si tú no indicas cuales serán los parámetros que utilizará la siguiente transacción entonces el Cliente del Firebird determina los siguientes:

  • READ WRITE
  • WAIT
  • SNAPSHOT

READ WRITE significa que la transacción podrá cambiar el contenido de las tablas. La alternativa es READ ONLY que significa que se puede leer el contenido de las tablas pero no cambiarlos.

WAIT significa que si encuentra un conflicto con otra transacción entonces esperará hasta que la otra transacción termine. La alternativa es NO WAIT lo cual significa que si encuentra un conflicto con otra transacción inmediatamente se elevará una excepción con un mensaje de error.

SNAPSHOT significa que si otra transacción realizó cambios a las tablas después que esta transacción haya empezado, esta transacción no se enterará de esos cambios. Las alternativas son READ COMMITTED que le permite a esta transacción enterarse de todos los cambios que otras transacciones hayan realizado y confirmado (se confirma con un COMMIT) y SNAPSHOT TABLE STABILITY que abre a todas las tablas que usa esta transacción en forma exclusiva, impidiendo por lo tanto a las otras transacciones realizar cambios a esas tablas.

Especificando los parámetros de la siguiente transacción

Una vez que una transacción empezó ya no puedes cambiar los parámetros de la misma. Eso implica que todos los parámetros debes establecerlos antes de que la transacción comience. Y recuerda que si tú no estableces los parámetros de una transacción entonces el Cliente del Firebird los establece por tí usando los valores por defecto (READ WRITE, WAIT, SNAPSHOT), como vimos más arriba.

Esto es importante recordar, porque puede prestarse a confusión: una transacción nunca hereda los parámetros de la transacción anterior. Los parámetros de una transacción los estableces tú específicamente con los valores que deseas o los establece el Cliente del Firebird, pero en ambos casos solamente se aplican a la siguiente transacción, a ninguna otra.

La clase SQL_CLASES

Si programas con el lenguaje Visual FoxPro entonces puedes descargar SQL_CLASES.PRG, allí encontrarás propiedades y métodos que te facilitarán grandemente el uso de Firebird.

http://www.mediafire.com/view/l36nar3o6ap19b9/SQL_CLASES.PRG

A continuación, la explicación sobre las propiedades y métodos relacionados con las transacciones.

Propiedades

cTransaccionAcceso           = "READ WRITE"     && la alternativa es   : READ ONLY
cTransaccionAislamiento      = "READ COMMITTED" && las alternativas son: SNAPSHOT, SNAPSHOT TABLE STABILITY
cTransaccionModoBloqueo      = "WAIT"           && la alternativa es   : NO WAIT
cTransaccionRecordVersion    = "RECORD_VERSION" && la alternativa es   : NO RECORD_VERSION
cTransaccionReservacion      = "SHARED"         && las alternativas son: PROTECTED, READ, WRITE
cTransaccionTablasReservadas = ""               && la alternativa es   : RESERVING MisTablas FOR PROTECTED WRITE
cTransaccionTipo             = ""               && las alternativas son: ABM, INFORME
nTipoTransaccion             = 2                && 1 = Transacción automática, 2 = Transacción manual.

Estas propiedades serán luego usadas por la función .Abrir_Transaccion(), la cual como su nombre lo indica se encarga de abrir las transacciones.

Métodos

.Abrir_Transaccion() abre una transacción utilizando los valores que tienen las propiedades. Es en esas propiedades donde determinamos como queremos que sea la transacción que se abrirá.

.Abrir_Transaccion_ABM() abre una transacción que será utilizada para realizar cambios a las tablas (INSERT, UPDATE, DELETE). Primero, les asigna valores a las propiedades y luego llama a la función .Abrir_Transacción(), que vimos en el párrafo anterior. Desde luego que podrías tener funciones .Abrir_Transaccion_ABM_2(), .Abrir_Transaccion_ABM_3(), etc. con valores distintos.

.Abrir_Transaccion_Informe() abre una transacción que será utilizada solamente con el comando SELECT. Primero, le asigna valores a las propiedades y luego llama a la función .Abrir_Transaccion(). Si tus necesidades aumentan podrías tener funciones .Abrir_Transaccion_Informe_2(), .Abrir_Transaccion_Informe_3(), etc.

En realidad, podríamos tener solamente la función .Abrir_Transaccion(), sin necesidad de las otras dos funciones. Pero están allí para facilitarnos la vida, así cuando queremos una transacción que cambiará el contenido de alguna tabla llamamos a la función  .Abrir_Transaccion_ABM() y cuando queremos una transacción que solamente realizará consultas llamamos a la función .Abrir_Transaccion_Informe(). Si no tuviéramos a esas dos funciones entonces antes de llamar a .Abrir_Transaccion() tendríamos que establecer los valores de las propiedades, lo cual nos haría escribir más y con el riesgo de equivocarnos alguna vez. Gracias a estas dos funciones escribimos menos y nos aseguramos que las propiedades siempre tengan los valores correctos.

.Ejecutar() se encarga de ejecutar el comando que le enviamos como parámetro. Pero antes de ello verifica que tengamos una transacción abierta con la función .Abrir_Transaccion_ABM() o con la función .Abrir_Transaccion_Informe(). ¿Por qué? Porque así nos aseguramos que la transacción cumpla con nuestros requisitos. Si queremos ejecutar un comando y no tenemos una transacción abierta por nosotros entonces el comando no será ejecutado. En otras palabras, no aceptamos transacciones abiertas automáticamente por el Cliente del Firebird, todas las transacciones debemos abrirlas nosotros usando o la función .Abrir_Transaccion_ABM() o la función .Abrir_Transaccion_Informe(). De esta manera nos aseguramos que la transacción siempre tenga los valores que deseamos que tenga. Si el comando que queremos ejecutar es un COMMIT o un ROLLBACK entonces sabemos que allí la transacción finaliza y se coloca en la propiedad .cTransaccionTipo el valor “”, de esa manera podemos saber que no hay una transacción abierta.

Funciones de Visual FoxPro

Para escribir menos y facilitarnos la vida, en lugar de llamar a las funciones de SQL_CLASES llamamos a otras funciones que realizan la misma tarea. A esta técnica se le llama “enmascarar” y podemos leerla en este artículo:

Enmascarando los stored procedures

Las funciones enmascaradoras que usamos son:

  • ABRIR_TRANSACCION_ABM()
  • ABRIR_TRANSACCION_INFORME()

Como ves, tienen los mismos nombres que los usados en SQL_CLASES pero en este caso se trata de funciones independientes, no son funciones dentro de una clase.

FUNCTION ABRIR_TRANSACCION_ABM
Local lcAlias, llResultadoOK

   lcAlias = Alias()

   llResultadoOK = _SCreen.goSQL.ABRIR_TRANSACCION_ABM()

   if !Empty(lcAlias)
      select (lcAlias)
   endif

Return(llResultadoOK)
*
*
FUNCTION ABRIR_TRANSACCION_INFORME
Local lcAlias, llResultadoOK

   lcAlias = Alias()

   llResultadoOK = _SCreen.goSQL.ABRIR_TRANSACCION_INFORME()

   if !Empty(lcAlias)
      select (lcAlias)
   endif

Return(llResultadoOK)
*
*
FUNCTION CERRAR_TRANSACCION
LParameters tcModo
Local lcAlias, llResultadoOK

   if !Empty(_Screen.goSQL.cTransaccionTipo) && Solamente cierra las transacciones abiertas
      lcAlias = Alias()
      tcModo = iif(VarType(tcModo) <> "C", "COMMIT", tcModo)
      llResultadoOK = iif((tcModo == "COMMIT" .or. tcModo == "ROLLBACK"), .T., .F.)
      if llResultadoOK
         llResultadoOK = SQL_Ejecutar(tcModo, "CUR_CERRAR")
      endif
      if !llResultadoOK .and. tcModo == "COMMIT" && Si no se pudo conseguir que la transacción finalice con un COMMIT, hay que finalizarla con un ROLLBACK
         =SQL_Ejecutar("ROLLBACK", "CUR_CERRAR")
         do MENSAJE_ERROR with "Falló el COMMIT, eso es un error grave.*Se finalizó la transacción con un ROLLBACK"
      endif
      _Screen.goSQL.cTransaccionTipo = ""
      if !Empty(lcAlias) .and. Used(lcAlias)
         select (lcAlias)
      endif
   else
      llResultadoOK = .T.
   endif

Return(llResultadoOK)
*
*

Lo que hacen las dos primeras funciones es, primero guardar el nombre de la tabla .DBF que tenemos abierta (si hay alguna tabla .DBF abierta, por supuesto), segundo llamar a la función de SQL_CLASES que nos interesa y tercero si había una tabla .DBF abierta volver a seleccionarla. Esas funciones nos devuelven un valor indicando si la transacción se pudo abrir exitosamente o no.

La función Cerrar_Transaccion() como su nombre lo indica se encarga de cerrar la transacción que tenemos abierta. Si no le indicamos si queremos cerrarla con un COMMIT o con un ROLLBACK entonces asume que queremos cerrarla con un COMMIT, ya que eso es lo más común. Para que sepamos que ya no hay una transacción abierta le asigna “” a la propiedad .cTransaccionTipo.

Ejemplos de uso de las transacciones

Ahora que ya hemos visto la clase y las funciones relacionadas con las transacciones veamos algunos ejemplos de como usarlas.

=Abrir_Transaccion_Informe()
llResultado = SQL_Ejecutar("SELECT * FROM PERSONALIZAR WHERE PER_USUARI = " + lcUsuario)
=Cerrar_Transaccion()

En este caso queremos realizar una consulta entonces se llamó a la función enmascaradora Abrir_Transaccion_Informe(), se realizó la consulta y se cerró la transacción.

TEXT TO lcComando NOSHOW
   EXECUTE PROCEDURE GRABAR_PERSONALIZACION (?lcUsuario, ?lcImagenFondo, ?lnColorTextoMensajesAyuda, ?lcArchivoSonido)
ENDTEXT

=Abrir_Transaccion_ABM()
llGrabacionOK = SQL_Ejecutar(lcComando)
=Cerrar_Transaccion()

En este caso llamamos a un stored procedure que cambiará el contenido de una tabla, por lo tanto llamamos a la función enmascaradora Abrir_Transaccion_ABM(), ejecutamos el comando y cerramos la transacción.

IMPORTANTE:

En Firebird es extremadamente importante tener a las transacciones abiertas durante el menor tiempo posible. Cuanto menos tiempo dure una transacción mucho mejor, porque eso ayudará a evitar conflictos con otras transacciones que quieran acceder a las mismas filas de las mismas tablas.

Como puedes ver en los ejemplos anteriores el procedimiento es abrir la transacción, ejecutar un comando, e inmediatamente cerrar la transacción. Eso es lo correcto.

Conclusión:

Los parámetros de las transacciones los puede establecer el Cliente del Firebird o los podemos establecer nosotros. Si los establecemos nosotros entonces tenemos un mucho mayor control sobre el actuar de esa transacción.

El lenguaje Visual FoxPro nos permite crear clases, esa es una muy buena característica del lenguaje y debemos aprovecharla. Aquí vemos un ejemplo del uso de las clases, en este caso para usarlas con Firebird.

Enmascarar las llamadas a rutinas o funciones nos facilita la vida porque escribimos menos y nos aseguramos de que todo esté siempre correcto. Las transacciones deben durar el menor tiempo posible, por eso lo correcto es abrirlas, ejecutar operaciones en ellas, y cerrarlas inmediatamente, tal como se ha visto en los ejemplos.

Artículos relacionados:

Entendiendo las transacciones

Enmascarando los stored procedures

El índice del blog Firebird21

El foro del blog Firebird21