[Bernstein09] 2.2. Transactions

来源:百度文库 编辑:神马文学网 时间:2024/05/04 13:16:10

2.2. Transactions

  • The programming model; that is, the style in which application programs are written

  • The application programming interface (API); that is, the commands available to the application programmer

  • Components of the system software that support TP applications

Itis up to the application programmer to bracket the set of operationsthat should be executed as part of the same transaction. This sectionfocuses on the semantics that is implied by the transaction brackets.For the most part, we use pseudocode to express this bracketingexplicitly, because it is easy to understand and exposes the semantic issuesthat are at stake. Other styles of programming are described later inthis section. Product-specific programming models and APIs fortransaction bracketing are presented in Chapter 10. System software components that support TP applications are discussed in Chapter 3.

Transaction Bracketing

Transactionbracketing offers the application programmer commands to Start, Commit,and Abort a transaction. These are expressed explicitly in someprogramming models and implicitly in others, but in either case theseare the commands whose execution begins and terminates a transaction.

The commands to bracket a transaction are used to identify which operations execute in the scopeof a transaction. The Start command creates a new transaction. After anapplication invokes a Start command, all of its operations executewithin that transaction until the application invokes Commit or Abort.In particular, if the application calls a procedure, that procedureordinarily executes within the same transaction as its caller. Afterinvoking Commit or Abort, the application is no longer executing atransaction until it invokes Start again.

Sometimes,a procedure is designed to be executed either as an independenttransaction or as a step within a larger transaction. For example,consider the following two procedures:

  • DebitChecking(acct, amt). Withdraw a given amount of money ( amt) from a given checking account ( acct).

  • PayLoan(loan, amt). Pay back a given amount of money ( amt) on a given loan ( loan).

Eachof these procedures could execute as an independent ACID transaction.In that case, you would expect to see Start at the beginning of thebody of each of the procedures and Commit at the end. Exampleprocedures forDebitChecking andPayLoan are shown in Figure 2.1.

Figure 2.1. Explicit Transaction Brackets. TheDebitChecking and PayLoan procedures explicitly bracket theirtransactions with a Start command and a Commit or Abort command.
Boolean DebitChecking(acct, amt) {
int acct, amt;
Start;
Boolean success = true;
// Code to perform the withdrawal goes here.
// Set "success = false" if the withdrawal fails,
// e.g., due to insufficient funds
if success Commit else Abort;
return success;
}

Boolean PayLoan(loan, amt) {
int loan, amt;
Start;
Boolean success = true;
// Code to perform the payment goes here.
// Set "success = false" if the payment fails,
// e.g., because the loan has already been paid
if success Commit else Abort;
return success;
}

Aslong as a transaction executes a single procedure, it is quitestraightforward to bracket the transaction using Start, Commit, andAbort. Things get more complicated if a procedure that is running atransaction calls another procedure to do part of the work of thetransaction. For example, suppose there is a procedurePayLoan FromChecking(acct, loan, amt) that calls theDebitChecking andPayLoan procedures to withdraw money from a checking account to pay back part of a loan, as shown in Figure 2.2.

Figure 2.2. A Composite Transaction. The transaction PayLoanFromChecking is written by composing the DebitChecking and PayLoan procedures.
Boolean PayLoanFromChecking(acct, loan, amt) {
int acct, loan, amt;
Start;
if ? DebitChecking(acct, amt) {Abort; return false;};
if ? PayLoan(loan, amt) {Abort; return false;};
Commit;
return true;
}

We would like thePayLoanFromCheckingprocedure to execute as an ACID transaction. We therefore bracket thebody of the procedure with calls to Start and Commit. ThisPayLoanFromChecking transaction includes its calls to theDebitChecking andPayLoan procedures. However, there is a potential problem with this, namely, thatDebitChecking andPayLoan also invoke the Start and Commit commands. Thus, as they’re currently written,DebitChecking andPayLoan would execute separate transactions that commit independently ofPayLoanFromChecking, which is not what we want. That is, we cannot composeDebitChecking andPayLoan into a larger transaction. We call this the transaction composability problem.

Onesolution is to have the system ignore invocations of the Start commandwhen it is executed by a program that is already running within atransaction. In this approach, when thePayLoanFromChecking procedure calls theDebitChecking procedure, the Start command in theDebitChecking procedure in Figure 2.1would not cause a new transaction to be created. However, the systemcannot completely ignore this second Start command. It must rememberthat this second Start command was invoked, so it will know that itshould ignore the execution of the corresponding Commit command inDebitChecking. That is, the Commit command in Figure 2.1 should not commit the “outer” transaction created byPayLoanFromChecking.More generally, the system maintains a start-count for each executingapplication, which is initialized to zero. Each execution of the Startcommand increments the start-count and each Commit decrements it. Onlythe last Commit, which decrements the count back to zero, causes thetransaction to commit.

What if theDebitCheckingprocedure issues the Abort command? One possible interpretation is thatif an inner procedure calls Abort, then the transaction that theprocedure is executing really does need to abort. Thus, unlike theCommit command, the Abort command in theDebitChecking procedure causes an abort of the outer transaction created byPayLoanFromChecking.In some systems, it is simply an error for a procedure that hasexecuted a second Start command to subsequently invoke an Abortcommand. In others, the invocation of Abort is ignored. Anotherpossible interpretation is that it is an attempt to abort only the workthat was performed since the last Start command executed. Thissemantics is discussed in the later subsection, Nested Transactions.

Another solution to the transaction composability problem is to remove the Start and Commit commands fromDebitChecking andPayLoan, so they can be invoked within the transaction bracketed by thePayLoanFromChecking procedure. Using this approach, theDebitChecking procedure would be replaced by the one in Figure 2.3. To enableDebitChecking to execute as an independent transaction, one can write a “wrapper” procedureCallDebitChecking that includes the transaction brackets, also shown in Figure 2.3.This approach avoids the need to rewrite application code when existingprocedures are composed in new ways. Another programming model thatrealizes this benefit is described in a later subsection entitled, Transaction Bracketing in Object-Oriented Programming.

Figure 2.3. Enabling Composability. TheStart, Commit, and Abort commands are removed in this revised versionof the DebitChecking procedure (Figure 2.1), so it can be invoked in alarger transaction, such as PayLoanFromChecking (Figure 2.2). A wrapperprocedure CallDebitChecking is added, which includes the transactionbrackets needed to execute DebitChecking as an independent transaction.
Boolean DebitChecking(acct, amt) {
int acct, amt;
Boolean success = true;
// Code to perform the withdrawal goes here.
// Set "success = false" if the withdrawal fails,
// e.g., due to insufficient funds
return success;
}

Boolean CallDebitChecking(acct, amt) {
int acct, amt;
Start;
Boolean success = DebitChecking(acct, amt);
if success Commit else Abort;
return success;
}

Theimpact of the transaction composability problem is something that needsto be evaluated and understood in the context of whichever programmingmodel or models you are using.

Transaction Identifiers

As we explained in Chapter 1,each transaction has a unique transaction identifier (transaction ID),which is assigned when the transaction is started. The transaction IDis assigned by whichever component is responsible for creating thetransaction in response to the Start command. That component could be atransaction manager (see Section 1.4) or a transactional resource manager such as a database system, file system, or queue manager.

Thereare two major types of transaction IDs: global and local. Thetransaction manager assigns a global ID, which is needed when more thanone transactional resource participates in a transaction. If thetransactional resource managers also assign transaction IDs, then theseare local IDs that are correlated with the global transaction ID sincethey all refer to the same transaction.

Whenevera transaction accesses a transactional resource, it needs to supply itstransaction ID, to tell the resource’s manager on which transaction’sbehalf the access is being made. The resource manager needs thisinformation to enforce the ACID properties. In particular, it needs itfor write accesses, so that it knows which write operations topermanently install or undo when the transaction commits or aborts.

Whenan application program invokes Commit or Abort, it needs to pass thetransaction ID as a parameter. This tells the transaction manager whichtransaction it is supposed to commit or abort.

Sincethe application needs to supply its transaction ID to resource managersand the transaction manager, it needs to manage its transaction ID. Itcould do this explicitly. That is, the Start operation could return a transaction ID explicitly to the application, and the application could pass that transaction ID to every resource it accesses.

Mostsystems hide this complexity from the application programmer. Insteadof returning the transaction ID to the program P that invokes Start,the system typically makes the transaction ID part of a hidden context,which is data that is associated with P but is manipulated only by thesystem, not by P. In particular, using the context the systemtransparently attaches the transaction ID to all database operationsand Commit and Abort operations. This is more convenient forapplication programmers—it’s one less piece of bookkeeping for them todeal with. It also avoids errors, because if the application passes thewrong transaction identifier, the system could malfunction.

Typically,the hidden context is associated with a thread, which is a sequentialflow of control through a program. A thread can have only onetransaction ID in its context, so there is no ambiguity about whichtransaction should be associated with each database operation andCommit or Abort. Threads are discussed in detail in the next section.

Notice that there are no transaction IDs in Figure 2.1 through Figure 2.3.The transaction ID is simply part of the hidden program context.Throughout this chapter, we will assume that transaction IDs are hiddenin this way, although as we will see some programming models allowaccess to this transaction context.

Chained Transactions

Insome programming models, an application is assumed to be alwaysexecuting within a transaction, so there is no need for the developerto start a transaction explicitly. Instead, an application simplyspecifies the boundary between each pair of transactions. This“boundary operation” commits one transaction and immediately startsanother transaction, thereby ensuring that the program is alwaysexecuting a transaction. In IBM’s CICS product, the verb called syncpoint works in this way. Microsoft SQL Server offers an implicit transaction mode that works this way too.

This programming style is called chained transactions,because the sequence of transactions executed by a program forms achain, one transaction after the next, with no gaps in between. Thealternative is an unchainedmodel, where after a program finishes one transaction, it need notstart the execution of another transaction right away. For example,this can be done using the Start and Commit commands for explicittransaction bracketing. Most of today’s programming models use theunchained model, requiring that the developer explicitly defines thestart of each new transaction.

Onthe face of it, the unchained model sounds more flexible, since theremay be times when you would want an application to do work outside of atransaction. However, in fact there is really very little purpose init. The only benefit is in systems where a transaction has significantoverhead even if it doesn’t access recoverable data. In that case, theunchained model avoids this overhead.

Onthe other hand, the unchained model has two significant disadvantages.First, if the code that executes outside a transaction updates anytransactional resources, then each of those updates in effect executesas a separate transaction. This is usually more expensive than groupingsets of updates into a single transaction. That is, it is sometimesimportant to group together updates into a single transaction forperformance reasons. Second, the unchained model gives the programmeran opportunity to break the consistency property of transactions byaccidentally executing a set of updates outside of a transaction. Forthese reasons, the chained model usually is considered preferable tothe unchained model.

Transaction Bracketing in Object-Oriented Programming

Withthe advent of object-oriented programming for TP applications, a richerstyle of chained transaction model has become popular. In this approacheach method is tagged with a transaction attribute that indicates itstransactional behavior, thereby avoiding explicit transactionbracketing in the application code itself. The transaction attributecan have one of the following values:

  • Requires New: Every invocation of the method starts executing in a new transaction, whether or not the caller is already executing in a transaction.

  • Required: If the caller is already running within a transaction, then the called method executes within that transaction. If not, then the called method starts executing in a new transaction.

  • Supported: If the caller is already running within a transaction, then the called method executes within that transaction. If not, then the called method does not execute within a transaction.

  • Not Supported: The called method does not execute within a transaction, even if the program that created the object is running within a transaction.[1]

    [1] Microsoft supports an additional value, Disabled, which has the same transaction behavior as Not Supported. A newly created object uses the “context” of its caller, whereas for Not Supported the newly created object is given a fresh context of its own. Contexts are explained in Section 2.5.

Thisstyle of programming was introduced in the mid-1990s in MicrosoftTransaction Server, which evolved later into COM+ in Microsoft’s .NETEnterprise Services. In that system, a transaction attribute isattached to a component, which is a set of classes, and applies to allclasses in the component. In its intended usage, the caller creates anobject of the class (rather than calling a method of an existingobject), at which time the transaction attribute is interpreted todecide whether it is part of the caller’s transaction, is part of a newtransaction, is not part of any transaction, or throws an exception.The called object is destroyed when the transaction ends.

Theconcept of transaction attribute was adopted and extended by OMG’sCORBA standard and Enterprise Java Beans (EJB, now part of JavaEnterprise Edition (Java EE)). It is now widely used in transactionalmiddleware products, as well as in Web Services. In EJB, the attributestag each method and apply per method call, not just when the calledobject is created. A class can be tagged with a transaction attribute,in which case it applies to all untagged methods. EJB also addsattributes to cover some other transaction options, in particular,Mandatory, where the called method runs in the caller’s transaction ifit exists and otherwise throws an exception.

Microsoftintroduced per-method transaction attributes in Windows CommunicationFoundation in .NET 3.0. It uses separate attributes to specify whetherthe method executes as a transaction and whether the caller’stransaction context propagates to the called method (i.e., thedifference between Required and Requires New).

