Tải bản đầy đủ (.pdf) (10 trang)

Microsoft SQL Server 2008 R2 Unleashed- P107 doc

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (205.1 KB, 10 trang )

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

×