bcquality/microsoft/knowledge/performance/split-read-only-and-write-paths-to-avoid-locktable.md
Jesper Schulz-Wedde dc14e7bb2a Update 6 knowledge articles to align with revised instructions
- performance/use-setloadfields-for-partial-records: Clarify that
  filter-only fields (SetRange/SetFilter) do not need to be listed in
  SetLoadFields — the DB resolves them via the index without hydrating
  the value into AL memory.

- performance/avoid-calcfields-in-loops: Add explicit exception for
  OnAfterGetRecord and OnValidate triggers, which are platform-managed
  and not developer-authored loops.

- performance/split-read-only-and-write-paths-to-avoid-locktable: Add
  ReadIsolation as the primary recommendation for read-only paths;
  LockTable reserved for confirmed write paths only.

- performance/prefer-direct-record-over-recordref: Scope the finding to
  hot unbounded loops (10k+ rows) over ledger-entry-scale tables;
  RecordRef in bounded/admin/setup contexts is not a concern.

- upgrade/enum-changes-must-be-additive-at-the-end: Replace direct
  ObsoleteState = Removed guidance with the two-stage workflow (Pending
  first, Removed later); reference use-obsolete-pending-before-removed.

- upgrade/use-datatransfer-for-large-dataset-initialization: Add the
  >300,000 records threshold as the concrete trigger for requiring
  DataTransfer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-24 10:36:08 +02:00

1.9 KiB

bc-version domain keywords technologies countries application-area
all
performance
locktable
read-only
write-path
contention
helper
al
w1
all

Split read-only and write paths so LockTable runs only when needed

Description

LockTable takes an exclusive write lock on the affected table for the remainder of the transaction. In a helper that is called from many read-only sites and a few write sites, placing LockTable unconditionally at the top serializes every reader on every other reader's lock — the helper becomes a system-wide contention point. The correct shape is a conditional structure: try the read-only path first, and only fall through to LockTable when the code genuinely needs to modify the table.

Best Practice

For paths that are read-only, prefer ReadIsolation over LockTable. Setting Rec.ReadIsolation := IsolationLevel::ReadCommitted on a record variable gives fine-grained, per-instance control over the isolation level without taking an update lock on the table for the rest of the transaction. Use LockTable only for paths that genuinely write to the table.

For helpers that may or may not modify records, factor the code so readers return immediately without a lock and only writers reach the LockTable call. A common pattern: attempt Rec.Get() first; if it returns the row, exit with the value; otherwise LockTable and proceed with the Insert. Document the pattern in a comment on the helper so callers understand why the LockTable is inside a branch.

See sample: split-read-only-and-write-paths-to-avoid-locktable.good.al.

Anti Pattern

A GetOrCreate helper that unconditionally calls Rec.LockTable() at the top, then Gets the row, then returns it. Every reader now blocks every other reader even though none of them intend to write. Under load the helper becomes the dominant bottleneck.

See sample: split-read-only-and-write-paths-to-avoid-locktable.bad.al.