Let us call a method invocation top-levelif it caused a new transaction to be started. That is, it is top-levelif it is tagged with Requires New or is tagged with Required and itscaller was not executing in a transaction. Generally speaking, atransaction commits when its top-level method terminates without anerror. If it throws an exception during its execution, then itstransaction aborts.

Atop-level method can call other methods whose transaction attribute isRequired, Mandatory, or Supported. This submethod executes in the sametransaction as the top-level method. If the submethod terminateswithout error, the top-level method can assume that it is fine tocommit the transaction. However, the top-level method is not obligatedto commit, for example, if it encounters an error later in theexecution of another submethod. In some execution models, a submethodcan continue to execute after announcing that the transaction can becommitted as far as it is concerned.

Ifthe submethod throws an exception, then the top-level method must abortthe transaction. In some execution models, the exception immediatelycauses the transaction to abort, as if the submethod had issued theAbort command. In other models, it is left to the top-level method tocause the abort to happen.

Insteadof having a method automatically vote to commit or abort depending onwhether it terminates normally or throws an exception, an option isavailable to give the developer more explicit control. For example, inthe .NET Framework, a program can do this by calling SetComplete andSetAbort. Java EE is similar, offering the setRollbackOnly command fora subobject to tell the top-level object to abort.

Theapproach of using transaction attributes is declarative in that theattributes are attached to interface definitions or methodimplementations. Microsoft’s .NET framework also offers a runtimelayer, exposed through the class TransactionScope, that allows aprogram to invoke the functionality of the transaction bracketingattributes shown previously. A program defines a transaction bracket bycreating a TransactionScope object with one of the following options:

  • Requires New: The program starts executing within a new transaction, whether or not it was previously executing in the context of a transaction.

  • Required: If the program was executing in the context of a transaction, then it continues doing so. Otherwise, it starts a new transaction.

  • Suppress: The program is now executing outside of a transaction.

In the case of Requires New and Suppress, if the program was running within a transaction T when it created the new transaction scope S, then T remains alive but has no activity until S exits.

Additional details of these approaches to transaction bracketing appear in Section 10.3 for .NET and Section 10.4 for Java EE.

Nested Transactions

The nested transactionprogramming model addresses the transaction composability problem bycapturing the program-subprogram structure of an application within thetransaction structure itself. In nested transactions, each transactioncan have subtransactions. For example, thePayLoanFromChecking transaction can have two subtransactionsDebitChecking andPayLoan.

Likeordinary “flat” (i.e., non-nested) transactions, subtransactions arebracketed by the Start, Commit, and Abort operations. In fact, theprograms of Figure 2.1 and Figure 2.2could be a nested transaction. What is different about nestedtransactions is not the bracketing operations—it’s their semantics.They behave as follows:

  1. If a program is already executing inside a transaction and issues a Start command, then Start creates a subtransaction of its parent transaction, rather than creating a new, independent transaction. For example, if DebitChecking is called from PayLoanFromChecking, the Start in DebitChecking starts a subtransaction.

  2. If a program is not already executing inside a transaction and issues a Start command, then Start creates a new, independent transaction, called a top-level transaction, which is not a subtransaction of another transaction. For example, Start in PayLoanFromChecking creates a top-level transaction.

  3. The Commit and Abort operations executed by a top-level transaction have their usual semantics. That is, Commit permanently installs the transaction’s updates and allows them to be read by other transactions. Abort undoes all the transaction’s updates. For example, Commit and Abort in PayLoanFromChecking have these effects.

  4. If a subtransaction S aborts, then all the operations of S are undone. This includes all the subtransactions of S. However, the abort does not cause the abort of S’s parent. The parent is simply notified that its child subtransaction aborted. For example, Abort in DebitChecking aborts the subtransaction, but not its parent transaction that was started by PayLoanFromChecking.

  5. While a subtransaction is executing, data items that it has updated are isolated and hence not visible to other transactions and subtransactions (just like the flat transaction model). For example, if PayLoanFromChecking executed its subtransactions DebitChecking and PayLoan concurrently and those subtransactions read and wrote some shared data (which they don’t in this example), then DebitChecking would not see PayLoan’s updates until after PayLoan commits, and PayLoan would not see DebitChecking’s updates until after DebitChecking commits.

  6. When a subtransaction commits, the data items it has updated are made visible to other subtransactions. For example, after PayLoan commits, any data it has updated would be visible to DebitChecking (if they shared data).

Considerthe properties of subtransactions relative to the ACID properties. Rule(4) means that a subtransaction is atomic (i.e., all-or-nothing)relative to other subtransactions of the same parent. Rule (5) meansthat a subtransaction is isolated relative to other transactions andsubtransactions. However, a subtransaction is not durable. Rule (6)implies that its results become visible once it commits, but by rule(3) the results become permanent only when the top-level transactionthat contains it commits.

The nested transaction model provides a nice solution to the transaction composability problem. In our example,DebitChecking andPayLoan in Figure 2.1 can execute as subtransactions within a top-level transaction executed byPayLoanFromChecking or as independent top-level transactions, without writing an artificial wrapper transaction likeCallDebitChecking in Figure 2.3.

Althoughnested transactions are appealing from an application programmingperspective, they are not supported in many commercial products.

Exception Handling

Anapplication program that brackets a transaction must say what to do ifthe transaction fails and therefore aborts. For example, suppose theprogram divides by zero, or one of the underlying database systemsdeadlocks and aborts the transaction. The result would be an unsolicited abort—onethat the application did not cause directly by calling the Abortcommand. Alternatively, the whole computer system could go down. Forexample, the operating system might crash, in which case all thetransactions that were running at the time of the crash are affected.Thus, an application program that brackets a transaction must provideerror handling for two types of exceptions—transaction failures andsystem failures.

For each type of exception, the application should specify an exception handler,which is a program that executes after the system recovers from theerror. To write an exception handler, a programmer needs to knowexactly what state information is available to the exception handler;that is, the reason for the error and what state was lost due to theerror. Two other issues are how the exception handler is called andwhether it is running in a transaction.

Informationabout the cause of the abort should be available to the exceptionhandler, usually as a status variable that the exception handler canread. If the abort was caused by the execution of a program statement,then the program needs to know both the exception that caused thestatement to malfunction and the reason for the abort—they might not bethe same. For example, it’s possible that there was some error in theassignment statement due to an overflow in some variable, but the realreason for the abort was an unavailable database system. The exceptionhandler must be able to tell the difference between these two kinds ofexceptions.

Whena transaction aborts, all the transactional resources it accessed arerestored to the state they had before the transaction started. This iswhat an abort means, undo all the transaction’s effects.Nontransactional resources—such as a local variable in the applicationprogram, or a communications message sent to another program—arecompletely unaffected by the abort. In other words, actions onnontransactional resources are not undone as a result of the abort.

It’sgenerally best if a transaction failure automatically causes theprogram to branch to an exception handler. Otherwise the applicationprogram needs an explicit test, such as anIF-statement,after each and every statement, which checks the status returned by theprevious statement and calls the appropriate exception handler in theevent of a transaction abort.

Inthe chained model, the exception handler is automatically part of a newtransaction, because the previous transaction aborted and, bydefinition, the chained model is always executing inside of sometransaction. In the unchained model, the exception handler isresponsible for demarcating a transaction in which the exceptionhandling logic executes. It could execute the handler code outside of a transaction, although as we said earlier this is usually undesirable.

Ifthe whole system goes down, all the transactions that were active atthe time of the failure abort. Since a system failure causes thecontents of main memory to be lost, transactions cannot resumeexecution when the system recovers. So the recovery procedure fortransaction programs needs to apply to the application as a whole, notto individual transactions. The only state that the recovery procedurecan rely on is information that was saved in a database or some otherstable storage area before the system failed. A popular way to do thisis to save request messages on persistent queues. The technology to dothis is described in Chapter 4.

Some applications execute several transactions in response to a user request. This is called a business process or workflow.If the system fails while a business process is executing, then it maybe that some but not all of the transactions involved in the businessprocess committed. In this case, the application’s exception handlermay execute compensating transactions for the business process’transactions that already committed. Business process engines typicallyinclude this type of functionality. More details appear in Chapter 5.

Savepoints

Ifa transaction periodically saves its state, then at recovery time theexception handler can restore that state instead of undoing all thetransaction’s effects. This idea leads to an abstraction calledsavepoints.

A savepoint is a point in a program where the application saves all its state, generally by issuing a savepoint command.The savepoint command tells the database system and other resourcemanagers to mark this point in their execution, so they can returntheir resources to this state later, if asked to do so. This is usefulfor handling exceptions that only require undoing part of the work ofthe transaction, as in Figure 2.4.

