Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Selecionar/Inserir versão de um Upsert:existe um padrão de design para alta simultaneidade?


Você pode usar LOCKs para tornar as coisas SERIALIZÁVEIS, mas isso reduz a simultaneidade. Por que não tentar primeiro a condição comum ("principalmente inserir ou principalmente selecionar"), seguida pelo manuseio seguro da ação "corretiva"? Ou seja, o padrão "JFDI"...

Principalmente INSERTs esperados (estacionamento de bola 70-80%+):

Basta tentar inserir. Se falhar, a linha já foi criada. Não há necessidade de se preocupar com a simultaneidade porque o TRY/CATCH lida com duplicatas para você.
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 SELECTs:

Semelhante, mas tente obter os dados primeiro. Sem dados =INSERT necessário. Novamente, se 2 chamadas simultâneas tentarem INSERT porque ambas encontraram a linha sem os 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

O segundo parece se repetir, mas é altamente simultâneo. Os bloqueios alcançariam o mesmo, mas às custas da simultaneidade ...

Editar:

Por que não para usar MERGE...

Se você usar a cláusula OUTPUT ela retornará apenas o que estiver atualizado. Portanto, você precisa de um UPDATE fictício para gerar a tabela INSERTED para a cláusula OUTPUT. Se você tiver que fazer atualizações fictícias com muitas chamadas (como implícito no OP), há muitas gravações de log apenas para poder usar MERGE.