Quando un record viene aggiornato o creato, Salesforce mette un blocco su quel record per impedire ad un'altra operazione di aggiornarlo contemporaneamente e causare incoerenze sui dati.
Questi blocchi normalmente durano alcuni secondi e quando vengono rilasciati, altre operazioni possono fare qualunque elaborazione sul record in questione. Tuttavia, una determinata transazione può attendere solo un massimo di 10 secondi per il rilascio di un blocco, altrimenti scadrà.
Perchè e quando i record vengono bloccati dipende dall'operazione che si sta eseguendo e dal record padre su cui si sta lavorando.
La pagina Force.com Record Locking fornisce informazioni dettagliate al riguardo ed è altamente consigliato leggere il suo contenuto.
Scenari comuni che impediscono lo sblocco dei record
a. Email-To-Case
Quando un'email viene elaborata via email-to-case, i triggers sull'oggetto EmailMessage o sugli oggetti correlati (ad esempio l'Account padre) tenteranno di bloccare tali record per l'elaborazione. Se un altro processo sta mantenendo un blocco su questi record e l'elaborazione dell'email deve attendere più di 10 secondi, si verificherà un timeout e comparirà l'errore "unable to lock row".
b. Apex Triggers/API
Supponiamo che ci sia un After Insert nel Trigger sui Tasks, che viene eseguito per circa 14 secondi durante l'elaborazione di un processo. Questo trigger verrà eseguito quando viene creato un Task. Quando i Tasks vengono creati e sono legati ad un Account, Salesforce posiziona un blocco sull'Account padre durante la creazione del Task. Ciò significa che l'account non può essere aggiornato mentre è in corso la creazione del Task. In questo caso, è possibile ridurre l'errore "unable to lock row" usando il Salesforce Locking Statements.
Account [] accts = [SELECT Id FROM Account LIMIT 2 FOR UPDATE];
Scenario:
1. L'utente A importa un Task tramite il data loader e lo assegna ad un record Account esistente. Quando il Task viene inserito, l'Apex Trigger viene attivato.
2. Solo 2 secondi dopo che l'utente A ha iniziato l'insert tramite data loader, l'utente B sta modificando manualmente lo stesso record Account a cui è legato il Task.
3. Quando l'utente B fa clic su Salva, internamente Salesforce tenta di posizionare un blocco sull'Account, ma l'Account è già bloccato, quindi Salesforce non può posizionare un altro blocco. L'Account era già stato bloccato dalla creazione del Task.
La seconda transazione attende quindi che il blocco venga rimosso. Poiché il Task richiede circa 14 secondi per essere creato, il blocco viene mantenuto per 14 secondi. La seconda transazione (da parte dell'utente B) scade, poiché può attendere solo un massimo di 10 secondi.
In questo caso, l'utente B visualizzerà l'errore a schermo "unable to lock row". Apex Tests possono inoltre incorrere in blocchi se eseguiti su dati di produzione.
c. Bulk API
L'inserimento o l'aggiornamento di record tramite Bulk API può causare più aggiornamenti contemporaneamente sullo stesso record padre, poiché i batches vengono elaborati in parallelo. Ad esempio, se due batches vengono elaborati contemporaneamente e i due contengono record che puntano allo stesso record padre, uno dei batches tenterà di posizionare un blocco nel record padre, il che può portare gli altri batches al lancio dell'errore "unable to lock row", poiché il batch non è stato in grado di ottenere un blocco entro 10 secondi.
Per evitare ciò, è possibile effettuare una delle seguenti operazioni:
- Ridurre la dimensione del batch;
- Elaborare i record in modalità seriale anziché in parallelo, in questo modo viene elaborato un batch alla volta;
- Ordinare i record in base al loro record padre, per evitare di avere records figli (con lo stesso padre) in batches diversi quando si utilizza la modalità parallela.
Per questo tipo di scenari, si consiglia vivamente di acquisire familiarità con le linee guida che potete trovate in questo articolo.
d. Master-detail Relationship
Se un record master di una relazione master-detail ha troppi record figli (migliaia), è probabile che si verifichino errori "unable to lock row", poiché ogni volta che si modifica il record di detail, il record master viene bloccato. Maggiore è il numero di record di detail, maggiore è la probabilità che questi vengano modificati dagli utenti, causando il blocco del record padre.
Per evitare questo problema, è possibile spostare alcuni records figli in un altro padre, in modo da ridurre la quantità di record figli allegati ad un singolo record padre.
Record-Level Locking è uno scenario comune che può essere ridotto.
Risoluzione dei problemi
1. È possibile abilitare i Debug Log per l'utente che si trova ad affrontare l'errore, per trovare il motivo che causa il problema.
2. Verificare l'eventuale presenza di Jobs in background in esecuzione sullo stesso oggetto. Se ce ne sono, provare a mettere in pausa i jobs e ad eseguire le azioni per ridurre i blocchi dei record.