Puoi usare i LOCK per rendere le cose SERIALIZZABILI ma questo riduce la concorrenza. Perché non provare prima la condizione comune ("per lo più inserisci o per lo più seleziona") seguita da una gestione sicura dell'azione "riparatrice"? Cioè, il modello "JFDI"...
Per lo più INSERT previsti (parco palla 70-80%+):
Prova a inserire. Se fallisce, la riga è già stata creata. Non c'è bisogno di preoccuparsi della concorrenza perché TRY/CATCH si occupa dei duplicati per te.
BEGIN TRY
INSERT Table VALUES (@Value)
SELECT @id = SCOPE_IDENTITY()
END TRY
BEGIN CATCH
IF ERROR_NUMBER() <> 2627
RAISERROR etc
ELSE -- only error was a dupe insert so must already have a row to select
SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
END CATCH
Principalmente SELECT:
Simile, ma prima prova a ottenere i dati. Nessun dato =INSERTO necessario. Di nuovo, se 2 chiamate simultanee provano a INSERT perché entrambe hanno trovato la riga mancante degli handle TRY/CATCH.
BEGIN TRY
SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
IF @@ROWCOUNT = 0
BEGIN
INSERT Table VALUES (@Value)
SELECT @id = SCOPE_IDENTITY()
END
END TRY
BEGIN CATCH
IF ERROR_NUMBER() <> 2627
RAISERROR etc
ELSE
SELECT @id = RowID FROM Table WHERE RowValue = @VALUE
END CATCH
Il secondo sembra ripetersi, ma è altamente simultaneo. I blocchi otterrebbero lo stesso risultato ma a scapito della concorrenza...
Modifica:
Perché non per utilizzare UNISCI...
Se utilizzi la clausola OUTPUT, restituirà solo ciò che è aggiornato. Quindi è necessario un UPDATE fittizio per generare la tabella INSERTED per la clausola OUTPUT. Se devi eseguire aggiornamenti fittizi con molte chiamate (come implicito da OP) sono molte scritture di log solo per poter usare MERGE.