Derrick Leggett Blog

Ramblings, grumblings, and other nonintelligible nonsense about SQL Server.

Nested Triggers and Transactions

I ran across an interesting post on www.sqlteam.com that got me thinking about how transactions interact inside of nested triggers.  Since I'm trying to study how transactions work in SQL Server anyway, I thought I would give this a try.

EXEC sp_configure

BEGIN TRANSACTION TRAN_WRAP

 BEGIN TRANSACTION CREATE_TABLE
  IF (SELECT OBJECT_ID('djl_test_trigger_transactions')) IS NOT NULL
   BEGIN
    DROP TABLE djl_test_trigger_transactions
   END

  CREATE TABLE djl_test_trigger_transactions(
   int_value INT PRIMARY KEY,
   varchar_value VARCHAR(55))
 COMMIT TRANSACTION CREATE_TABLE

 BEGIN TRANSACTION TRIGGER1
  DECLARE @sql NVARCHAR(4000)

  SELECT @sql = '
   CREATE TRIGGER [trg_djl_test_trigger_transactions_ins_1] ON [dbo].[djl_test_trigger_transactions]
   FOR INSERT, UPDATE
   AS
   
   PRINT ''EXECUTING FIRST TRIGGER''

   IF EXISTS(SELECT varchar_value FROM inserted WHERE varchar_value = ''' + 'aaa' + ''')
    BEGIN
    
     IF EXISTS( SELECT * FROM inserted WHERE int_value = 2)
      BEGIN
       RAISERROR(''' + 'Invalid entry.' + ''',16,1)
       ROLLBACK
       RETURN
      END
    END

   IF EXISTS(SELECT int_value FROM inserted WHERE int_value = 1)
    BEGIN

     UPDATE djl_test_trigger_transactions SET varchar_value = ''' + 'aab' + ''' WHERE int_value = 1

    END 

   PRINT ''' + 'ARE YOU STILL ALIVE?' + ''''

  EXEC(@sql)
 
 COMMIT TRANSACTION TRIGGER1
 
 BEGIN TRANSACTION TRIGGER2

  SELECT @sql = '
  CREATE TRIGGER [trg_djl_test_trigger_transactions_ins_2] ON [dbo].[djl_test_trigger_transactions]
  FOR INSERT, UPDATE
  AS
   BEGIN

    PRINT ''EXECUTING SECOND TRIGGER''

    IF EXISTS(SELECT varchar_value FROM inserted WHERE varchar_value = ''' + 'xxx' + ''')
     BEGIN
      INSERT djl_test_trigger_transactions(int_value, varchar_value)
       VALUES(6,''' + 'axs' + ''')
     END

    IF EXISTS(SELECT * FROM inserted WHERE varchar_value = ''' + 'aab' + ''')
     BEGIN

      UPDATE djl_test_trigger_transactions SET int_value = int_value + 12 WHERE varchar_value = ''' + 'aab' + '''

     END
    ELSE
     BEGIN

      INSERT djl_test_trigger_transactions(int_value, varchar_value)
       SELECT MAX(int_value)+2,''axs'' FROM djl_test_trigger_transactions

     END

   END'

  EXEC(@sql)

 COMMIT TRANSACTION TRIGGER2

 BEGIN TRANSACTION TRIGGER_ORDER

  EXEC sp_settriggerorder 'trg_djl_test_trigger_transactions_ins_1', 'first', 'UPDATE'

 COMMIT TRANSACTION TRIGGER_ORDER

COMMIT TRANSACTION TRAN_WRAP
GO

 SELECT * FROM djl_test_trigger_transactions
 PRINT '1,zzz'
 INSERT djl_test_trigger_transactions(int_value, varchar_value) VALUES(1,'zzz')

 SELECT * FROM djl_test_trigger_transactions

GO
 PRINT '2,aaa'
 INSERT djl_test_trigger_transactions(int_value, varchar_value) VALUES(2,'aaa')
GO
 SELECT * FROM djl_test_trigger_transactions

GO
 PRINT '4,aaa'
 INSERT djl_test_trigger_transactions(int_value, varchar_value) VALUES(4,'aaa')

 SELECT * FROM djl_test_trigger_transactions

GO
 PRINT '2,aab'
 INSERT djl_test_trigger_transactions(int_value, varchar_value) VALUES(2,'aab')

 SELECT * FROM djl_test_trigger_transactions
GO
 PRINT '12,xxx'
 INSERT djl_test_trigger_transactions(int_value, varchar_value) VALUES(12,'xxx')
GO
 SELECT * FROM djl_test_trigger_transactions
GO
 PRINT '18,xxx'
 INSERT djl_test_trigger_transactions(int_value, varchar_value) VALUES(18,'xxx')
GO
 SELECT * FROM djl_test_trigger_transactions
GO


EXEC sp_configure 'nested triggers','0'
RECONFIGURE

