Você precisa fazer isso na transação para garantir que dois clientes simultâneos não insiram o mesmo fieldValue duas vezes:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
DECLARE @id AS INT
SELECT @id = tableId FROM table WHERE [email protected]
IF @id IS NULL
BEGIN
INSERT INTO table (fieldValue) VALUES (@newValue)
SELECT @id = SCOPE_IDENTITY()
END
SELECT @id
COMMIT TRANSACTION
você também pode usar bloqueio com verificação dupla para reduzir a sobrecarga de bloqueio
DECLARE @id AS INT
SELECT @id = tableID FROM table (NOLOCK) WHERE [email protected]
IF @id IS NULL
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
SELECT @id = tableID FROM table WHERE [email protected]
IF @id IS NULL
BEGIN
INSERT INTO table (fieldValue) VALUES (@newValue)
SELECT @id = SCOPE_IDENTITY()
END
COMMIT TRANSACTION
END
SELECT @id
Quanto à necessidade de ISOLATION LEVEL SERIALIZABLE, quando você está dentro de uma transação serializável, o primeiro SELECT que atinge a tabela cria um range lock cobrindo o local onde o registro deveria estar, para que ninguém mais possa inserir o mesmo registro até que essa transação termine.
Sem ISOLATION LEVEL SERIALIZABLE, o nível de isolamento padrão (READ COMMITTED) não travaria a tabela em tempo de leitura, então entre SELECT e UPDATE, alguém ainda poderia inserir. As transações com nível de isolamento READ COMMITTED não causam o bloqueio de SELECT. As transações com REPEATABLE READS bloqueiam o registro (se encontrado), mas não a lacuna.