FoxPress – Febrero 2001

 

Transacciones en VFP y SQL Server

http://www.fpress.com/

 

Por Alberto Rodríguez                                       transac.prg

 

 

Una transacción propiamente es un acceso a la Base de Datos. Las Transacciones fueron implementadas en el mundo Fox en la versión 3.0 pero en otras Bases de Datos existen desde hace tiempo.

 

Una transacción actúa como un log que controla las operaciones de actualización de datos en la memoria o en el disco, dependiendo de la implementación y de los requerimientos del sistema, más bien que aplicando estas actualizaciones directamente a la base de datos. La actualización de la base de datos es realizada al final de las transaccion. Si por alguna razón el sistema no puede realizar la actualización de la base de datos, se puede volver atrás la entera transacción y no realizar ninguna de las operaciones que se han realizado.

 

Las transacciones deben cumplir el Test ACID que es un acrónimo que apareció por primera vez en 1980 en un documento presentado a la ACM. Desde entonces, las Bases de Datos tienen que superar el test ACID para decir que soportan completamente las Transacciones. Las propiedades ACID con: Atomicidad, Consistencia, Aislamiento y Durabilidad.

·   Atomicidad: Una transacción debe grabar todas las operaciones que se encuentran dentro de ella o eliminarlas todas.

·   Consistencia: significa que después de las transacciones los datos deben quedar consistentes.

·   Aislamiento: Cada transacción debe estar aislada del resto de transacciones: una transacción no puede usar datos de otra transacción que todavía no ha sido granada.

·   Durabilidad: una transacción debe ser guardada permanentemente con independencia de cualquier tipo de fallo del sistema: esto es, una transacción debe mantener la unidad de trabajo guardando los cambios que han hecho el commit o haciendo el Rollback de toda la transacción.

SQL Server soporta las cuatro pero VFP sólo soporta las tres primeras: no tiene durabilidad.

 

 

Instrucciones

 

VFP

SQL Server

BEGIN TRANSACTION

BEGIN TRANSACTION

SET IMPLICIT_TRANSACTIONS ON

END TRANSACTION

COMMIT

ROLLBACK

ROLLBACK

TnxLevel()

@@TranCount

 

 

BEGIN TRANSACTION                             Incia la transacción

ROLLBACK                                             Vuelve a la situación original sin cambios desde el último BEGIN TRANSACTION

END TRANSACTION/COMMIT      Bloquea los registros, graba a disco todos los cambios hechos en las tablas de las base de datos desde el más reciente BEGIN TRANSACTION. A continuación bloquea los registros.

 

 

Las Transacciones en Visual FoxPro  

 

Se pueden usar transacciones para asegurar las modificaciones de las tablas. Si tienes que actualizar dos o más tablas, las transacciones te garantizan que se podrán actualizar las dos. Si puede actualizar una pero otra no entonces generará un error y podrás hacer un RollBack.

 

   Un ejemplo de un código que ya se publicó hace tiempo en esta revista del uso conjunto de Transacciones con los TableUpdate sería el siguiente (se están actualizando dos tablas (cabeceras y líneas):

 

BEGIN TRANSACTION

*******************************************

* Prueba si puede actualizar en tabla padre

*******************************************

IF TABLEUPDATE(0, .F., Orders) 

  *******************************************

  * Tabla padre se actualizó correctamente, prueba

  * si puede actualizar tabla hija   

  *******************************************

  IF TABLEUPDATE(2, .F., OrdItems)

        *******************************************

        * Tabla hija se actualiza correctamente, finaliza

        * la transacción

        *******************************************

        END TRANSACTION

        WAIT WINDOW "Cambios salvados" TIMEOUT 1

  ELSE

        *******************************************

        * Problemas actualizando el hijo, pero el padre

        * ya está actualizado

        *******************************************

        = AERROR(aErrorArray)

        CR = CHR(13)

        cMsg = "Actualizando la tabla hija "+;

               "ha ocurrido un error."+CR+CR+ ;

               "Error: "+aErrorArray(1)+CR+CR+;

              aErrorArray(2)

        nType = MB_ICONINFORMATION+MB_OK

        cTitle = "Problemas con actualización!"

 

        *******************************************

        * Descarta todos los cambios

        *******************************************

        ROLLBACK

        =MESSAGEBOX(cMsg,nType,cTitle)

  ENDIF

ELSE

*******************************************

* Problemas actualizando tabla padre

*******************************************

  = AERROR(aErrorArray)

  CR = CHR(13)

  cMsg = "Actualizando tabla padre, "+;

        "ha ocurrido un error."+CR+CR+ ;

        "Error: "+aErrorArray(1)+CR+CR+;

        aErrorArray(2)

  nType = MB_ICONINFORMATION+MB_OK

  cTitle = "Problemas con actualización!"

 

  *******************************************

  * Descarta los cambios

  *******************************************

  ROLLBACK

  =MESSAGEBOX(cMsg,nType,cTitle)

ENDIF

 

 

  Las transacciones sirven como otra forma de control sobre los datos. En este caso, nos permite asegurar que una unidad de trabajo puede ser procesada como un todo o retornar a la situación original sin ningún cambio. Los cambios realizados mientras una transacción está pendiente no se escriben a la tabla hasta que se ejecuta el comando END TRANSACTION y los cambios se pueden descartar si se ejecuta el comando ROLLBACK. Si el sistema se cae o el usuario sale se la aplicación con los cambios pendientes, el buffer de transacción es descartado dejando la tabla en su situación original. Es importante señalar que a diferencia de otros DBMS, FoxPro no guarda un log físico de las transacciones, de forma que si el sistema se cae no existe forma de recuperar esos cambios.

 

   Ya que BEGIN TRANSACTION y END TRANSACTION usan el punto de vista pesimista, es mejor reducir el espacio de tiempo que el programa está sometido a estos comandos.

 

   Se pueden establecer transacciones hasta con 5 niveles de profundidad. Cuando una transacción termina, no vuelca los resultados a la tabla sino al Buffer de la Transacción anterior.

 

   De todos modos, las instrucciones anteriores sólo funcionan con tablas y vistas locales pero no con datos remotos. Si quieres hacerlo en remoto deberás usar SQLSetProp() para crear la transacción y luego usar SQLCommit() y SQLRollback().

 

 

Las Transacciones en SQLServer

 

 

   En SQL Server las Transacciones son algo diferentes pues se pueden distinguir de dos tipos: implícitas o explícitas.

 

   Las Transacciones Implícitas las crea el propio sistema y las explícitas las escribes tú empezando con BEGIN TRANSACTION y acabando con COMMIT o ROLLBACK

 

   Las implícitas se crean incluso con una simple SELECT. Aunque no lo veas, antes de hacer la SELECT, SQL Server habre una transacción y al final la cierra. Esto tiene múltiples ventajas pues si sé que cada operación en la Base de Datos me crea de forma automática una transacción, podría cerrarla de forma manual. Por ejemplo, el siguiente Trigger rechazará cualquier actualización de la tabla de productos que toque el precio (fíjate que no hay inicio de transacción pero sí de cierre):

 

Create Trigger tcproductosUpdate

On productos

For Update

AS

IF Update(precio)

Begin

  Rollback Transaction

  Raiseerror 9999 “No puedes actualizar los precios”

End

 

Si tienes dos tablas relacionadas del tipo pedidos y líneas_de_pediodo y quieres borrar un pedido entero con sus líneas, esto por defecto creará dos transacciones. Para crear una sola transacción deberás establecer las transacciones como manuales para o borrar los dos registros o no borrar ninguno.

 

En las transacciones tenemos más diferencias con respecto a Fox y es que mientras en Fox sólo puedes crear 5 niveles de profundidad en las transacciones, aquí puedes crear ilimitados.

 

Otra diferencia importante es que en Fox necesitas tantos (End Transaction ó Rollback) cuantas transacciones tengas, sin embargo en SQL Server un error en una transacción en por ejemplo el tercer nivel de profundidad te desmonta todas las operaciones de todos los niveles (de arriba y de debajo de esa transaccion)

 

Cuando estás haciendo aplicaciones Cliente/Servidor desde Fox puedes indicarle al Servidor que las transacciones las vas a manejar tú mediante la instrucción :

 

SQLSETPROP(nHandle,’Transactions’,2)

 

Si usas las transacciones en manual es muy importante al acabar acordarse de ponerlas en automático otra vez. Mira el siguiente código:

 

? SQLSetProp(gnhandle,’Transactions’,2)       && Devuelve 1 si ha habido un éxito

? SQLExec (gnhandle,” Select count(*) from predidos where cod_ped = 3”)

? sqlresult.ext     && Devuelve 1 indicando que se ha encontrado el pedido

? SQLExec (gnHandle, “delete pedidos where cod_ped = 3”)

? SQLExec (gnhandle,” Select count(*) from predidos where cod_ped = 3”)

? sqlresult.ext     && Devuelve 0 indicando que no se ha encontrado el pedido

? SQLRollback (gnhandle)

? SQLExec (gnhandle,” Select count(*) from predidos where cod_ped = 3”)

? sqlresult.ext     && Devuelve 1 indicando que se ha encontrado el pedido

? SQLSetProp(gnhandle,’Transactions’,1)      

 

 

Como ves el equivalente del BEGIN TRANSACTION es el SQLSETPROP(). En las líneas anteriores se podría meter un control de devolución de parámetros para controlar que el valor devuelto por cada uno de los SQLEXEC es el correcto. Podríamos escribir algo así:

 

 

? SQLSetProp(gnhandle,’Transactions’,2)       && Devuelve 1 si ha habido un éxito

lnresultado =  SQLExec (gnHandle, “delete pedidos where cod_ped = 3”)

IF lnresultado <>1

  SQLRollback(gnhandle)

  Messagebox(“Algo ha ido mal”)

  Return

ENDIF

lnresultado = SQLExec (gnhandle,” “delete lineas_de_pedidos where cod_ped = 3”)

IF lnresultado <>1

  SQLRollback(gnhandle)

  Messagebox(“Algo ha ido mal”)

  Return

ENDIF

SQLCommit()

? SQLSetProp(gnhandle,’Transactions’,1)      

Messagebox(“Se ha borrado correctamente”)

 

No obstante,  trabajar con con el  SQLSetProp(gnhandle,’Transactions’,1) es un poco oscurantista ya que desde Fox podemos trabajar directamente con las transacciones explícitas del SQL Server:

 

El anterior código lo podríamos traducir a éste:

 

SQLEXEC(nHandle,’BEGIN TRANSACTION’)

Lnresultado1 =  SQLExec (gnHandle, “delete pedidos where cod_ped = 3”)

Lnresultado2 =  SQLExec (gnhandle,” “delete lineas_de_pedidos where cod_ped = 3”)

 

IF lnresultado1 = 1 and lnResultado2 = 1

  SQLEXEC(nHandle,’IF @@TranCount > 0 COMMIT’)

ELSE

  SQLEXEC(nHandle,’IF @@TranCount > 0 ROLLBACK’)

ENDIF

 

   Como se puede ver este código es mucho más limpio y fácil de seguir. La variable @@TranCount nos devuelve el número de transacción.

 

   En SQL Server tenemos algunas otras opciones que nos pueden servir de utilidad como la variable @@NESTLEVEL que nos indica en que nivel de profundidad de las transacciones estamos metidos.

 

   De todos modos creo que con estos ejemplos, hay suficiente.

 

 

 

 

 

FoxPress – Febrero de 2001

© 2001 FoxPress. All rights reserved