Loading

Record Id Appears to Change After Commit in Partial-Success DML Transaction

Publiceringsdatum: Jun 28, 2026
Beskrivning

When using Database.insert() with allOrNone = false (partial success mode enabled), the record Id logged in an after insert Apex trigger during the initial DML attempt may differ from the final committed Id returned by Database.SaveResult.
For example, a developer logs the record Id inside an after insert trigger and later finds that the SaveResult returns a different Id for what appears to be the same record.

Why This Happens

In a partial-success DML transaction, Salesforce internally retries the DML operation after removing the records that failed validation or caused errors. During this retry, Salesforce generates new record Ids for the successfully inserted records. The Ids that were assigned during the first (failed) attempt are discarded. As a result, the Id logged inside the trigger during the first attempt differs from the Id in the final committed SaveResult.
This behavior is specific to partial-success DML operations using Database.insert(records, false) or Database.insert(records, dmlOptions) with AllowSave = true.

Steps to Reproduce:

Trigger

trigger AccountTrigger on Account (after insert) {
    for (Account acc : Trigger.new) {
        System.debug('After Insert Account Name: ' + acc.Name);
        System.debug('After Insert Account Id: ' + acc.Id);
    }
}

Anonymous Apex

List<Account> accountsToInsert = new List<Account>();

// Valid record
accountsToInsert.add(new Account(
    Name = 'Successful Account'
));

// Invalid record: Account Name is required
accountsToInsert.add(new Account());

Database.SaveResult[] results = Database.insert(accountsToInsert, false);

System.debug('---- FINAL SAVE RESULTS ----');

for (Integer i = 0; i < results.size(); i++) {
    if (results[i].isSuccess()) {
        System.debug('Success Index: ' + i);
        System.debug('Final committed Id from SaveResult: ' + results[i].getId());
    } else {
        System.debug('Failed Index: ' + i);

        for (Database.Error err : results[i].getErrors()) {
            System.debug('Error: ' + err.getStatusCode() + ' - ' + err.getMessage());
        }
    }
}

 

Observed Result

The debug log shows entries from the `after insert` trigger for the valid record.

These entries can appear for the first trigger invocation and again for the retry invocation.

The `Database.SaveResult` output shows the final committed Id for the successfully inserted record.

In case partial-success scenario, The Id logged during the first trigger invocation will be different from the final committed Id returned by `SaveResult`.

Lösning

Why This Happens

When Database.insert() is called with allOrNone = false:

  1. Salesforce attempts to insert all records in the list.
  2. If some records fail (due to validation rules, duplicate rules, or other errors), Salesforce retries the operation with only the valid records.
  3. The retry generates new Ids for the records being committed. The Ids from the first attempt are temporary and are not persisted.
  4. The after insert trigger fires once during the first attempt (with temporary Ids) and once during the retry (with the final committed Ids).

Developer Note

If your code uses static variables in triggers to prevent re-entry (a common pattern for preventing recursive trigger execution), be aware that the static variable set during the first trigger execution will still be true during the retry. This can cause your trigger logic to be skipped on the retry pass, resulting in incomplete processing for the successfully inserted records.
To handle this safely, design your static variable guards to count executions rather than using a simple boolean, or reset them explicitly in the catch block of your DML error handling logic.

Knowledge-artikelnummer

005386519

 
Laddar
Salesforce Help | Article