ptg 1014 CHAPTER 31 Transaction Management and the Transaction Log IF (@@error <> 0) BEGIN ROLLBACK TRAN mytran RETURN –1969 END . . /* Once you reach the end of the code, you need to pair the BEGIN TRAN, if you issued it, with a COMMIT TRAN. If you executed the SAVE TRAN instead, you have nothing else to do end of game! */ IF (@trancount = 0) COMMIT TRAN RETURN 0 If you apply these concepts to all stored procedures that need to incorporate transaction processing as well as the code that calls the stored procedures, you should be able to avoid problems with transaction nesting and inconsistency in your transaction processing. You just need to be sure to check the return value of the stored procedure and determine whether the whole batch should be failed or whether that one call is of little importance to the overall outcome and the transaction can continue. For additional examples of and discussion about coding guidelines for stored procedures in transactions, see Chapter 44, “Advanced Stored Procedure Programming and Optimization.” Transactions and Triggers SQL Server 2008 provides two types of Data Manipulation Language (DML) triggers: AFTER and INSTEAD OF. INSTEAD OF triggers perform their actions before any modifications are made to the actual table the trigger is defined on. Whenever a trigger is invoked, it is always invoked within another transaction, whether it’s a single-statement AutoCommit transaction or a user-defined multistatement transac- tion. This is true for both AFTER triggers and INSTEAD OF triggers. Even though an INSTEAD OF trigger fires before, or “instead of,” the data modification statement itself, if a transac- tion is not already active, an AutoCommit transaction is still automatically initiated as the data modification statement is invoked and prior to the invocation of the INSTEAD OF trigger. (For more information on AFTER and INSTEAD OF triggers, see Chapter 30, “Creating and Managing Triggers.”) NOTE Although the information presented in this section applies to both AFTER and INSTEAD OF triggers, the examples presented pertain primarily to AFTER triggers. Because the trigger is already operating within the context of a transaction, the only trans- action control statements you should ever consider using in a trigger are ROLLBACK and Download from www.wowebook.com ptg 1015 Transactions and Triggers 31 SAVE TRAN. You don’t need to issue a BEGIN TRAN because a transaction is already active; a BEGIN TRAN would only serve to increase the transaction nesting level, and that would complicate things further. Triggers and Transaction Nesting To demonstrate the relationship between a trigger and the transaction nesting level, you can use the following SQL code to create a trigger on the employee table: use bigpubs2008 go CREATE TRIGGER tD_employee ON employee FOR DELETE AS DECLARE @msg VARCHAR(255) SELECT @msg = ‘Trancount in trigger = ‘ + CONVERT(VARCHAR(2), @@trancount) PRINT @msg RETURN go The purpose of this trigger is simply to show the state of the @@trancount within the trigger as the deletion is taking place. If you now execute code for implied and explicit transactions, you can see the values of @@trancount and behavior of the batch. First, here’s the implied transaction: set nocount on print ‘Trancount before delete = ‘ + CONVERT(VARCHAR(2), @@trancount) DELETE FROM employee WHERE emp_id = ‘PMA42628M’ print ‘Trancount after delete = ‘ + CONVERT( VARCHAR(2), @@trancount) go The results of this are as follows: Trancount before delete = 0 Trancount in trigger = 1 Trancount after delete = 0 Because no transaction starts until the DELETE statement executes, the first value of @@trancount indicates this with a value of 0. Within the trigger, the transaction count has a value of 1; you are now inside the implied transaction caused by the DELETE. After the trigger returns, the DELETE is automatically committed, the transaction is finished, and @@trancount returns to 0 to indicate that no transaction is currently active. Now explore what happens within an explicit transaction: Download from www.wowebook.com ptg 1016 CHAPTER 31 Transaction Management and the Transaction Log begin tran print ‘Trancount before delete = ‘ + CONVERT(VARCHAR(2), @@trancount) DELETE FROM employee WHERE emp_id = ‘PMA42628M’ print ‘Trancount after delete = ‘ + CONVERT( VARCHAR(2), @@trancount) commit tran print ‘Trancount after commit = ‘ + CONVERT( VARCHAR(2), @@trancount) go This code gives the following results: Trancount before delete = 1 Trancount in trigger = 1 Trancount after delete = 1 Trancount after commit = 0 In this example, a transaction is already active when the DELETE is executed. The BEGIN TRAN statement initiates the transaction, and @@trancount is 1 before the DELETE is executed. The trigger becomes a part of that transaction, which is not committed until the COMMIT TRAN statement is executed. What would happen, however, if the trigger performed a rollback? You can find out by modifying the trigger to perform a rollback as follows: ALTER TRIGGER tD_employee ON employee FOR DELETE AS print ‘Trancount in trigger = ‘ + CONVERT(VARCHAR(2), @@trancount) ROLLBACK TRAN return Now rerun the previous batch. The outcome this time is as follows: Trancount before delete = 1 Trancount in trigger = 1 Msg 3609, Level 16, State 1, Line 3 The transaction ended in the trigger. The batch has been aborted. Notice in this example that the batch did not complete, as evidenced by the missing output from the last two print statements. When a rollback occurs within a trigger, SQL Server aborts the current transaction, continues processing the commands in the trigger, and after the trigger returns, aborts the rest of the batch and returns error message 3609 to indicate that the batch has been aborted because the transaction ended within the trigger. A ROLLBACK TRAN statement in a trigger rolls back all work to the first BEGIN TRAN statement. It is not possible to roll back to a specific named transaction, although you can roll back to a named savepoint, as discussed later in this section. Download from www.wowebook.com ptg 1017 Transactions and Triggers 31 Again, the batch and transaction are aborted when the trigger rolls back; any subsequent statements in the batch are not executed. The key concept to remember is that the trigger becomes an integral part of the statement that fired it and of the transaction in which that statement occurs. It is important to note, however, that although the batch is aborted immediately after the trigger that performed a rollback returns, any statements within the trigger that follow the ROLLBACK TRAN statement but before it returns are executed. For example, you can modify the previous trigger further to include a print statement after the ROLLBACK TRAN statement: ALTER TRIGGER tD_employee ON employee FOR DELETE AS print ‘Trancount in trigger = ‘ + CONVERT(VARCHAR(2), @@trancount) ROLLBACK TRAN print ‘Trancount in trigger after rollback = ‘ + CONVERT(VARCHAR(2), @@trancount) return Now, if you rerun the previous batch, you can see the print statement after the ROLLBACK TRAN but before the RETURN statement is executed: Trancount before delete = 1 Trancount in trigger = 1 Trancount in trigger after rollback = 0 Msg 3609, Level 16, State 1, Line 3 The transaction ended in the trigger. The batch has been aborted. Notice that the Trancount after the ROLLBACK TRAN in the trigger is now 0. If the trigger subsequently performed any data modifications following the ROLLBACK TRAN, they would now be running as AutoCommit transactions. For this reason, you must be sure to issue a RETURN statement to exit the trigger after a ROLLBACK TRAN is issued to avoid the trigger performing any operations that would then be automatically committing, leaving no opportunity to roll them back. Triggers and Multistatement Transactions Now let’s look at another example. First, you need to create a trigger to enforce referential integrity between the titles table and publishers table: The first statement is used to disable any previously created DDL triggers in the database which would prevent creating a new trigger. Download from www.wowebook.com ptg 1018 CHAPTER 31 Transaction Management and the Transaction Log DISABLE TRIGGER ALL ON titles go create trigger tr_titles_i on titles for insert as declare @rows int — create variable to hold @@rowcount select @rows = @@rowcount if @rows = 0 return if update(pub_id) and (select count(*) from inserted i, publishers p where p.pub_id = i.pub_id ) != @rows begin rollback transaction raiserror (‘Invalid pub_id inserted’, 16, 1) end return go Next, for the trigger to take care of the referential integrity, you might first need to disable the foreign key constraint on the titles table with a command similar to the following: alter table titles nocheck constraint FK__titles__pub_id__0F424F67 NOTE The system-generated name for the foreign key constraint may possibly be different on your database. You can use sp_helpconstraint titles to verify the name of the for- eign key constraint on the pub_id column of the titles table and use it in place of the constraint name specified in this example. Now, run a multistatement transaction with an invalid pub_id in the second insert statement: /* transaction inserts rows into a table */ begin tran add_titles insert titles (title_id, pub_id, title) values (‘XX1234’, ‘0736’, ‘Tuning SQL Server’) insert titles (title_id, pub_id, title) values (‘XX1235’, ‘abcd’, ‘Tuning SQL Server’) insert titles (title_id, pub_id, title) values (‘XX1236’, ‘0877’, ‘Tuning SQL Server’) commit tran go Msg 50000, Level 16, State 1, Procedure tr_titles_i, Line 10 Invalid pub_id inserted Msg 3609, Level 16, State 1, Line 4 The transaction ended in the trigger. The batch has been aborted. Download from www.wowebook.com ptg 1019 Transactions and Triggers 31 How many rows are inserted if ’abcd’ is an invalid pub_id? In this example, no rows are inserted because the ROLLBACK TRAN in the trigger rolls back all modifications made by the trigger, including the insert with the bad pub_id and all statements preceding it within the transaction. After the RETURN statement is encountered in the trigger, the rest of the batch is aborted. CAUTION You should never issue a BEGIN TRAN statement in a trigger because a transaction is already active at the time the trigger is executed. Rolling back to a named transaction in a trigger is illegal and generates a runtime error, rolling back the transaction and immediately terminating processing of the trigger and batch. The only transaction con- trol statements you should ever consider including in a trigger are ROLLBACK TRAN and SAVE TRAN. Using Savepoints in Triggers Although BEGIN TRAN statements are not recommended within a trigger, you can set a savepoint in a trigger and roll back to the savepoint. This technique rolls back only the operations within the trigger subsequent to the savepoint. The trigger and transaction it is a part of are still active until the transaction is subsequently committed or rolled back. The batch continues processing. Savepoints can be used to avoid a trigger’s arbitrarily rolling back an entire transaction. You can roll back to the named savepoint in the trigger and then issue a raiserror and return immediately to pass the error code back to the calling process. The calling process can then check the error status of the data modification statement and take appropriate action, either rolling back the transaction, rolling back to a savepoint in the transaction, or ignoring the error and committing the data modification. The following example shows a trigger that uses a savepoint: alter trigger tr_titles_i on titles for insert as declare @rows int — create variable to hold @@rowcount select @rows = @@rowcount if @rows = 0 return save tran titlestrig if update(pub_id) and (select count(*) from inserted i, publishers p where p.pub_id = i.pub_id ) != @rows begin rollback transaction titlestrig raiserror (‘Invalid pub_id inserted’, 16, 1) end return Download from www.wowebook.com ptg 1020 CHAPTER 31 Transaction Management and the Transaction Log This trigger rolls back all work since the savepoint and returns an error number of 50000. In the transaction, you can check for the error number and make the decision about whether to continue the transaction, roll back the transaction, or, if savepoints were set in the transaction, roll back to a savepoint and let the transaction continue. The following example rolls back the entire transaction if either of the first two inserts fail, but it rolls back to the named savepoint only if the third insert fails, allowing the first two to be committed: begin tran add_titles insert titles (title_id, pub_id, title) values (‘XX1234’, ‘0736’, ‘Tuning SQL Server’) if @@error = 50000 — roll back entire transaction and abort batch begin rollback tran add_titles return end insert titles (title_id, pub_id, title) values (‘XX1236’, ‘0877’, ‘Tuning SQL Server’) if @@error = 50000 — roll back entire transaction and abort batch begin rollback tran add_titles return end save tran keep_first_two — set savepoint for partial rollback insert titles (title_id, pub_id, title) values (‘XX1235’, ‘abcd’, ‘Tuning SQL Server’) if @@error = 50000 — roll back to save point, continue batch begin rollback tran keep_first_two end commit tran TIP When you use a savepoint inside a trigger, the trigger does not roll back the transac- tion. Therefore, the batch is not automatically aborted. You must explicitly return from the batch after rolling back the transaction to prevent subsequent statements from executing. NOTE Don’t forget to reenable the constraint on the titles table when you are finished testing: alter table titles check constraint FK__titles__pub_id__0F424F67 Download from www.wowebook.com ptg 1021 Transactions and Locking 31 Transactions and Locking SQL Server issues and holds on to locks for the duration of a transaction to ensure the isolation and consistency of the modifications. Data modifications that occur within a transaction acquire exclusive locks, which are then held until the completion of the trans- action. Shared locks, or read locks, are held for only as long as the statement needs them; usually, a shared lock is released as soon as data has been read from the resource (for example, row, page, table). You can modify the length of time a shared lock is held by using keywords such as HOLDLOCK in a query or setting the REPEATABLE_READ or SERIALIZABLE lock isolation levels. If one of these options is specified, shared locks are held until the completion of the transaction. What this means for you as a database application developer is that you should try to hold on to as few locks or as small a lock as possible for as short a time as possible to avoid locking contention between applications and to improve concurrency and applica- tion performance. The simple rule when working with transactions is to keep them short and keep them simple. In other words, you should do what you need to do in the most concise manner, in the shortest possible time. You should keep any extraneous commands that do not need to be part of the logical unit of work—such as SELECT statements, commands for dropping temporary tables, commands for setting up local variables, and so on—outside the transaction. To modify the manner in which a transaction and its locks can be handled by a SELECT statement, you can issue the SET TRANSACTION ISOLATION LEVEL statement. This state- ment allows the query to choose how much it is protected against other transactions modifying the data being used. The SET TRANSACTION ISOLATION LEVEL statement has the following mutually exclusive options: . READ COMMITTED—This setting is the default for SQL Server. Modifications made within a transaction are locked exclusively, and the changes cannot be viewed by other user processes until the transaction completes. Commands that read data only hold shared locks on the data for as long as they are reading it. Because other trans- actions are not blocked from modifying the data after you have read it within your transaction, subsequent reads of the data within the transaction might encounter nonrepeatable reads or phantom data. . READ UNCOMMITTED—With this level of isolation, one transaction can read the modifications made by other transactions prior to being committed. This is, there- fore, the least restrictive isolation level, but it is one that allows the reading of dirty and uncommitted data. This option has the same effect as issuing NOLOCK within SELECT statements, but it has to be set only once for your connection. This option should never be used in an application in which accuracy of the query results is required. . REPEATABLE READ—When this option is set, as data is read, locks are placed and held on the data for the duration of the transaction. These locks prevent other trans- actions from modifying the data you have read so that you can carry out multiple passes across the same information and get the same results each time. This isolation Download from www.wowebook.com ptg 1022 CHAPTER 31 Transaction Management and the Transaction Log level is obviously more restrictive than READ COMMITTED and READ UNCOMMITTED, and it can block other transactions. However, although it prevents nonrepeatable reads, it does not prevent the addition of new rows or phantom rows because only existing data is locked. . SERIALIZABLE—This option is the most restrictive isolation level because it places a range lock on the data. This prevents any modifications to the data being read from until the end of the transaction. It also avoids phantom reads by preventing rows from being added or removed from the data range set. . SNAPSHOT—Snapshot isolation specifies that data read by any statement will see only data modifications that were committed before the start of the transaction. The effect is as if the statements in a transaction see a snapshot of the committed data as it existed at the start of the transaction. The ALLOW_SNAPSHOT_ISOLATION database option must be set to ON for a transaction to specify the SNAPSHOT isolation level. READ_COMMITTED_SNAPSHOT Isolation In addition to the SNAPSHOT isolation level, SQL Server also supports a special form of read- committed isolation, referred to as READ_COMMITTED_SNAPSHOT. This form of isolation is similar to snapshot isolation, but unlike snapshot isolation, which sees the version of the data at the start of the transaction, read committed snapshot queries see the version of the data at the start of the statement. To enable the READ_COMMITTED_SNAPSHOT isolation level for queries, you need to enable the READ_COMMITTED_SNAPSHOT database option. Any queries that normally would run at the standard READ_COMMITTED isolation level automatically run at the READ_COMMITTED_SNAPSHOT isolation level, without requiring any code changes. For more information on transaction isolation levels and their effect on lock types, locking behavior, and performance, see Chapter 37, “Locking and Performance.” Coding Effective Transactions Poorly written or inefficient transactions can have a detrimental effect on concurrency of access to data and overall application performance. SQL Server can hold locks on a number of resources while the transaction is open; modified rows acquire exclusive locks, and other locks might also be held, depending on the isolation level used. To reduce locking contention for resources, transactions should be kept as short and efficient as possible. During development, you might not even notice that a problem exists; the problem might become noticeable only after the system load is increased and multiple users are executing transactions simultaneously. Following are some guidelines to consider when coding transactions to minimize locking contention and improve application performance: . Do not return result sets within a transaction. Doing so prolongs the transaction unnecessarily. Perform all data retrieval and analysis outside the transaction. Download from www.wowebook.com ptg 1023 Transaction Logging and the Recover y Process 31 . Never prompt for user input during a transaction. If you do, you lose all control over the duration of the transaction. (Even the best programmers miss this one on occa- sion.) On the failure of a transaction, be sure to issue the rollback before putting up a message box telling the user that a problem occurred. . Keep the start and end of a transaction together in the same batch or, better yet, use a stored procedure for the operation. . Keep the transaction short. Start the transaction at the point where you need to do the modifications. Do any preliminary work beforehand. . Make careful use of different locking schemes and transaction isolation levels. . If user input is unavoidable between data retrieval and modification and you need to handle the possibility of another user modifying the data values read, use optimistic locking strategies or snapshot isolation rather than acquiring and holding locks by using HOLDLOCK or other locking options. Chapter 37 covers optimistic locking methods and snapshot isolation in more detail. . Collect multiple transactions into one transaction, or batch transactions together, if appropriate. This advice might seem to go against some of the other suggestions, but it reduces the amount of overhead SQL Server will encounter to start, finish, and log the transactions. Transaction Logging and the Recovery Process Every SQL Server database has its own transaction log that keeps a record of all data modi- fications in a database (for example, insert, update, delete) in the order in which they occur. This information is stored in one or more log files associated with the database. The information stored in these log files cannot be modified or viewed effectively by any user process. SQL Server uses a write-ahead log. The buffer manager guarantees that changes are written to the transaction log before the changes are written to the database. The buffer manager also ensures that the log pages are written out in sequence so that transactions can be recovered properly in the event of a system crash. The following is an overview of the sequence of events that occurs when a transaction modifies data: 1. Writes a BEGIN TRAN record to the transaction log in buffer memory. 2. Writes data modification information to transaction log pages in buffer memory. 3. Writes data modifications to the database in buffer memory. 4. Writes a COMMIT TRAN record to the transaction log in buffer memory. 5. Writes transaction log records to the transaction log file(s) on disk. 6. Sends a COMMIT acknowledgment to the client process. Download from www.wowebook.com . ‘Tuning SQL Server ) insert titles (title_id, pub_id, title) values (‘XX1235’, ‘abcd’, ‘Tuning SQL Server ) insert titles (title_id, pub_id, title) values (‘XX1236’, ‘0877’, ‘Tuning SQL Server ). it reduces the amount of overhead SQL Server will encounter to start, finish, and log the transactions. Transaction Logging and the Recovery Process Every SQL Server database has its own transaction. Chapter 44, “Advanced Stored Procedure Programming and Optimization.” Transactions and Triggers SQL Server 2008 provides two types of Data Manipulation Language (DML) triggers: AFTER and INSTEAD OF.