BEGIN TRANSACTION TRAN_WRAP

 BEGIN TRANSACTION CREATE_TABLE
  IF (SELECT OBJECT_ID('djl_test_trigger_transactions')) IS NOT NULL
   BEGIN
    DROP TABLE djl_test_trigger_transactions
   END

  CREATE TABLE djl_test_trigger_transactions(
   int_value INT PRIMARY KEY,
   varchar_value VARCHAR(55))
 COMMIT TRANSACTION CREATE_TABLE

 BEGIN TRANSACTION TRIGGER1
  DECLARE @sql NVARCHAR(4000)

  SELECT @sql = '
   CREATE TRIGGER [trg_djl_test_trigger_transactions_ins_1] ON [dbo].[djl_test_trigger_transactions]
   FOR INSERT, UPDATE
   AS
   
   PRINT ''EXECUTING FIRST TRIGGER''

   IF EXISTS(SELECT varchar_value FROM inserted WHERE varchar_value = ''' + 'aaa' + ''')
    BEGIN
    
     IF EXISTS( SELECT * FROM inserted WHERE int_value = 2)
      BEGIN
       RAISERROR(''' + 'Invalid entry.' + ''',16,1)
       ROLLBACK
       RETURN
      END
    END

   IF EXISTS(SELECT int_value FROM inserted WHERE int_value = 1)
    BEGIN

     UPDATE djl_test_trigger_transactions SET varchar_value = ''' + 'aab' + ''' WHERE int_value = 1

    END 

   PRINT ''' + 'ARE YOU STILL ALIVE?' + ''''

  EXEC(@sql)
 
 COMMIT TRANSACTION TRIGGER1
 
 BEGIN TRANSACTION TRIGGER2

  SELECT @sql = '
  CREATE TRIGGER [trg_djl_test_trigger_transactions_ins_2] ON [dbo].[djl_test_trigger_transactions]
  FOR INSERT, UPDATE
  AS
   BEGIN

    PRINT ''EXECUTING SECOND TRIGGER''

    IF EXISTS(SELECT varchar_value FROM inserted WHERE varchar_value = ''' + 'xxx' + ''')
     BEGIN
      INSERT djl_test_trigger_transactions(int_value, varchar_value)
       VALUES(6,''' + 'axs' + ''')
     END

    IF EXISTS(SELECT * FROM inserted WHERE varchar_value = ''' + 'aab' + ''')
     BEGIN

      UPDATE djl_test_trigger_transactions SET int_value = int_value + 12 WHERE varchar_value = ''' + 'aab' + '''

     END
    ELSE
     BEGIN

      INSERT djl_test_trigger_transactions(int_value, varchar_value)
       SELECT MAX(int_value)+2,''axs'' FROM djl_test_trigger_transactions

     END

   END'

  EXEC(@sql)

 COMMIT TRANSACTION TRIGGER2

 BEGIN TRANSACTION TRIGGER_ORDER

  EXEC sp_settriggerorder 'trg_djl_test_trigger_transactions_ins_1', 'first', 'UPDATE'

 COMMIT TRANSACTION TRIGGER_ORDER

COMMIT TRANSACTION TRAN_WRAP
GO

 SELECT * FROM djl_test_trigger_transactions
 PRINT '1,zzz'
 INSERT djl_test_trigger_transactions(int_value, varchar_value) VALUES(1,'zzz')

 SELECT * FROM djl_test_trigger_transactions

GO
 PRINT '2,aaa'
 INSERT djl_test_trigger_transactions(int_value, varchar_value) VALUES(2,'aaa')
GO
 SELECT * FROM djl_test_trigger_transactions

GO
 PRINT '4,aaa'
 INSERT djl_test_trigger_transactions(int_value, varchar_value) VALUES(4,'aaa')
GO
 SELECT * FROM djl_test_trigger_transactions

GO
 PRINT '2,aab'
 INSERT djl_test_trigger_transactions(int_value, varchar_value) VALUES(2,'aab')

 SELECT * FROM djl_test_trigger_transactions

GO
 PRINT '12,xxx'
 INSERT djl_test_trigger_transactions(int_value, varchar_value) VALUES(12,'xxx')
GO
 SELECT * FROM djl_test_trigger_transactions
GO
 PRINT '18,xxx'
 INSERT djl_test_trigger_transactions(int_value, varchar_value) VALUES(18,'xxx')
GO
 SELECT * FROM djl_test_trigger_transactions
GO

EXEC sp_configure 'nested triggers','1'
RECONFIGURE

Results:
 1.  The nested triggers option does not affect the ability to roll back an entire transaction.
 2.  The nested triggers run in order if you have set the order, regardless of whether nested triggers are on or not.
 3.  ROLLBACK rolls back the work already done in the batch. 
 4.  RETURN stops the batch from processing any further.  Otherwise, the trigger would finish to completion.
 5.  RAISERROR is informational.
 6.  Any fatal errors in the trigger transaction space, such as primary key duplicates, results in a complete immediate rollback and stop of the batch.

Let me know if I have missed anything or if you have something to add.  I'm trying to put together an article that explains in detail how transactions are handled in SQL Server.  So far, I have pieces in progress or planned on nested transactions, transactions in cursors and loops, error handling, distributed transactions, transaction considerations for application architecture, and a piece on transaction logic flow dealing with transaction nesting logic and use of the return state to make control transaction flow properly and be carried through multiple steps in the application or job.  If anyone wants to see anything else, let me know.  I'm trying to have it done by next week, but if I get good feedback or find out I'm totally wrong about some things I'll push it back.  (The totally wrong part is more likely to happen then anything.)

Favorite words used: 0 (WOW)

Mean level (1-10):  0 (I'm ruined.)

Education level (1-10):  3 (I learned something from this.  I'm not sure if anyone else will, but it's fun to test concepts.

Entertainment level (1-10): 1 (If you are a hopeless nerd, you should find this to be mildly entertaining.  I did anyway.)

 

Legacy Comments


Tonks
2004-09-13
re: Nested Triggers and Transactions
This is not related to your post...

"Nonintelligable" is not a word. I suspect you mean "unintelligable"