AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 346132
Accepted
J. Mini
J. Mini
Asked: 2025-04-12 07:15:22 +0800 CST2025-04-12 07:15:22 +0800 CST 2025-04-12 07:15:22 +0800 CST

Qual abordagem para upserts seguros de simultaneidade é melhor para entradas com valor de tabela se MERGE for proibido?

  • 772

Este artigo clássico sobre segurança de concorrência foi claramente projetado para realizar o upsert de apenas uma linha por vez. Na minha situação, tenho uma entrada com valor de tabela e quero realizar o upsert de cada linha de forma segura em termos de concorrência. Sei que isso nem sempre é possível, mas quero chegar o mais perto possível. MERGEParece uma solução natural, mas desconfio dela e, na verdade, estou em uma situação em que é propensa a bugs . As duas abordagens restantes no artigo de Michael J. Swart são:

  1. Dentro de uma transação com dicas de bloqueio (atualização mais comum)
CREATE PROCEDURE s_AccountDetails_Upsert ( @Email nvarchar(4000), @Etc nvarchar(max) )
AS 
SET XACT_ABORT ON;
BEGIN TRAN
 
  UPDATE TOP (1) dbo.AccountDetails WITH (UPDLOCK, SERIALIZABLE)
     SET Etc = @Etc
   WHERE Email = @Email;
 
  IF (@@ROWCOUNT = 0)
  BEGIN      
      INSERT dbo.AccountDetails ( Email, Etc )
      VALUES ( @Email, @Etc );
  END 
COMMIT
  1. Dentro de uma transação com dicas de bloqueio (inserir mais comum)
CREATE PROCEDURE s_AccountDetails_Upsert ( @Email nvarchar(4000), @Etc nvarchar(max) )
AS 
SET XACT_ABORT ON;
BEGIN TRAN
 
  INSERT dbo.AccountDetails ( Email, Etc )
  SELECT @Email, @Etc
  WHERE NOT EXISTS (
      SELECT *
      FROM dbo.AccountDetails WITH (UPDLOCK, SERIALIZABLE)
      WHERE Email = @Email
  )
 
  IF (@@ROWCOUNT = 0)
  BEGIN      
      UPDATE TOP (1) dbo.AccountDetails
      SET Etc = @Etc
      WHERE Email = @Email;
  END 
COMMIT 

Eu poderia adaptar qualquer uma delas para usar variáveis ​​de tabela (por exemplo, suspeito que IF (@@ROWCOUNT = 0)precise ser totalmente removida), mas o uso de uma entrada com valor de tabela torna óbvio que devemos preferir a primeira ou a segunda solução? Se não, com base em quê a decisão deve ser tomada?

sql-server
  • 2 2 respostas
  • 424 Views

