Transaction
is any interesting unit of work that takes time to complete and may fail.
Basically, all data access across the boundary needs to be logged as a Transaction
since it may fail and time consuming. For example, URL request, disk IO, JDBC query, search query, HTTP request, 3rd party API call etc.
Sometime if A needs call B which is owned by another team, although A and B are deployed together without any physical boundary. To make the ownership clear, there could be some Transaction
logged when A calls B.
Most of Transaction
should be logged in the infrastructure level or framework level, which is transparent to the application.
All CAT message will be constructed as a message tree and send to back-end for further analysis, and for monitoring. Only Transaction
can be a tree node, all other message will be the tree leaf. The transaction without other messages nested is an atomic transaction.
The Transaction
object itself is not thread-safe and may be accessed and used by only one thread at a time. However, the database operations being executed within the scope defined by begin
and end
are capable of highly concurrent execution.
Applications must manage the scope of any transaction by ensuring that whenever the begin
method is called, there is a concluding invocation of the end
method. The commit
and rollback
methods do not end the scope of a transaction; they merely signify the transaction's intended outcome when end
is invoked. Applications should follow either the try/finally
or the TransactionRunnable
pattern to ensure correctness.
commit
method. These are: commit
method does not return until all updates created by the transaction have been written to non-volatile storage (e.g., disk storage).commit
method does not return until all updates created by the transaction have been written to non-volatile storage. In addition, the committing transaction waits briefly in an attempt to recruit other concurrently running transactions to write their updates with the same physical I/O operation.commit
method returns before the updates have been recorded on non-volatile storage. Persistit attempts to write them within 100 milliseconds, but this interval is not guaranteed.You can specify a default policy in the Persistit initialization properties using the {@value com.persistit.Configuration#COMMIT_POLICY_PROPERTY_NAME}property or under program control using {@link Persistit#setDefaultTransactionCommitPolicy} . The default policyapplies to the {@link #commit()} method. You can override the default policyusing {@link #commit(CommitPolicy)}.
HARD and GROUP ensure each transaction is written durably to non-volatile storage before the commit
method returns. The difference is that GROUP can improve throughput somewhat when many transactions are running concurrently because the average number of I/O operations needed to commit N transactions can be smaller than N. However, for one or a small number of concurrent threads, GROUP reduces throughput because it works by introducing a delay to allow other concurrent transactions to commit within a single I/O operation.
SOFT commits are generally much faster than HARD or GROUP commits, especially for single-threaded applications, because the results of numerous update operations can be aggregated and written to disk in a much smaller number of I/O operations. However, transactions written with the SOFT commit policy are not immediately durable and it is possible that the recovered state of a database will be missing transactions that were committed shortly before a crash.
For SOFT commits, the state of the database after restart is such that for any committed transaction T, either all or none of its modifications will be present in the recovered database. Further, if a transaction T2 reads or updates data that was written by any other transaction T1, and if T2 is present in the recovered database, then so is T1. Any transaction that was in progress, but had not been committed at the time of the failure, is guaranteed not to be present in the recovered database. SOFT commits are designed to be durable within 100 milliseconds after the commit returns. However, this interval is determined by computing the average duration of recent I/O operations to predict the completion time of the I/O that will write the transaction to disk, and therefore the interval cannot be guaranteed.
Persistit schedules concurrently executing transactions optimistically, without locking any database records. Instead, Persistit uses a well-known protocol called Snapshot Isolation to achieve atomicity and isolation. While transactions are modifying data, Persistit maintains multiple versions of values being modified. Each version is labeled with the commit timestamp of the transaction that modified it. Whenever a transaction reads a value that has been modified by other transactions, it reads the latest version that was committed before its own start timestamp. In other words, all read operations are performed as if from a "snapshot" of the state of the database made at the transaction's start timestamp - hence the name "Snapshot Isolation."
Given that all updates written through transactions are created as versions within the MVCC scheme, a large number of versions can accumulate over time. Persistit reduces this number through an activity called "version pruning." Pruning resolves the final state of each version by removing any versions created by aborted transactions and removing obsolete versions no longer needed by currently executing transactions. If a value contains only one version and the commit timestamp of the transaction that created it is before the start of any currently running transaction, that value is called primordial. The goal of pruning is to reduce almost all values in a Persistit Tree to their primordial states because updating and reading primordial values is more efficient than the handling required for multiple version values. Pruning happens automatically and is generally not visible to the application.
Usually Snapshot Isolation allows concurrent transactions to commit without interference but this is not always the case. Two concurrent transactions that attempt to modify the same Persistit key-value pair before committing are said to have a "write-write dependency". To avoid anomalous results one of them must abort, rolling back any other updates it may have created, and retry. Persistit implements a "first updater wins" policy in which if two transactions attempt to update the same record, the first transaction "wins" by being allowed to continue, while the second transaction "loses" and is required to abort.
Once a transaction has aborted, any subsequent database operation it attempts throws a {@link RollbackException}. Application code should generally catch and handle the RollbackException
. Usually the correct and desired behavior is simply to retry the transaction. See try/finally
for a code pattern that accomplishes this.
A transaction can also voluntarily roll back. For example, transaction logic could detect an error condition that it chooses to handle by throwing an exception back to the application. In this case the transaction should invoke the {@link #rollback} method to explicit declare its intent to abort thetransaction.
Under Snapshot Isolation, transactions that read but do not modify data cannot generate any write-write dependencies and are therefore not subject to being rolled back because of the actions of other transactions. However, note that even if it modifies no data, a long-running transaction can force Persistit to retain old value versions for its duration in order to provide a snapshot view. This behavior can cause congestion and performance degradation by preventing very old values from being pruned. The degree to which this is a problem depends on the volume of update transactions being processed and the duration of long-running transactions.
The following code fragment illustrates a transaction executed with up to to RETRY_COUNT retries. If the commit
method succeeds, the whole transaction is completed and the retry loop terminates. If after RETRY_COUNT retries commit
has not been successfully completed, the application throws a TransactionFailedException
.
Transaction txn = Persistit.getTransaction(); int remainingAttempts = RETRY_COUNT; for (;;) { txn.begin(); // Begin transaction scope try { // // ...perform Persistit fetch, remove and store operations... // txn.commit(); // attempt to commit the updates break; // Exit retry loop } catch (RollbackException re) { if (--remainingAttempts < 0) { throw new TransactionFailedException(); { } finally { txn.end(); // End transaction scope. Implicitly // roll back all updates unless // commit completed successfully. } }
As an alternative, the application can embed the actual database operations within a {@link TransactionRunnable} and invoke the {@link #run} method toexecute it. The retry logic detailed in the fragment shown above is handled automatically by run
; it could be rewritten as follows:
Transaction txn = Persistit.getTransaction(); txn.run(new TransactionRunnable() { public void runTransaction() throws PersistitException, RollbackException { // //...perform Persistit fetch, remove and store operations... // } }, RETRY_COUNT, 0);
Persistit supports nested transactions by counting the number of nested {@link #begin} and {@link #end} operations. Each invocation ofbegin
increments this count and each invocation of end
decrements the count. These methods are intended to be used in a standard essential pattern, shown here, to ensure that the scope of of the transaction is reliably determined by the lexical the structure of the code rather than conditional logic:
txn.begin(); try { // // Application transaction logic here, possibly including // invocation of methods that also call txn.begin() and // txn.end(). // txn.commit(); } finally { txn.end(); }
This pattern ensures that the transaction scope is ended properly regardless of whether the application code throws an exception or completes and commits normally.
The {@link #commit} method performs the actual commit operation only when thecurrent nested level count (see {@link #getNestedTransactionDepth()}) is 1. That is, if begin
has been invoked N times, then commit
will actually commit the data only when end
is invoked the Nth time. Data updated by an inner (nested) transaction is never actually committed until the outermost commit
is called. This permits transactional code to invoke other code (possibly an opaque library supplied by a third party) that may itself begin
and commit
transactions.
Invoking {@link #rollback} removes all pending but uncommitted updates andmarks the current transaction scope as rollback pending. Any subsequent attempt to perform any Persistit operation, including commit
in the current transaction scope, will fail with a RollbackException
.
Application developers should beware that the {@link #end} method performs animplicit rollback if commit
has not completed. Therefore, if an application fails to call commit
, the transaction will silently fail. The end
method sends a warning message to the log subsystem when this happens, but does not throw an exception. The end
method is designed this way to allow an exception thrown within the application code to be caught and handled without being obscured by a RollbackException thrown by end
. But as a consequence, developers must carefully verify that the end
method is always invoked whether or not the transaction completes normally.
By default, application logic within the scope of a transaction can read two kinds of values: those that were committed by other transactions prior to the start of the current transaction (from the "snapshot") and those that were modified by the transaction itself. However, in some applications it is useful to control the visibility of modifications made by the current transaction. For example, update queries that select records to update and then change the very values used as selection criteria can produce anomalous results. See Halloween Problem for a succinct description of this issue. Persistit provides a mechanism to control visibility of a transaction's own modifications to avoid this problem.
While a transaction is executing, every updated value it generates is stored within a multi-version value and labeled with the transaction ID of the transaction that produced it and a small integer index (0-99) called the step.
The current step index is an attribute of the Transaction
object available from {@link #getStep}. The begin
method resets its value to zero. An application can invoke {@link #incrementStep} to incrementit, or {@link #setStep} to control its current value. Modifications createdby the transaction are labeled with the current step value.
When reading data, modifications created by the current transaction are visible to Persistit if and only if the step number they were assigned is less or equal to the Transaction
's current step number. An application can take advantage of this by controlling the current step index, for example, by reading data using step 0 while posting updates with a step value of 1.
As noted above, a Transaction
typically belongs to one thread for its entire lifetime and is not threadsafe. However, to support server applications which may manage a large number of sessions among a smaller number of threads, Persisit allows an application to manage sessions explicitly. See {@link Persistit#getSessionId()} and{@link Persistit#setSessionId(SessionId)}. The method {@link Persistit#getTransaction()} is sensitive to the thread's currentSessionId
, and therefore the following style of interaction is possible:
begin
, does some work and then returns control to a client.commit
and end
to complete the transaction.Transaction
or Exchange
can cause serious errors, including database corruption. Optimistic concurrency control works well when the likelihood of conflicting transactions - that is, concurrent execution of two or more transactions that modify the same database records - is low. For most applications this assumption is valid.
For best performance, applications in which multiple threads frequently operate on overlapping data such that roll-backs are likely, the application should implement its own locks to prevent or reduce the likelihood of collisions.
An application can examine counts of commits, rollbacks and rollbacks since the last successful commit using {@link #getCommittedTransactionCount()}, {@link #getRolledBackTransactionCount()} and{@link #getRolledBackSinceLastCommitCount()}, respectively.
@author peter @version 1.1Transaction handles are not free-threaded; transactions handles may be used by multiple threads, but only serially, that is, the application must serialize access to the handle. Once the {@link com.sleepycat.db.Transaction#abort Transaction.abort}, {@link com.sleepycat.db.Transaction#commit Transaction.commit} or{@link com.sleepycat.db.Transaction#discard Transaction.discard}methods are called, the handle may not be accessed again, regardless of the success or failure of the method. In addition, parent transactions may not issue any Berkeley DB operations while they have active child transactions (child transactions that have not yet been committed or aborted) except for {@link com.sleepycat.db.Environment#beginTransaction Environment.beginTransaction}, {@link com.sleepycat.db.Transaction#abort Transaction.abort} and {@link com.sleepycat.db.Transaction#commit Transaction.commit}.
To obtain a transaction with default attributes:
To customize the attributes of a transaction:Transaction txn = myEnvironment.beginTransaction(null, null);
TransactionConfig config = new TransactionConfig(); config.setDirtyRead(true); Transaction txn = myEnvironment.beginTransaction(null, config);
A single Transaction may be used to protect operations for any number of Databases in a given environment. However, a single Transaction may not be used for operations in more than one distinct environment.
Transaction handles are free-threaded; transactions handles may be used concurrently by multiple threads. Once the {@link Transaction#abort Transaction.abort} or {@link Transaction#commit Transaction.commit} methodis called, the handle may not be accessed again, regardless of the success or failure of the method, with one exception: the {@code abort} method maybe called any number of times to simplify error handling.
To obtain a transaction with default attributes:
Transaction txn = myEnvironment.beginTransaction(null, null);
To customize the attributes of a transaction:
TransactionConfig config = new TransactionConfig(); config.setReadUncommitted(true); Transaction txn = myEnvironment.beginTransaction(null, config);
Transaction options include whether optimistic concurrency control should be used for the current transaction, and whether values should be retained in JDO instances after transaction completion.
Transaction completion methods have the same semantics as javax.transaction UserTransaction, and are valid only in the non-managed, non-distributed transaction environment. @author Craig Russell @version 0.1
Note that some transaction implementations may only support transactions with at most one durable transaction participant, because of the need to communicate the outcome of prepared transactions to transaction participants following a crash when there are multiple durable participants.
All implementations of Transaction
must implement equals
and hashCode
. Two Transaction
s are equal if and only if they represent the same transaction.
The implementations of the {@link #join join}, {@link #abort abort}, and {@link #registerListener registerListener} methods of this interface are notthread-safe. Callers should insure that calls they make to these methods are made from the thread that created the transaction.
For more examples, have a look at {@link de.zib.scalaris.examples.TransactionReadExample}, {@link de.zib.scalaris.examples.TransactionWriteExample} and{@link de.zib.scalaris.examples.TransactionReadWriteExample}.
Transaction
interface provides for initiation and completion of transactions under user control. It is a sub-interface of the {@link PersistenceManager}that deals with options and transaction demarcation. Transaction options include whether optimistic concurrency control should be used for the current transaction, whether instances may hold values in the cache outside transactions, and whether values should be retained in the cache after transaction completion. These options are valid for both managed and non-managed transactions.
Transaction initiation and completion methods have similar semantics to javax.transaction.UserTransaction
when used outside a managed environment. When used in a managed environment, transaction initiation and completion methods may only be used with bean-managed transaction semantics.
@version 2.3
This interface represents a generic transaction interface defining the methods common between client and server transactions. @see TransactionState @author BEA Systems, NIST @version 1.2
TransactionManager
servers for use with transaction participants that implement the default transaction semantics. The semantics: The overall effect of executing a set of sibling pure transactions concurrently is always equivalent to some sequential execution.
Ancestor transactions can execute concurrently with child transactions, subject to the locking rules below.
Every transactional operation is described in terms of acquiring locks on objects; these locks are held until the transaction completes. Whatever the lock rules are, conflict rules are defined such that if two operations do not commute, then they acquire conflicting locks. A transaction can acquire a lock only if the conflicting locks are those held by ancestor transactions (or itself). When a subtransaction commits, its locks are inherited by the parent transaction.
If an object is defined to be created under a transaction, then the existence of the object is only visible within that transaction and its inferiors, but will disappear if the transaction aborts. If an object is defined to be deleted under a transaction, then the object is not visible to any transaction (including the deleting transaction) but will reappear if the transaction aborts. When a nested transaction commits, visibility state is inherited by the parent transaction.
Once a transaction reaches the VOTING
stage, if all execution under the transaction (and its subtransactions) has finished, then the only reasons the transaction can abort are: the manager crashes (or has crashed); one or more participants crash (or have crashed); or an explicit abort.
Transaction deadlocks are not guaranteed to be prevented or even detected, but managers and participants are permitted to break known deadlocks by aborting transactions.
An orphan transaction (it or one of its ancestors is guaranteed to abort) is not guaranteed to be detected.
Causal ordering information about transactions is not guaranteed to be propagated.
As long as a transaction persists in attempting to acquire a lock that conflicts with another transaction, the participant will persist in attempting to resolve the outcome of the transaction that holds the conflicting lock. @author Sun Microsystems, Inc. @see NestableTransaction @see net.jini.core.transaction.server.TransactionManager @see net.jini.core.transaction.server.NestableTransactionManager @see TransactionFactory @since 1.0
Depending upon the implementation of the channel, the transaction semantics may be strong, or best-effort only.
@see org.apache.flume.ChannelTransaction
@ @author Scott T. Weaver @version $ $The following features are supported:
Example code walkthrough (from the perspective of Transaction):
A transaction always involves just one execution thread. Several different threads may have their own transactions, and the transactions' ACID:ity properties guarantee that they will never see data in an intermediate state. This is accomplished by the thread locking implemented in the involved databases.
HeliDB has both read/write and read only transactions. It is up to the {@link org.helidb.Database} implementation used if they are treateddifferently. For instance, a {@code Database} could allow several concurrentread only transactions, but only one simultaneous transaction if that transaction is read/write.
A new transaction is started by any of the static {@link #startTransaction(boolean)} or {@link #getOrStartTransaction(boolean)}methods. If the thread already is involved in a transaction and wants to get that transaction's {@code Transaction} object, it can call any of the staticgetter methods.
Implementation note:
A database joins a {@code Transaction} via its{@link TransactionCollaborator}. It uses one collaborator object per transaction it is involved in. The collaborator keeps track of the state for the database associated with just that transaction.
@author Karl Gustafsson
@since 1.0
@see TransactionCollaborator
@see TransactionalDatabase
Let's walk through this example line by line. First we retrieve a Transaction object by invoking the {@link GraphDatabaseService#beginTx()} factory method.This creates a new Transaction instance which has internal state to keep track of whether the current transaction is successful. Then we wrap all operations that work with the node space in a try-finally block. At the end of the block, we invoke the {@link #finish() tx.success()} method to indicatethat the transaction is successful. As we exit the block, the finally clause will kick in and {@link #finish() tx.finish} will commit the transaction ifthe internal state indicates success or else mark it for rollback.
If an exception is raised in the try-block, {@link #success()} will never beinvoked and the internal state of the transaction object will cause {@link #finish()} to roll back the transaction. This is very important:unless {@link #success()} is invoked, the transaction will fail upon{@link #finish()}. A transaction can be explicitly marked for rollback by invoking the {@link #failure()} method.
Read operations inside of a transaction will also read uncommitted data from the same transaction.
join
), and that transaction must be open (through a call to begin
). All subsequent operations by the thread, including reads, writes, and lock acquisitions, are done under the thread�s current transaction. A thread may only operate on its current transaction. For example, a TransactionNotInProgressException
is thrown if a thread attempts to begin, commit, checkpoint, or abort a transaction prior to joining itself to that transaction.
A transaction is either open or closed. A transaction is open if a call has been made to begin
, but no call has been made to commit
or abort
. Once commit
or abort
is called, the transaction is closed. The method isOpen
can be called to determine the state of the transaction.
Read locks are implicitly obtained on objects as they are accessed. Write locks are implicitly obtained as objects are modified. Transaction
objects are transient, they cannot be stored in the database.
@author David Jordan (as Java Editor of the Object Data Management Group)
@version ODMG 3.0
@see TransactionNotInProgressException
prevalentSystem
in each Transaction. prevalentSystem
will be a freshly deserialized copy, so cannot reference anything in the Prevalent System. (-) doesn't break hard links
(-) What if we read newly written data (won't find it);
(-) complex #commit
(+) simple rollback
(+) Modified file is in place right away;
(+) easy #commit
A transaction is a sorted collection of modifications that is executed atomically. Transactions resemble {@link DatabaseInsertGroup}s, but they may contain modifications across multiple databases, creations and deletions of databases as well as snapshot operations.
BabuDB transactions are called lightweight, as their semantics differ from the ones typically provided by database systems. The most important differences are:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|