Puede usar LOCK para hacer que las cosas sean SERIALIZABLES, pero esto reduce la concurrencia. ¿Por qué no probar primero la condición común ("en su mayoría insertar o en su mayoría seleccionar") seguido de un manejo seguro de la acción "remedial"? Es decir, el patrón "JFDI"...
En su mayoría INSERT esperados (parque de béisbol 70-80%+):
Solo intenta insertar. Si falla, la fila ya se ha creado. No debe preocuparse por la concurrencia porque TRY/CATCH se ocupa de los duplicados por usted.
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
Mayormente SELECCIONA:
Similar, pero trate de obtener datos primero. Sin datos =se necesita INSERTAR. Nuevamente, si 2 llamadas simultáneas intentan INSERTAR porque ambas encontraron que a la fila le faltan los identificadores 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
El segundo parece repetirse, pero es muy concurrente. Los bloqueos lograrían lo mismo pero a expensas de la concurrencia...
Editar:
Por qué no para usar COMBINAR...
Si usa la cláusula OUTPUT, solo devolverá lo que se actualiza. Por lo tanto, necesita una ACTUALIZACIÓN ficticia para generar la tabla INSERTADA para la cláusula OUTPUT. Si tiene que hacer actualizaciones ficticias con muchas llamadas (como lo implica OP), eso es una gran cantidad de escrituras de registro solo para poder usar MERGE.