2 respostas

  • Voted
  1. Best Answer
    Rob Farley
    2025-04-12T11:17:41+08:002025-04-12T11:17:41+08:00

    Faça a atualização primeiro e depois faça uma inserção com uma where not existscláusula. Não é possível testar @@rowcountporque algumas linhas podem ter sido atualizadas.

    Se você fizer a inserção primeiro, você também atualizará as linhas que acabou de inserir.

    • 6
  2. Paul White
    2025-04-13T18:35:32+08:002025-04-13T18:35:32+08:00

    Problemas MERGEgeralmente se manifestam com combinações de recursos (especialmente os relativamente novos) que resultam em planos de alteração de dados altamente complexos. Isso é uma pena, pois MERGEé, sem dúvida, uma maneira conveniente de expressar coisas como um "upsert".

    Solução alternativa

    Você pode considerar o seguinte:

    1. Crie uma visualização sobre a tabela de destino.
    2. Crie INSTEAD OFgatilho(s) para visualização INSERTe UPDATEações.
    3. Escreva seu 'upsert' como um MERGEcontraponto à visão.

    Vantagens:

    1. Ele MERGEvê um alvo simples , então o plano de alteração de dados não é complexo.
    2. O SQL Server preenche tabelas de trabalho internas separadas para arquivos * inseridos e excluídos .
    3. O código do gatilho processa inserções e atualizações simples separadamente.
    4. Você não precisa mais de uma transação explícita em torno de instruções de inserção e atualização separadas.

    Desvantagens:

    1. Os gatilhos acrescentam alguma sobrecarga.
    2. Você precisa lembrar de direcionar 'upserts' para a visualização.
    3. A OUTPUTcláusula não funcionará para referências inseridas .

    Esta solução alternativa permite que você escreva um MERGE, mas tenha as ações resultantes executadas como inserções e atualizações simples separadas.

    As precauções usuais para simultaneidade MERGEainda são necessárias.

    Usei uma visualização assumindo que você prefere não ter INSTEAD OFgatilhos na tabela base.

    * INSTEAD OFgatilhos não usam controle de versão de linha. Veja meu artigo, Coisas interessantes sobre gatilhos INSTEAD OF .

    Demonstração

    Há melhorias a serem feitas (como a possibilidade de combinar os gatilhos). O código a seguir prioriza a clareza:

    db<>violino

    Linhas de tabela e amostra

    CREATE TABLE dbo.AccountDetails
    (
        Email nvarchar(400) NOT NULL 
            CONSTRAINT PK_AccountDetails 
            PRIMARY KEY CLUSTERED,
        Created datetime NOT NULL 
            DEFAULT GETUTCDATE(),
        Etc nvarchar(max) NULL
    );
    
    INSERT dbo.AccountDetails
        (Email, Etc)
    VALUES
        (N'[email protected]', N'Original 2792'),
        (N'[email protected]', N'Original 3129'),
        (N'[email protected]', N'Original 4726'),
        (N'[email protected]', N'Original 5766');
    

    Visualizar

    CREATE VIEW 
        dbo.AccountDetails_Upsert
    WITH SCHEMABINDING
    AS
    SELECT 
        Email, 
        Etc 
    FROM dbo.AccountDetails;
    

    INSTEAD OF INSERTgatilho de visualização

    CREATE TRIGGER 
        AccountDetails_Upsert_InsteadOfInsert
    ON dbo.AccountDetails_Upsert
    INSTEAD OF INSERT 
    AS
    IF  ROWCOUNT_BIG() = 0 RETURN;
    
    SET NOCOUNT, XACT_ABORT ON;
    SET ROWCOUNT 0;
    
    IF NOT EXISTS (SELECT * FROM Inserted) RETURN;
    
    -- Simple insert
    INSERT dbo.AccountDetails 
        (Email, Etc)
    SELECT 
        Email, Etc
    FROM Inserted;
    

    INSTEAD OF UPDATEgatilho de visualização

    CREATE TRIGGER 
        AccountDetails_Upsert_InsteadOfUpdate
    ON dbo.AccountDetails_Upsert
    INSTEAD OF UPDATE 
    AS
    IF  ROWCOUNT_BIG() = 0 RETURN;
    
    SET NOCOUNT, XACT_ABORT ON;
    SET ROWCOUNT 0;
    
    IF NOT EXISTS (SELECT * FROM Inserted) RETURN;
    
    -- Simple update
    UPDATE AD
    SET Etc = I.Etc
    FROM Inserted AS I
    JOIN dbo.AccountDetails AS AD
        ON AD.Email = I.Email;
    

    Valor de tabelaMERGE

    DECLARE @Changes AS table
    (
        Email nvarchar(400) NOT NULL PRIMARY KEY,
        Etc nvarchar(max) NULL
    );
    
    INSERT @Changes
        (Email, Etc)
    VALUES
        -- Update some rows
        (N'[email protected]', N'Updated 2792'),
        (N'[email protected]', N'Updated 3129'),
        (N'[email protected]', N'Updated 4726'),
        (N'[email protected]', N'Updated 5766'),
        -- Insert some rows
        (N'[email protected]', N'Inserted a'),
        (N'[email protected]', N'Inserted b');
    
    MERGE dbo.AccountDetails_Upsert
    WITH 
    (
        UPDLOCK, 
        SERIALIZABLE,
        FORCESEEK
    ) AS V
    USING @Changes AS C
        ON C.Email = V.Email
    WHEN MATCHED THEN 
        UPDATE
        SET Etc = C.Etc
    WHEN NOT MATCHED BY TARGET THEN 
        INSERT (Email, Etc) 
        VALUES (C.Email, C.Etc);
    

    Resultados

    E-mail Criado Etc
    [email protected] 2025-04-13 09:48:39.687 Atualizado em 2792
    [email protected] 2025-04-13 09:48:39.687 Atualizado 3129
    [email protected] 2025-04-13 09:48:39.687 Atualizado 4726
    [email protected] 2025-04-13 09:48:39.687 Atualizado 5766
    [email protected] 2025-04-13 09:48:39.960 Inserido um
    [email protected] 2025-04-13 09:48:39.960 Inserido b

    MERGEplano

    Plano de fusão

    Arrumar

    DROP VIEW IF EXISTS 
        dbo.AccountDetails_Upsert;
    
    DROP TABLE IF EXISTS 
        dbo.AccountDetails;
    
    • 2

relate perguntas

  • SQL Server - Como as páginas de dados são armazenadas ao usar um índice clusterizado

  • Preciso de índices separados para cada tipo de consulta ou um índice de várias colunas funcionará?

  • Quando devo usar uma restrição exclusiva em vez de um índice exclusivo?

  • Quais são as principais causas de deadlocks e podem ser evitadas?

  • Como determinar se um Índice é necessário ou necessário

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve