mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-08-06 09:26:52 +01:00
Bound commit checkpoints by key range
Use a capped ordered query to discover each checkpoint watermark before locking and processing only that key range. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 85be3fc4-5253-47b8-ba1b-6b8fd188fcea
This commit is contained in:
parent
bad62d2763
commit
e374a8a4b8
2 changed files with 43 additions and 12 deletions
|
|
@ -1,3 +1,17 @@
|
||||||
|
query 50127 "Perf Customer Chunk"
|
||||||
|
{
|
||||||
|
QueryType = Normal;
|
||||||
|
OrderBy = ascending(CustomerNo);
|
||||||
|
|
||||||
|
elements
|
||||||
|
{
|
||||||
|
dataitem(Customer; Customer)
|
||||||
|
{
|
||||||
|
column(CustomerNo; "No.") { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
codeunit 50128 "Perf Sample CommitInLoop Good"
|
codeunit 50128 "Perf Sample CommitInLoop Good"
|
||||||
{
|
{
|
||||||
procedure NormalizeCustomerNames()
|
procedure NormalizeCustomerNames()
|
||||||
|
|
@ -5,28 +19,45 @@ codeunit 50128 "Perf Sample CommitInLoop Good"
|
||||||
LastCustomerNo: Code[20];
|
LastCustomerNo: Code[20];
|
||||||
begin
|
begin
|
||||||
// The outer loop owns checkpoints; the per-row loop contains no Commit.
|
// The outer loop owns checkpoints; the per-row loop contains no Commit.
|
||||||
while NormalizeNextChunk(LastCustomerNo, 500) do
|
while NormalizeNextChunk(LastCustomerNo) do
|
||||||
Commit();
|
Commit();
|
||||||
end;
|
end;
|
||||||
|
|
||||||
local procedure NormalizeNextChunk(var LastCustomerNo: Code[20]; ChunkSize: Integer): Boolean
|
local procedure NormalizeNextChunk(var LastCustomerNo: Code[20]): Boolean
|
||||||
var
|
var
|
||||||
Customer: Record Customer;
|
Customer: Record Customer;
|
||||||
RowsInChunk: Integer;
|
CustomerChunk: Query "Perf Customer Chunk";
|
||||||
|
FirstCustomerNo: Code[20];
|
||||||
|
LastChunkCustomerNo: Code[20];
|
||||||
begin
|
begin
|
||||||
Customer.SetCurrentKey("No.");
|
CustomerChunk.TopNumberOfRows(500);
|
||||||
if LastCustomerNo <> '' then
|
if LastCustomerNo <> '' then
|
||||||
Customer.SetFilter("No.", '>%1', LastCustomerNo);
|
CustomerChunk.SetFilter(CustomerNo, '>%1', LastCustomerNo);
|
||||||
if not Customer.FindSet(true) then
|
CustomerChunk.Open();
|
||||||
|
if not CustomerChunk.Read() then begin
|
||||||
|
CustomerChunk.Close();
|
||||||
exit(false);
|
exit(false);
|
||||||
|
end;
|
||||||
|
|
||||||
|
FirstCustomerNo := CustomerChunk.CustomerNo;
|
||||||
|
repeat
|
||||||
|
LastChunkCustomerNo := CustomerChunk.CustomerNo;
|
||||||
|
until not CustomerChunk.Read();
|
||||||
|
CustomerChunk.Close();
|
||||||
|
|
||||||
|
Customer.SetCurrentKey("No.");
|
||||||
|
Customer.SetRange("No.", FirstCustomerNo, LastChunkCustomerNo);
|
||||||
|
if not Customer.FindSet(true) then begin
|
||||||
|
LastCustomerNo := LastChunkCustomerNo;
|
||||||
|
exit(true);
|
||||||
|
end;
|
||||||
|
|
||||||
repeat
|
repeat
|
||||||
Customer.Name := UpperCase(Customer.Name);
|
Customer.Name := UpperCase(Customer.Name);
|
||||||
Customer.Modify();
|
Customer.Modify();
|
||||||
LastCustomerNo := Customer."No.";
|
until Customer.Next() = 0;
|
||||||
RowsInChunk += 1;
|
|
||||||
until (RowsInChunk >= ChunkSize) or (Customer.Next() = 0);
|
|
||||||
|
|
||||||
|
LastCustomerNo := LastChunkCustomerNo;
|
||||||
exit(true);
|
exit(true);
|
||||||
end;
|
end;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
bc-version: [all]
|
bc-version: [all]
|
||||||
domain: performance
|
domain: performance
|
||||||
keywords: [commit, loop, transaction, lock, checkpoint, codeunit-run]
|
keywords: [commit, loop, transaction, lock, checkpoint, bounded, watermark, topnumberofrows, codeunit-run]
|
||||||
technologies: [al]
|
technologies: [al]
|
||||||
countries: [w1]
|
countries: [w1]
|
||||||
application-area: [all]
|
application-area: [all]
|
||||||
|
|
@ -13,11 +13,11 @@ application-area: [all]
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
Commit ends the current write transaction. Calling it inside a per-row loop produces one transaction per iteration and loses the ability to roll back the whole operation atomically; it also interferes with the platform's ability to batch write operations. Most loops need no explicit Commit at all — AL auto-commits the enclosing code module on successful completion (see `understand-implicit-transaction-boundary.md`). When the batch is too large for one transaction, the fix is not a per-row Commit but bounded checkpoints that each process N rows.
|
Commit ends the current write transaction. Calling it inside a per-row loop produces one transaction per iteration and loses the ability to roll back the whole operation atomically; it also interferes with the platform's ability to batch write operations. Most loops need no explicit Commit at all — AL auto-commits the enclosing code module on successful completion (see `understand-implicit-transaction-boundary.md`). When the batch is too large for one transaction, the fix is not a per-row Commit but bounded checkpoints that each retrieve and process the next N rows.
|
||||||
|
|
||||||
## Best Practice
|
## Best Practice
|
||||||
|
|
||||||
If the batch is large enough that a single transaction is untenable, use an outer loop that selects and finishes the next N rows. Commit only after the inner row loop has returned and the checkpoint state identifies where the next chunk starts. A `Codeunit.Run` boundary can also own a chunk when its implicit commit and error behavior fit the caller — see `codeunit-run-as-atomic-sub-operation.md`.
|
If the batch is large enough that a single transaction is untenable, use an ordered primary-key watermark and retrieve a bounded next-N window. `FindSet` is optimized for reading the complete filtered set and isn't implemented as `TOP X`, so calling it over the remaining tail and breaking after N rows does not bound retrieval. The sample uses a query capped by [`TopNumberOfRows`](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/methods-auto/query/queryinstance-topnumberofrows-method) to discover the next upper key, then locks and processes only that key range. Commit after the bounded inner loop returns and persist its upper key as the next watermark. Use a stable key and define how a later run handles records inserted at or below an already committed watermark. A `Codeunit.Run` boundary can also own a chunk when its implicit commit and error behavior fit the caller — see `codeunit-run-as-atomic-sub-operation.md`.
|
||||||
|
|
||||||
See sample: `avoid-commit-inside-loops.good.al`.
|
See sample: `avoid-commit-inside-loops.good.al`.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue