ptg 1004 CHAPTER 31 Transaction Management and the Transaction Log begin a multistatement transaction that remains in effect until an explicit ROLLBACK or COMMIT statement is issued. Microsoft refers to this transation mode as IMPLICIT_ TRANSACTIONS. To enable implicit transactions for a connection in SQL Server 2008, you need to enable the IMPLICIT_TRANSACTIONS session setting using the following command: SET IMPLICIT_TRANSACTIONS ON After this option is turned on, transactions are implicitly started, if they are not already in progress, whenever any of the following commands are executed: ALTER TABLE CREATE DELETE DROP FETCH GRANT INSERT OPEN REVOKE SELECT TRUNCATE TABLE UPDATE Note that neither the ALTER VIEW nor ALTER PROCEDURE statement starts an implicit transaction. You must explicitly complete implicit transactions by issuing a COMMIT or ROLLBACK; a new transaction is started again on the execution of any of the preceding commands. If you plan to use implicit transactions, the main issue to be aware of is that locks are held until you explicitly commit the transaction. This can cause problems with concurrency and the system’s capability to truncate the transaction log. Even when using implicit transactions, you can still issue the BEGIN TRAN statement and create transaction nesting. In the following example, IMPLICIT_TRANSACTIONS ON is turned on to see the effect this has on the value of @@trancount. SQL Statements @@trancount Value SET IMPLICIT_TRANSACTIONS ON 0 go 0 INSERT INTO table1 1 UPDATE table2 1 COMMIT 0 Download from www.wowebook.com ptg 1005 Defining Transactions 31 As you can see in this example, if a BEGIN TRAN is issued while a transaction is still active, transaction nesting occurs, and a second COMMIT is required to finish the transaction. The main difference between this example and the preceding one is that here, a BEGIN TRAN is not required to start the transaction. The first INSERT statement initiates the transaction. When you are running in implicit transaction mode, you don’t need to issue a BEGIN TRAN statement; in fact, you should avoid doing so to prevent transaction nesting and the need for multiple commits. The following example shows the previous banking transaction using implicit transactions: set implicit_transactions on go declare @checking_account char(10), @savings_account char(10) select @checking_account = ‘0003456321’, @savings_account = ‘0003456322’ update account set balance = balance - $1000 where account_number = @checking_account if @@error != 0 begin rollback return end update savings_account set balance = balance + $1000 where account_number = @savings_account if @@error != 0 begin rollback go SELECT * FROM table1 1 BEGIN TRAN 2 DELETE FROM table1 2 COMMIT 1 go DROP TABLE table1 1 COMMIT 0 Download from www.wowebook.com ptg 1006 CHAPTER 31 Transaction Management and the Transaction Log return end commit This example is nearly identical to the explicit transaction example except for the lack of a BEGIN TRAN statement. In addition, when in implicit transaction mode, you cannot roll back to a named transaction because no name is assigned when the transaction is invoked implicitly. You can, however, still set savepoints and roll back to savepoints to partially roll back work within an implicit transaction. TIP If you need to know within your SQL code whether implicit transactions are enabled so you can avoid issuing explicit BEGIN TRAN statements, you can check the @@options function. @@options returns a bitmap that indicates which session-level options are enabled for the current session. If bit 2 is on, implicit transactions are enabled. The following code snippet can be used in stored procedures or SQL batches to check this value and decide whether to issue a BEGIN TRAN statement: if @@options & 2 != 2 — if bit 2 is not turned on BEGIN TRAN —a begin tran can be issued since implicit transactions are off Implicit Transactions Versus Explicit Transactions When would you want to use implicit transactions versus explicit transactions? If you are porting an application from another database environment, such as DB2 or Oracle, that uses implicit transactions, that application converts over to SQL Server more easily and with fewer code changes if you run in implicit transaction mode. Also, if the application you are developing needs to be ANSI compliant and run across multiple database plat- forms with minimal code changes, you might want to use implicit transactions. If you use implicit transactions in your applications, you need to be sure to issue COMMIT statements as frequently as possible to prevent leaving transactions open and holding locks for an extended period of time, which can have an adverse impact on concurrency and overall system performance. If an application is going to be hosted only on SQL Server, it is recommended that you use AutoCommit and explicit transactions so that changes are committed as quickly as possi- ble and so that only those logical units of work that are explicitly defined contain multi- ple commands within a transaction. Download from www.wowebook.com ptg 1007 Transactions and Batches 31 Transactions and Batches There is no inherent transactional quality to batches. As you have seen already, unless you provide the syntax to define a single transaction made up of several statements, each indi- vidual statement in a batch is its own separate transaction, and each statement is carried to completion or fails individually. The failure of a transaction within a batch does not cause the batch to stop processing. In other words, transaction flow does not affect process flow. After a ROLLBACK TRAN state- ment, processing continues with the next statement in the batch or stored procedure. For this reason, you should be sure to check for error conditions after each data modification within a transaction and exit the batch or stored procedure, as appropriate. Consider the banking transaction again, this time removing the RETURN statements: declare @checking_account char(10), @savings_account char(10) select @checking_account = ‘0003456321’, @savings_account = ‘0003456322’ begin tran update account set balance = balance - $1000 where account_number = @checking_account if @@error != 0 rollback tran update savings_account set balance = balance + $1000 where account_number = @savings_account if @@error != 0 rollback tran commit tran Assume that a check constraint on the account prevents the balance from being set to a value less than 0. If the checking account has less than $1,000 in it, the first update fails, and the T-SQL code catches the error condition and rolls back the transaction. At this point, the transaction is no longer active, but the batch still contains additional state- ments to execute. Without a return after the rollback, SQL Server continues with the next statement in the batch, which in this case is the update to the savings account. However, this now executes as its own separate transaction, and it automatically commits if it completes successfully. This is not the result you want because now that second update is its own separate unit of work, so you have no way to roll it back. The key concept to keep in mind here is that transaction flow does not affect program flow. In the event of an error within a transaction, you need to make sure you have the proper error checking and a means to exit the transaction in the event of an error. This Download from www.wowebook.com ptg 1008 CHAPTER 31 Transaction Management and the Transaction Log prevents the batch from continuing with any remaining modifications that were meant to be a part of the original transaction. As a general rule, a RETURN statement should almost always follow a rollback. In addition to being able to define multiple transactions within a batch, you can also have transactions that span multiple batches. For example, you could write an application that begins a transaction in one batch and then asks for user verification during a second batch. The SQL might look like this: First batch: use bigpubs2008 go begin transaction insert publishers (pub_id, pub_name, city, state) values (‘1111’, ‘Joe and Marys Books’, ‘Northern Plains’, ‘IA’) if @@error = 0 print ‘publishers insert was successful. Please go on.’ else print ‘publisher insert failed. Please roll back’ Second batch: update titles set pub_id = ‘1111’ where pub_id = ‘1234’ delete authors where state = ‘CA’ commit transaction Writing transactions that span multiple batches is almost always a bad idea. The locking and concurrency problems can become complicated, with awful performance implications. What if the application prompted for user input between batches, and the user went out to lunch? Locks would be held until the user got back and continued the transaction. In general, you want to enclose each transaction in a single batch, using conditional program- ming constructs to handle situations like the preceding example. Following is a better way to write that code: begin transaction insert publishers (pub_id, pub_name, city, state) values (‘1111’, ‘Joe and Marys Books’, ‘Northern Plains’, ‘IA’) if @@error = 0 begin print ‘publishers insert was successful. Continuing.’ update titles set pub_id = ‘1111’ where pub_id = ‘1234’ delete authors Download from www.wowebook.com ptg 1009 Transactions and Stored Procedures 31 where state = ‘CA’ commit transaction end else begin print ‘publisher insert failed. rolling back transaction’ rollback transaction end The important point in this example is that the transaction now takes place within a single batch for better performance and consistency. As you see in the next section, it is usually best to encode transactions in stored procedures for even better performance and to avoid the possibility of unfinished transactions. Transactions and Stored Procedures Because SQL code in stored procedures runs locally on the server, it is recommended that entire transactions be completely encapsulated within stored procedures to speed transac- tion processing. This way, the entire transaction executes within a single stored procedure call from the client application, rather than being executed across multiple requests. The less network traffic that occurs between the client application and SQL Server during transactions, the faster they can finish. Another advantage of using stored procedures for transactions is that doing so helps you avoid the occurrence of partial transactions—that is, transactions that are started but not fully committed. Using stored procedures this way also avoids the possibility of user inter- action within a transaction. The stored procedure keeps the transaction processing completely contained because it starts the transaction, carries out the data modifications, completes the transaction, and returns the status or data to the client. Stored procedures also provide the additional benefit that if you need to fix, fine-tune, or expand the duties of the transaction, you can do all this at one time, in one central loca- tion. Your applications can share the same stored procedure, providing consistency for the logical unit of work across your applications. Although stored procedures provide a useful solution to managing transactions, you need to know how transactions work within stored procedures and code for them appropriately. Consider what happens when one stored procedure calls another, and they both do their own transaction management. Obviously, they now need to work in concert with each other. If the called stored procedure has to roll back its work, how can it do so correctly without causing data integrity problems? The issues you need to deal with go back to the earlier topics of transaction nesting and transaction flow versus program flow. Unlike a rollback in a trigger (see the next section), a rollback in a stored procedure does not abort the rest of the batch or the calling proce- dure. Download from www.wowebook.com ptg 1010 CHAPTER 31 Transaction Management and the Transaction Log For each BEGIN TRAN encountered in a nested procedure, the transaction nesting level is incremented by 1. For each COMMIT encountered, the transaction nesting level is decre- mented by 1. However, if a rollback other than to a named savepoint occurs in a nested procedure, it rolls back all statements to the outermost BEGIN TRAN, including any work performed inside the nested stored procedures that has not been fully committed. It then continues processing the remaining commands in the current procedure as well as the calling procedure(s). To explore the issues involved, you can work with the sample stored procedure shown in Listing 31.1. The procedure takes a single integer argument, which it then attempts to insert into a table ( test_table). All data entry attempts—whether successful or not—are logged to a second table (auditlog). Listing 31.1 contains the code for the stored proce- dure and the tables it uses. LISTING 31.1 Sample Stored Procedure and Tables for Transaction Testing CREATE TABLE test_table (col1 int) go CREATE TABLE auditlog (who varchar(128), valuentered int null) go CREATE PROCEDURE trantest @arg INT AS BEGIN TRAN IF EXISTS( SELECT * FROM test_table WHERE col1 = @arg ) BEGIN RAISERROR (‘Value %d already exists!’, 16, -1, @arg) ROLLBACK TRANSACTION END ELSE BEGIN INSERT INTO test_table (col1) VALUES (@arg) COMMIT TRAN END INSERT INTO auditlog (who, valuentered) VALUES (USER_NAME(), @arg) return Now explore what happens if you call this stored procedure in the following way and check the values of the two tables: set nocount on EXEC trantest 1 EXEC trantest 2 SELECT * FROM test_table SELECT valuentered FROM auditlog go Download from www.wowebook.com ptg 1011 Transactions and Stored Procedures 31 The execution of this code gives the following results: col1 —————- 1 2 valuentered —————- 1 2 These would be the results you would expect because no errors would occur, and nothing would be rolled back. Now, if you were to run the same code a second time, test_table would still have only two rows because the procedure would roll back the attempted insert of the duplicate rows. However, because the procedure and batch are not aborted, the code would continue processing, and the rows would still be added to the auditlog table. The result would be as follows: set nocount on EXEC trantest 1 EXEC trantest 2 SELECT * FROM test_table SELECT valuentered FROM auditlog go Msg 50000, Level 16, State 1, Procedure trantest, Line 6 Value 1 already exists! Msg 50000, Level 16, State 1, Procedure trantest, Line 6 Value 2 already exists! col1 —————- 1 2 valuentered —————- 1 2 1 2 Download from www.wowebook.com ptg 1012 CHAPTER 31 Transaction Management and the Transaction Log Now explore what happens when you execute the stored procedure from within a transac- tion: set nocount on BEGIN TRAN EXEC trantest 3 EXEC trantest 1 EXEC trantest 4 COMMIT TRAN SELECT * FROM test_table SELECT valuentered FROM auditlog go The execution of this code gives the following results: Msg 50000, Level 16, State 1, Procedure trantest, Line 6 Value 1 already exists! Msg 266, Level 16, State 2, Procedure trantest, Line 0 Transaction count after EXECUTE indicates that a COMMIT or ROLLBACK TRANSACTION statement is missing. Previous count = 1, current count = 0. Msg 3902, Level 16, State 1, Line 6 The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION. col1 —————- 1 2 4 valuentered —————- 1 2 1 2 1 4 A number of problems are occurring now. For starters, you get a message telling you that the transaction nesting level was messed up. More seriously, the results show that the value 4 made it into the test_table table anyway and that the auditlog table picked up the inserts of 1 and the 4 but lost the fact that you tried to insert a value of 3. What happened? Download from www.wowebook.com ptg 1013 Transactions and Stored Procedures 31 Let’s examine this example one step at a time. First, you start the transaction and insert the value 3 into trantest. The stored procedure starts its own transaction, adds the value to test_table, commits that, and then adds a row to auditlog. Next, you execute the procedure with the value 1. This value already exists in the table, so the procedure raises an error and rolls back the transaction. Remember that a ROLLBACK undoes work to the outermost BEGIN TRAN—which means the start of this batch. This rolls back everything, including the insert of 3 into trantest and auditlog. The auditlog entry for the value 1 is inserted and not rolled back because it occurred after the transaction was rolled back and is a standalone, automatically committed statement now. You then receive an error regarding the change in the transaction nesting level because a transaction should leave the state of a governing procedure in the same way it was entered; it should make no net change to the transaction nesting level. In other words, the value of @@trancount should be the same when the procedure exits as when it was entered. If it is not, the transaction control statements are not properly balanced. Also, because the batch is not aborted, the value 4 is inserted into trantest, an operation that completes successfully and is automatically committed. Finally, when you try to commit the transaction, you receive the last error regarding a mismatch between BEGIN TRAN and COMMIT TRAN because no transaction is currently in operation. The solution to this problem is to write the stored procedures so that transaction nesting doesn’t occur and so the stored procedure rolls back only its own work. When a rollback occurs, it should return an error status so that the calling batch or procedure is aware of the error condition and can choose to continue or abort the work at that level. You can manage this by checking the current value of @@trancount and determining what needs to be done. If a transaction is already active, the stored procedure should not issue a BEGIN TRAN and nest the transaction; rather, it should set a savepoint. This allows the procedure to perform a partial rollback of its work. If no transaction is active, the procedure can safely begin a new transaction. The following SQL code fragment is an example of using this approach: DECLARE @trancount INT /* Capture the value of the transaction nesting level at the start */ SELECT @trancount = @@trancount IF (@trancount = 0) — no transaction is currently active, start one BEGIN TRAN mytran ELSE — a transaction is active, set a savepoint only SAVE TRAN mytran . . /* This is how to trap an error. Roll back either to your own BEGIN TRAN or roll back to the savepoint. Return an error code to the caller to indicate an internal failure. How the caller handles the transaction is up to the caller.*/ Download from www.wowebook.com . COMMIT statement is issued. Microsoft refers to this transation mode as IMPLICIT_ TRANSACTIONS. To enable implicit transactions for a connection in SQL Server 2008, you need to enable the. environment, such as DB2 or Oracle, that uses implicit transactions, that application converts over to SQL Server more easily and with fewer code changes if you run in implicit transaction mode. Also,. concurrency and overall system performance. If an application is going to be hosted only on SQL Server, it is recommended that you use AutoCommit and explicit transactions so that changes are