3. Transaction Performance
In
the preceding section, we focused on limiting traffic between the
client and server tiers. When a Dynamics AX application is executed,
however, these tiers are just two of the three tiers involved. The
third tier is the database tier. You need to optimize the exchange of
packages between the server tier and the database tier, just as you do
between the client and server tiers. In this section, we explain how
you can optimize the transactional part of the execution of application
logic. The Dynamics AX application runtime helps you minimize calls
made from the server tier to the database tier by supporting set-based
operators and data caching. However, you should also do your part by
reducing the amount of data you send from the database tier to the
server tier. The less data you send, the faster that data is fetched
from the database. Fewer packages are sent back as well. These
reductions result in less memory consumed. All these efforts promote
faster execution of application logic, which results in smaller
transaction scope, less locking and blocking, and improved concurrency
and throughput.
3.1 Set-Based Data Manipulation Operators
The X++ language contains specific operators and classes to enable
set-based manipulation in the database. The set-based constructs have
an advantage over record-set constructs—they make fewer round-trips to
the database. The following X++ code example, which shows the selection
of several custTable records, each updated with a new value in the creditMax field, illustrates that a round-trip is required for the execution of the select statement and for each execution of update.
static void UpdateCustomers(Args _args) { CustTable custTable; ; ttsbegin;
while select forupdate custTable where custTable.CustGroup == '20' // Round trip to database { custTable.CreditMax = 1000; custTable.update(); // Round trip to database }
ttscommit; }
|
In a scenario in which 100 custTable records qualify for the update because the custGroup field equals 20, the number of round-trips would be 1 select + 100 updates = 101 round-trips.
The number of round-trips for the select statement might be slightly higher, depending on the number of custTable records that can be retrieved simultaneously from the database and sent to the AOS.
Theoretically,
you could rewrite the preceding scenario to result in only one
round-trip to the database by changing the X++ code as indicated in the
following example. The example shows how to use the update_recordset operator, resulting in a single SQL UPDATE statement being parsed to the database.
static void UpdateCustomers(Args _args) { CustTable custTable; ; ttsbegin;
update_recordset custTable setting creditMax = 1000 where custTable.CustGroup == '20'; // Single round trip to database ttscommit; }
|
For several reasons, however, using a custTable
record buffer doesn’t result in only one round-trip. We explain why in
the following subsections on the set-based constructs supported by the
Dynamics AX application runtime. In these sections, we also describe
features available that allow you to modify the preceding scenario to
ensure a single round-trip to the database, even when you’re using a custTable record buffer.
Important
None
of the following set-based operations improves performance when used on
temporary tables. The Dynamics AX application runtime always downgrades
set-based operations on temporary tables to record-based operations.
This downgrading happens regardless of how the table became a temporary
table (whether specified in metadata in the table’s properties,
disabled because of the configuration of the Dynamics AX application,
or explicitly stated in the X++ code using the table). Also, the
downgrade by the application runtime always invokes the doInsert, doUpdate, and doDelete methods on the record buffer, so no application logic in the overridden methods is executed. |
3.1.1 The insert_recordset Operator
The insert_recordset
operator enables the insertion of multiple records into a table in one
round-trip to the database. The following X++ code illustrates the use
of insert_recordset as the code copies sizes from one item to another item. The item to which the sizes are copied is selected from inventTable.
static void CopySizes(Args _args) { InventSize inventSizeTo; InventSize inventSizeFrom; InventTable inventTable; ; ttsbegin; insert_recordset inventSizeTo (ItemId, InventSizeId, Description, Name) select itemId from inventTable where inventTable.ItemId == '1000' join inventSizeId, description, name from inventSizeFrom where inventSizeFrom.ItemId == '1002'; ttscommit; }
|
The round-trip to the database involves the execution of three statements in the database:
The select part of the insert_recordset
statement is executed where the selected rows are inserted into a
temporarily created new table in the database. The syntax of the select statement when executed in Microsoft SQL Server is similar to SELECT <field list> INTO <temporary table> FROM <source tables> WHERE <predicates>.
The records from the temporary table are inserted directly into the target table using syntax such as INSERT INTO <target table> (<field list>) SELECT <field list> FROM <temporary table>.
The temporary table is dropped with the execution of DROP TABLE <temporary table>.
This
approach has a tremendous performance advantage over inserting the
records one by one, as shown in the following X++ code, which addresses
the same scenario as the preceding X++ code.
static void CopySizes(Args _args) { InventSize inventSizeTo; InventSize inventSizeFrom; InventTable inventTable; ; ttsbegin; while select itemId from inventTable where inventTable.ItemId == '1000' join inventSizeId, description, name from inventSizeFrom where inventSizeFrom.ItemId == '1002' { inventSizeTo.ItemId = inventTable.ItemId; inventSizeTo.InventSizeId = inventSizeFrom.InventSizeId; inventSizeTo.Description = inventSizeFrom.Description; inventSizeTo.Name = inventSizeFrom.Name; inventSizeTo.insert(); } ttscommit; }
|
If 10 sizes were copied, this scenario would result in 1 round-trip caused by the select statement and an additional 10 round-trips caused by the inserts, totaling 11 round-trips.
The insert_recordset
operation could be downgraded, however, from a set-based operation to a
record-based operation. The operation is downgraded if any of the
following conditions is true:
The table is entire-table cached.
The insert method or the aosValidateInsert method is overridden on the target table.
Alerts have been set to be triggered by inserts into the target table.
The database log has been configured to log inserts into the target table.
Record level security (RLS) is enabled on the target table. If RLS is enabled only on the source table or tables, insert_recordset isn’t downgraded to row-by-row operation.
The Dynamics AX application runtime automatically handles the downgrade and internally executes a scenario similar to the while select scenario shown in the preceding example.
Important
When
the Dynamics AX application runtime checks for overridden methods, it
determines only whether the methods are implemented. It doesn’t
determine whether the overridden methods contain only the default X++
code. A method is therefore considered to be overridden by the
application runtime even though it contains the following X++ code. public void insert() { super; }
|
Any
set-based insert is then downgraded. You need to remember to delete
such a method to avoid the downgrade, with its performance
ramifications. |
If
a table is not entire-table cached, however, you can avoid any
downgrade caused by the previously mentioned functionality. The record
buffer contains methods that turn off the checks that the application
runtime performs when determining whether to downgrade the insert_recordset operation.
Calling skipDataMethods(true) prevents the check that determines whether the insert method is overridden.
Calling skipAosValidation(true) prevents the check on the aosValidateInsert method.
Calling skipDatabaseLog(true) prevents the check that determines whether the database log is configured to log inserts into the table.
Calling skipEvents(true) prevents the check that determines whether any alerts have been set to be triggered by the insert event on the table.
The following X++ code, which includes the call to skipDataMethods(true), ensures that the insert_recordset operation is not downgraded because the insert method is overridden on the InventSize table.
static void CopySizes(Args _args) { InventSize inventSizeTo; InventSize inventSizeFrom; InventTable inventTable; ; ttsbegin; inventSizeTo.skipDataMethods(true); // Skip override check on insert. insert_recordset inventSizeTo (ItemId, InventSizeId, Description, Name) select itemId from inventTable where inventTable.ItemId == '1000' join inventSizeId, description, name from inventSizeFrom where inventSizeFrom.ItemId == '1002'; ttscommit; }
|
You must use skip methods with extreme caution because they can lead to the logic in the insert
method not being executed, events not being raised, and potentially,
the database log not being written to. If you override the insert method, you should use the cross-reference system to determine whether any X++ code calls skipDataMethods(true). If you don’t, the X++ code could fail to execute the insert method. Moreover, when you implement calls to skipDataMethods(true), make sure that not executing the X++ code in the overridden insert method won’t lead to data inconsistency.
Skip methods can be used only to influence whether the insert_recordset operation is downgraded. If a call to skipDataMethods(true) is implemented to prevent downgrading because the insert method is overridden, the overridden version of the insert
method will eventually be executed if the operation is still
downgraded. The operation would be downgraded, if, for example, the
database log had been configured to log inserts into the table. In the
previous example, the overridden insertInventSize table would be executed if the database log were configured to log inserts into the InventSize table because the insert_recordset operation would then revert to a while select scenario in which the overridden insert method would get called. method on the
Dynamics AX 2009 introduces support for literals in insert_recordset. The literal support for insert_recordset
was introduced primarily to support upgrade scenarios in which the
target table is populated with records from one or more source tables
(using joins) and one or more columns in the target table need to be
populated with a literal value that doesn’t exist in the source. The
following code example illustrates the usage of literals in insert_recordset.
static void InsertRecordSetLiteralExample(Args _args) { CusttTable customer; CustTable custTable; boolean flag = boolean::false; ;
ttsbegin; insert_recordset customer ( Name, Active ) select Name, flag from custTable; ttscommit; } |