Skip to main content
Added reference to Order of Execution Overview.
Source Link
kennyc
  • 369
  • 2
  • 10

Inside an Apex before insert trigger, what is the correct way to get an exclusive lock all records of a particular object for the length of the trigger's transaction?

This trigger needs an exclusive lock to perform validation that can't easily be expressed any other way. The canonical example I see is to check for overlapping dates in a record. Plenty of questions and answers on how to do that, but none seem to address the race condition between doing the select followed by the insert.

It's our understanding that a single trigger runs serially within its own transaction. But it doesn't have exclusivity across multiple clients.

Example:

Check if a new record has overlapping dates with any existing records:

  • Client A: Enters before insert trigger
  • Client A: Fetches records and checks for overlapping dates.
  • Client B: Enters before insert trigger
  • Client B: Fetches records and checks for overlapping dates.
  • Client B: Writes new record to the database, trigger ends.
  • Client A: Now has a stale result with an insert pending.

The Salesforce documentation says to use FOR UPDATE to acquire an exclusive lock on certain records:

Apex Developer Guide: Locking Statements

Given the example in the documentation, is the following a valid (or even recommended) way to lock all records?

Account [] accts = [SELECT Id, StartDate, EndDate FROM Account FOR UPDATE];

Note that we're only likely to be looping over a small number of records. Think of it like a membership with monthly billing records. The dates for each monthly Billing record should not overlap with any others for a given membership.

Update 1:

Refer to: Salesforce Platform: Order of Execution Overview

When using FOR UPDATE in a SOQL query, is the "lock" effectively acquired in step 3 of the execution order and then released after step 19/20?

If so, is the following true:

  • The trigger using FOR UPDATE in step 3 will wait for all in-progress events on those records to complete before it can acquire the lock.

  • Once acquired, all subsequent events will then wait on step 1 until the event with the lock completes step 19/20?

  • The code that initiated the FOR UPDATE does not have to do any explicit clean-up to release the lock.

Inside an Apex before insert trigger, what is the correct way to get an exclusive lock all records of a particular object for the length of the trigger's transaction?

This trigger needs an exclusive lock to perform validation that can't easily be expressed any other way. The canonical example I see is to check for overlapping dates in a record. Plenty of questions and answers on how to do that, but none seem to address the race condition between doing the select followed by the insert.

It's our understanding that a single trigger runs serially within its own transaction. But it doesn't have exclusivity across multiple clients.

Example:

Check if a new record has overlapping dates with any existing records:

  • Client A: Enters before insert trigger
  • Client A: Fetches records and checks for overlapping dates.
  • Client B: Enters before insert trigger
  • Client B: Fetches records and checks for overlapping dates.
  • Client B: Writes new record to the database, trigger ends.
  • Client A: Now has a stale result with an insert pending.

The Salesforce documentation says to use FOR UPDATE to acquire an exclusive lock on certain records:

Apex Developer Guide: Locking Statements

Given the example in the documentation, is the following a valid (or even recommended) way to lock all records?

Account [] accts = [SELECT Id, StartDate, EndDate FROM Account FOR UPDATE];

Note that we're only likely to be looping over a small number of records. Think of it like a membership with monthly billing records. The dates for each monthly Billing record should not overlap with any others for a given membership.

Inside an Apex before insert trigger, what is the correct way to get an exclusive lock all records of a particular object for the length of the trigger's transaction?

This trigger needs an exclusive lock to perform validation that can't easily be expressed any other way. The canonical example I see is to check for overlapping dates in a record. Plenty of questions and answers on how to do that, but none seem to address the race condition between doing the select followed by the insert.

It's our understanding that a single trigger runs serially within its own transaction. But it doesn't have exclusivity across multiple clients.

Example:

Check if a new record has overlapping dates with any existing records:

  • Client A: Enters before insert trigger
  • Client A: Fetches records and checks for overlapping dates.
  • Client B: Enters before insert trigger
  • Client B: Fetches records and checks for overlapping dates.
  • Client B: Writes new record to the database, trigger ends.
  • Client A: Now has a stale result with an insert pending.

The Salesforce documentation says to use FOR UPDATE to acquire an exclusive lock on certain records:

Apex Developer Guide: Locking Statements

Given the example in the documentation, is the following a valid (or even recommended) way to lock all records?

Account [] accts = [SELECT Id, StartDate, EndDate FROM Account FOR UPDATE];

Note that we're only likely to be looping over a small number of records. Think of it like a membership with monthly billing records. The dates for each monthly Billing record should not overlap with any others for a given membership.

Update 1:

Refer to: Salesforce Platform: Order of Execution Overview

When using FOR UPDATE in a SOQL query, is the "lock" effectively acquired in step 3 of the execution order and then released after step 19/20?

If so, is the following true:

  • The trigger using FOR UPDATE in step 3 will wait for all in-progress events on those records to complete before it can acquire the lock.

  • Once acquired, all subsequent events will then wait on step 1 until the event with the lock completes step 19/20?

  • The code that initiated the FOR UPDATE does not have to do any explicit clean-up to release the lock.

Source Link
kennyc
  • 369
  • 2
  • 10

How do you acquire an exclusive lock on an object within an Apex before insert trigger?

Inside an Apex before insert trigger, what is the correct way to get an exclusive lock all records of a particular object for the length of the trigger's transaction?

This trigger needs an exclusive lock to perform validation that can't easily be expressed any other way. The canonical example I see is to check for overlapping dates in a record. Plenty of questions and answers on how to do that, but none seem to address the race condition between doing the select followed by the insert.

It's our understanding that a single trigger runs serially within its own transaction. But it doesn't have exclusivity across multiple clients.

Example:

Check if a new record has overlapping dates with any existing records:

  • Client A: Enters before insert trigger
  • Client A: Fetches records and checks for overlapping dates.
  • Client B: Enters before insert trigger
  • Client B: Fetches records and checks for overlapping dates.
  • Client B: Writes new record to the database, trigger ends.
  • Client A: Now has a stale result with an insert pending.

The Salesforce documentation says to use FOR UPDATE to acquire an exclusive lock on certain records:

Apex Developer Guide: Locking Statements

Given the example in the documentation, is the following a valid (or even recommended) way to lock all records?

Account [] accts = [SELECT Id, StartDate, EndDate FROM Account FOR UPDATE];

Note that we're only likely to be looping over a small number of records. Think of it like a membership with monthly billing records. The dates for each monthly Billing record should not overlap with any others for a given membership.