2. Isolation Levels
Prior versions of Dynamics AX supported
installations running on a SQL Server 2000 database. Dynamics AX 2009,
however, supports only installations running on the SQL Server 2005 and
SQL Server 2008 versions of the SQL Server database. With this change,
Dynamics AX no longer supports the READ UNCOMMITTED
isolation level and no longer provides the ability to read uncommitted
data. Installations running on a SQL Server 2005 database must have Read
Committed Snapshot Isolation (RCSI) enabled on the database.
RCSI prevents readers from being blocked behind
writers—the reader simply reads the prior version of the record. In
earlier versions of Dynamics AX, when installations were running on a
SQL Server 2000 database, readers could be blocked behind writers. The READ UNCOMMITTED
isolation level partly mitigated this issue when executing select
statements outside a transaction scope. Now that SQL Server 2000
databases are not supported, the isolation level is READ COMMITTED both
outside and inside a transaction scope in Dynamics AX.
The selectLocked record buffer method is essentially obsolete because executing selectLocked(false) on a record buffer before selecting any rows with it has no effect. Records are no longer read as uncommitted.pass.
Autocommit
As
explained earlier, explicit transaction mode is used inside a
transaction scope in Dynamics AX 2009 when it is running on SQL Server
2005. Outside the transaction scope, the autocommit transaction mode is
used. Any insert, update, or delete statement sent to the database in
autocommit mode is automatically committed. Although it’s still possible
to execute these statements outside a transaction scope, we advise you
not to because insert, update, or delete statements are committed
instantly to the database. In the event of an error, you wouldn’t be
able to roll back the database.
Transaction IDs
The Application Object Server (AOS) gives each
transaction in Dynamics AX a unique transaction ID only if one of the
following circumstances is true:
A record is inserted into a table in which the CreatedTransactionId property is set to Yes.
A record is updated on a table in which the ModifiedTransactionId property is set to Yes.
The X++ code explicitly requests a transaction by calling appl.curTransactionsId(true).
AOS Process Pool
The AOS doesn’t open a new process in the
database every time it needs a process. Any open process that is no
longer needed is placed in a pool of processes, and the AOS selects a
process from this pool when it needs one.
3. Concurrency Models
The Dynamics AX application runtime has built-in
support both in metadata and in X++ for the two concurrency models used
when updating data in the database: optimistic concurrency and
pessimistic concurrency. The optimistic model is also referred to as optimistic concurrency control (OCC), which is the term used in properties and in the application runtime.
The differences between the two models are the
methods they use to avoid “last writer wins” scenarios and,
consequently, to control the timing of locks requested in the database.
In a “last writer wins” scenario, two or more processes select and
update the same record with different data, each believing that it is
the only process updating that record. All processes commit their data
assuming that their version has been stored in the database. In reality,
only the data from the last writing process is stored in the database.
The data from the other processes is stored only for a moment, but there
is no indication that their data has been overwritten and lost.
Caution
In
Dynamics AX, you can develop “last writer wins” X++ code both
intentionally and unintentionally. If you don’t select records for
update before actually updating them, and you simply skip the
transaction check by calling skipTTSCheck(true) on the record buffer, you’re likely to overwrite a different version of the record than the one you selected. |
The Dynamics AX runtime manages the two
concurrency models generically, and you don’t need to decide whether to
use pessimistic or optimistic concurrency when you are writing
transactional X++ code. You can switch from optimistic to pessimistic
concurrency merely by changing a property on a table.
The following example illustrates what happens
from a locking perspective when executing X++ code using pessimistic
concurrency and running SQL Server 2005. The select statement contains the forupdate keyword that instructs the application runtime to execute a SELECT statement in the database with an UPDLOCK
hint added. The database is instructed to acquire an update lock on all
the selected records and to hold it until the end of the transaction,
thereby ensuring that no other process can modify the rows. Other
readers aren’t prevented from reading the rows, assuming that they don’t
require an update lock. Later, when the update method is called, an UPDATE
statement is executed in the database, knowing that no other process
has been able to modify the record since it was selected. At the same
time, the update lock is transformed into an exclusive lock, which is
held until the transaction is committed to the database. The exclusive
lock blocks readers requiring an update lock, as well as other writers.
static void UpdateCreditRating(Args _args)
{
CustTable custTable;
;
ttsbegin;
while select forupdate custTable // SELECT ... WITH (UPDLOCK)
// Acquire an update lock.
{
if (custTable.CreditMax < custTable.balanceMST())
{
if (custTable.CreditMax < 1000)
custTable.CreditRating = 'Good customer';
else
custTable.CreditRating = 'Solid customer';
custTable.update(); // UPDATE ... WHERE ACCOUNTNUM = <Id>
// Acquire an exclusive lock.
}
}
ttscommit;
}
|
The
following X++ code illustrates the same scenario as in the preceding
code, but it uses optimistic concurrency and SQL Server 2005. The select statement contains the forupdate keyword, which instructs the application runtime to execute a SELECT
statement without acquiring any locks. Because the process doesn’t hold
any locks, other processes can potentially modify the same rows. When
the update method is called, an UPDATE statement is executed in the database, at which time a predicate is added to determine whether the RecVersion field still contains the value that it contained when the record was originally selected.
Note
The RecVersion field is a 32-bit integer with a default value of 1, which is changed to a random value when the record is updated. |
If the RecVersion check fails when executing the UPDATE
statement, another process has modified the same record. If the check
doesn’t fail, an exclusive lock is acquired for the record and the
record is updated. In the event of a failure, the Dynamics AX
application runtime throws an update conflict exception.
static void UpdateCreditRating(Args _args)
{
CustTable custTable;
;
ttsbegin;
while select forupdate custTable // SELECT
{
if (custTable.CreditMax < custTable.balanceMST())
{
if (custTable.CreditMax < 1000)
custTable.CreditRating = 'Good customer';
else
custTable.CreditRating = 'Solid customer';
custTable.update(); // UPDATE ... WHERE ACCOUNTNUM = <Id>
// AND RECVERSION = <RecVersion>
// Acquire an exclusive lock.
}
}
ttscommit;
}
|
The two models differ in concurrency and
throughput. The concurrency difference lies in the number of locks held
at the time of the commit. Whether the preceding scenario is executed
using the optimistic or pessimistic model doesn’t affect the number of
exclusive locks the process holds because the number of custTable
records that need to be updated is the same. When you use the
pessimistic model, the update locks are held for the remainder of the custTable
records that were not updated. When you use the optimistic model, no
locks are held on rows that are not updated. The optimistic model allows
other processes to update these rows, and the pessimistic model
prevents other processes from updating the rows, which results in lower
concurrency. The optimistic model involves a risk, however: the update
could fail because other processes can update the same rows.
The optimistic model is better than the
pessimistic model for throughput. Fewer database resources are used
because fewer locks are acquired. When an update fails, however, the
optimistic model must retry, which leads to inferior throughput.
To illustrate the difference in the models, assume that the preceding X++ code example selected 100 custTable
rows but updated only 35 of them; the updated rows are distributed
evenly among the 100 selected rows. Using the pessimistic concurrency
model, a graphical representation would appear as shown in Figure 2.
If the optimistic concurrency model were used, the picture would look slightly different, as shown in Figure 3.
The number of exclusive locks would be the same, but there would be no
update locks. Also notice that no locks would be held from the time of
the selection of the rows until the first record was updated.
When choosing between the two models, you must
consider the potential risk or likelihood of an update conflict. If the
risk is minimal, the optimistic concurrency model most likely fits the
scenario; if the risk is significant, the pessimistic concurrency model
is probably your best choice. But the estimated cost of handling an
update conflict and retrying can also influence your decision.
Note
Although all the preceding examples mention updates only, the same RecVersion check is made when deleting records and is therefore also applicable in those scenarios. |