Figure 2.4. Using Savepoints. The program saves its state at savepoint “A.” It can restore the state later if there’s an error.
void Application
{ Start;
do some work;
. . .
Savepoint ("A");
do some more work;
. . .
if (error)
{ Restore ("A");
take corrective action;
Commit;
}
else Commit;
}

Asavepoint can be used to handle broken input requests. Suppose atransaction issues a savepoint immediately after receiving an inputrequest, as in the programApplication in Figure 2.5.If the system needs to spontaneously abort the transaction, it need notactually abort, but instead can roll back the transaction to its firstsavepoint, as inExceptionHandlerForApplication in Figure 2.5.This undoes all the transaction’s updates to transactional resources,but it leaves the exception handler with the opportunity to generate adiagnostic and then commit the transaction. This is useful if thetransaction needs to abort because there was incorrect data in therequest. If the whole transaction had aborted, then theget-input-request operation would be undone, which implies that therequest will be re-executed. Since the request was incorrect, it isbetter to generate the diagnostic and commit. Among other things, thisavoids having the request re-execute incorrectly over and over, forever.

Figure 2.5. Using Savepoints for Broken Requests. Theapplication’s savepoint after getting the request enables its exceptionhandler to generate a diagnostic and then commit. If the transactionwere to abort, the get-input-request would be undone, so the brokenrequest would be re-executed.
Void Application                    Void ExceptionHandlerForApplication
{ Start; { Restore ("B");
get-input-request; generate diagnostic;
Savepoint ("B"); Commit;
do some more work; }
Commit;
}

Unfortunately,in some execution models the exception handler of a transactionalapplication must abort the transaction. In this case, a mechanismoutside the transaction needs to recognize that the broken requestshould not be re-executed time after time. Queuing systems usuallyoffer this function, which is described in Chapter 4.

Somedatabase systems support the savepoint feature. Since the SQL standardrequires that each SQL operation be atomic, the database system doesits own internal savepoint before executing each SQL update operation.That way, if the SQL operation fails, it can return to its state beforeexecuting that operation. Since the database system supports savepointsanyway, only modest additional work is needed to have it makesavepoints available to applications.

Ingeneral, savepoints seem like a good idea, especially for transactionsthat execute for a long time, so that not all their work is lost in theevent of a failure. Although it’s available in some systems, it’s afeature that reportedly is not widely used by application programmers.

Using Savepoints to Support Nested Transactions

Sincea savepoint can be used to undo part of a transaction but not all ofit, it has some of the characteristics of nested transactions. In fact,if a transaction executes a sequential program, then it can usesavepoints to obtain the behavior of nested transactions if the systemadheres to the following rules:

  1. When a subtransaction first accesses a resource manager, it issues a savepoint operation.

  2. When a subtransaction aborts, the system restores the savepoint that the subtransaction previously established at each resource manager that the subtransaction accessed.

  3. To commit a subtransaction, no special action is required by the resource managers. However, future accesses to resource managers are now done on behalf of the subtransaction’s parent.

Thisimplementation works only if the transaction program is sequential. Ifit has internal concurrency, then it can have concurrently executingsubtransactions, each of which can independently commit or abort. Sincea savepoint applies to the state of the top-level transaction, therewill not always be a savepoint state that can selectively undo onlythose updates of one of the concurrently executing subtransactions.

Consider the example in Figure 2.6, wherefunctionX starts a transaction and then callsfunctionY andfunctionZ concurrently, indicated by the “concurrent block” bracketed bycobegin andcoend. BothfunctionY andfunctionZaccess a resource manager RM_A that supports savepoints. Each of themhas transaction brackets and therefore should run as a subtransaction.SincefunctionY andfunctionZ are executing concurrently, their operations can be interleaved in any order. Consider the following steps:

1. functionY accesses RM_A, and since this is its first access it issues a savepoint at RM_A (rule 1, in the previous list).

2. functionZ accesses RM_A, and therefore also issues a savepoint at RM_A (rule 1 in the previous list).

3. functionY performs an update at RM_A.

4. functionZ performs an update at RM_A.

5. functionZ commits its subtransaction.

6. functionY aborts its subtransaction.

Figure 2.6. Savepoints Aren’t Enough for Concurrent Subtransactions. If functionZ commits and functionY aborts, there is no savepoint in RM_A that produces the right state.


According to rule 2 (in the previous list), in step 6 the system should restore the savepoint created on behalf offunctionY. However, this will undo the update performed byfunctionZ, which is incorrect, sincefunctionZ commits in step 5.