Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
1 . Introducción
2 . Sentencias para una transacción
3 . Transacciones anidadas
4 . Un par de ejemplos más
5 . Transacciones y procedimientos almacenados.
Entre las habilidades de todo Sistema Gestor de Bases de Datos Relaciones tiene que
estar la de permitir al programador crear transacciones. Y aunque el SQL Server nos
permite trabajar con transacciones de manera sencilla y eficaz siempre hay
dificultades...
Introducción
Una transacción es un conjunto de operaciones que van a ser tratadas como una única
unidad. Estas transacciones deben cumplir 4 propiedades fundamentales comúnmente
conocidas como ACID (atomicidad, coherencia, asilamiento y durabilidad).
La transacción más simple en SQL Server es una única sentencia SQL. Por ejemplo una
sentencia como esta:
Por supuesto este tipo de transacciones no requieren de nuestra intervención puesto que
el sistema se encarga de todo. Sin embargo si hay que realizar varias operaciones y
queremos que sean tratadas como una unidad tenemos que crear esas transacciones de
manera explícita.
Si todas las operaciones de una transacción se completan con éxito hay que marcar el
fin de una transacción para que la base de datos vuelva a estar en un estado consistente
con la sentencia 'COMMIT TRAN'.
Un ejemplo
Trabajaremos con la base de datos Northwind en nuestros ejemplos. Vamos a realizar
una transacción que modifica el precio de dos productos de la base de datos.
USE NorthWind
DECLARE @Error int
--Declaramos una variable que utilizaremos para almacenar un posible
código de error
BEGIN TRAN
--Iniciamos la transacción
UPDATE Products SET UnitPrice=20 WHERE ProductName ='Chai'
--Ejecutamos la primera sentencia
SET @Error=@@ERROR
--Si ocurre un error almacenamos su código en @Error
--y saltamos al trozo de código que deshara la transacción. Si, eso de
ahí es un
--GOTO, el demonio de los programadores, pero no pasa nada por usarlo
--cuando es necesario
IF (@Error<>0) GOTO TratarError
--Si llegamos hasta aquí es que los dos UPDATE se han completado con
--éxito y podemos "guardar" la transacción en la base de datos
COMMIT TRAN
TratarError:
--Si ha ocurrido algún error llegamos hasta aquí
If @@Error<>0 THEN
BEGIN
PRINT 'Ha ecorrido un error. Abortamos la transacción'
--Se lo comunicamos al usuario y deshacemos la transacción
--todo volverá a estar como si nada hubiera ocurrido
ROLLBACK TRAN
END
Como se puede ver para cada sentencia que se ejecuta miramos si se ha producido o no
un error, y si detectamos un error ejecutamos el bloque de código que deshace la
transacción.
¡Nada más lejos de la realidad! Si tenemos dos sentencias dentro de una transacción.
USE NorthWind
BEGIN TRAN
UPDATE Products SET UnitPrice=20 WHERE ProductName='Chang'
UPDATE Products SET UnitPrice=20 WHERE ProductName='Chang'
COMMIT TRAN
Estas dos sentencias se ejecutarán como una sola. Si por ejemplo en medio de la
transacción (después del primer update y antes del segundo) hay un corte de
electricidad, cuando el SQL Server se recupere se encontrará en medio de una
transacción y, o bien la termina o bien la deshace, pero no se quedará a medias.
Por eso en el ejemplo que tenemos más arriba para cada sentencia de nuestro conjunto
averiguamos si se ha producido un error y si es así actuamos en consecuencia
cancelando toda la operación.
Transacciones anidadas
Otra de las posibilidades que nos ofrece el SQL Server es utilizar transacciones
anidadas. Esto quiere decir que podemos tener transacciones dentro de transacciones, es
decir, podemos empezar una nueva transacción sin haber terminado la anterior.
Asociada a esta idea de anidamiento existe una variable global @@TRANCOUNT que
tiene valor 0 si no existe ningún nivel de anidamiento, 1 si hay una transacción anidada,
2 si estamos en el segundo nivel de anidamiento. y así sucesivamente.
Por cierto que lo de usar nombre para las transacciones es por claridad, puesto que
COMMIT TRAN como ya hemos dicho solamente reduce en 1 el valor de
@@TRANCOUNT.
En este caso no se inserta nada puesto que el ROLLBACK TRAN deshace todas las
transacciones dentro de nuestro anidamiento hasta la transacción más externa y además
hace @@TRANCOUNT=0
Save Tran
Esta sentencia crea un punto de almacenamiento dentro de una transacción. Esta marca
sirve para deshacer una transacción en curso sólo hasta ese punto. Por supuesto nuestra
transacción debe continuar y terminar con un COMMIN TRAN (o los que hagan falta)
para que todo se guarde o con un ROLLBACK TRAN para volver al estado previo al
primer BEGIN TRAN.
Si no ponemos el nombre del punto salvado con SAVE TRAN al hacer un ROLLBACK
TRAN se deshace la transacción más externa y @@TRANCOUNT se pone a 0.
BEGIN TRAN
-- Primer BEGIN TRAN y ahora @@TRANCOUNT = 1
BEGIN TRAN
-- Ahora @@TRANCOUNT = 2
COMMIT TRAN
-- Volvemos a @@TRANCOUNT = 1
-- Pero no se guarda nada ni se hacen efectivos los posibles
cambios
COMMIT TRAN
-- Por fin @@TRANCOUNT = 0
-- Si hubiera cambios pendientes se llevan a la base de datos
-- Y volvemos a un estado normal con la transacción acabada
BEGIN TRAN
-- Primer BEGIN TRAN y @@TRANCOUNT = 1
BEGIN TRAN
-- Ahora @@TRANCOUNT = 2
COMMIT TRAN
-- Como antes @@TRANCOUNT = 1
--Y como antes nada se guarda
ROLLBACK TRAN
-- Se cancela TODA la transacción. Recordemos que el COMMIT
-- de antes no guardo nada, solo redujo @@TRANCOUNT
-- Ahora @@TRANCOUNT = 0
COMMIT TRAN
-- No vale para nada porque @@TRANCOUNT es 0 por el efecto del
ROLLBACK
BEGIN TRAN
INSERT INTO Tabla1 VALUES ('Primer valor')
SAVE TRAN Punto1
INSERT INTO Tabla1 VALUES ('Segundo valor')
ROLLBACK TRAN Punto1
INSERT INTO Tabla1 VALUES ('Tercer valor')
COMMIT TRAN
Con esto no quiero decir que no se pueda usar, simplemente que hay que tener muy
claras las 4 normas sobre las sentencias BEGIN, ROLLBACK y COMMIT que
comentamos al principio de este artículo. Veamos como se comporta una transacción en
un procedimiento almacenado llamado desde una transacción.
EXECUTE inserta1
SELECT * FROM tabla1
txt
--------------------------------------------------
Valor1
Valor3
(2 filas afectadas)
EXECUTE inserta1
SELECT * FROM tabla1
(1 filas afectadas)
(1 filas afectadas)
Servidor: mensaje 266, nivel 16, estado 2, procedimiento Inserta2,
línea 8
El recuento de transacciones después de EXECUTE
indica que falta una instrucción COMMIT o ROLLBACK TRANSACTION.
Recuento anterior = 1, recuento actual = 0.
(1 filas afectadas)
Servidor: mensaje 3902, nivel 16, estado 1, procedimiento Inserta1,
línea 8
La petición COMMIT TRANSACTION no tiene la correspondiente BEGIN
TRANSACTION.
txt
--------------------------------------------------
Valor3
(1 filas afectadas)
Si analizamos estos mensajes vemos que se inserta la primera fila, se salta al segundo
procedimiento almacenado y se inserta la segunda fila. Se ejecuta el 'ROLLBACK
TRAN -Dos' del segundo procedimiento almacenado y se deshacen las dos inserciones
porque este ROLLBACK cancela la transacción exterior, la del primer procedimiento
almacenado. A continuación se termina el segundo procedimiento almacenado y como
este procedimiento tiene un 'BEGIN TRAN -Dos' y no tiene un COMMIT o un
ROLLBACK (recordemos que el 'ROLLBACK TRAN -Dos' termina el 'BEGIN TRAN
-Uno') se produce un error y nos avisa que el procedimiento almacenado termina con
una transacción pendiente. Al volver al procedimiento almacenado externo se ejecuta el
INSERT que inserta la tercera fila y a continuación el 'COMMIT TRAN --Uno'. Aquí
aparece otro error puesto que este 'COMMIT TRAN -Uno' estaba ahí para finalizar una
transacción que ya ha sido cancelada anteriormente.
El modo correcto
Para que nuestras transacciones se comporten como se espera dentro de un
procedimiento almacenado podemos recurrir al SAVE TRAN.
Veamos como:
EXECUTE inserta1
SELECT * FROM tabla1
(1 filas afectadas)
(1 filas afectadas)
(1 filas afectadas)
txt
--------------------------------------------------
Valor 1
Valor 3
(2 filas afectadas)
Una vez vistos estos ejemplos queda claro que no hay problema en utilizar
transacciones dentro de procedimientos almacenados siempre que tengamos en cuenta el
comportamiento del ROLLBACK TRAN y que utilicemos el SAVE TRAN de manera
adecuada.
Últimos comentarios
Últimos 5 comentarios
Script (22/11/2007)
Por jarold
Hola, alguien puede ayudarme??, como DBA surge siempre la tarea de inicializar bases
de atos con catálogos predefinidos y otras tablas vacias, por lo que necesito un script o
una tool para poder insertar registros varios en diversas tablas, ojala me pueda alguien
ayudar.Gracias.
Cuando Algo sale mal, aplika rollback a todo, incluyendo mis registros de bitacora.
Pregunta...
Como le puedo hacer para ke el RollBack no Afecte mi Bitacora y si regrese los
movimientos de los empleados???
Es un Sp de casi 3500 Lineas, por eso me interesa mantener mi bitacora viva, para
detectar en ke movimiento fue donde trono.
Por henry
tengo una tabla con 27 registros pero deseo incrementar a 300 para hacer prubas como
puede hacer empleando un script de sql ingresarle registros en serie y evitarme estar
ingresando 1 x 1 desde la aplicación
Por JulianT
Mario, aqui van unas breves respuestas sobre tus preguntas:
1. Creo que estas confundiendo un transaccion en un motor con una funcion en un
lenguaje de programacion, una transaccion es inherente al motor de bases de datos y
simplemente lo que hace es ejecutar una serie de instrucciones SQL como un solo
conjunto de tal manera que se pueda posteriormente \"deshacer\" los cambios que se
hayan podido hacer o guardarlos definitivamente.
Viendolo de un modo sencillo es como una utilidad que nos permite \"deshacer\" si algo
no sale bien.
4. Eso lo puedes realizar con una funcion sobre el motor, esta funcion recibiria como
parametros los elementos del array y los insertaria en la tabla que quieras. Y puedes usar
una transaccion para llamar la funcion si deseas.