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 / 63609
Accepted
Yiping
Yiping
Asked: 2014-04-22 18:08:07 +0800 CST2014-04-22 18:08:07 +0800 CST 2014-04-22 18:08:07 +0800 CST

Sincronização usando gatilhos

  • 772

Eu tenho um requisito semelhante às discussões anteriores em:

  • Escrevendo um esquema bancário simples: como devo manter meus saldos sincronizados com o histórico de transações?
  • Gatilho em combinação com a transação

Eu tenho duas tabelas [Account].[Balance]e [Transaction].[Amount]:

CREATE TABLE Account (
      AccountID    INT
    , Balance      MONEY
);

CREATE TABLE Transaction (
      TransactionID INT
     , AccountID    INT
    , Amount      MONEY
);

Quando houver uma inserção, atualização ou exclusão na [Transaction]tabela, o [Account].[Balance]deve ser atualizado com base no arquivo [Amount].

Atualmente eu tenho um gatilho para fazer este trabalho:

ALTER TRIGGER [dbo].[TransactionChanged] 
ON  [dbo].[Transaction]
AFTER INSERT, UPDATE, DELETE
AS 
BEGIN
IF  EXISTS (select 1 from [Deleted]) OR EXISTS (select 1 from [Inserted])
    UPDATE [dbo].[Account]
    SET
    [Account].[Balance] = [Account].[Balance] + 
        (
            Select ISNULL(Sum([Inserted].[Amount]),0)
            From [Inserted] 
            Where [Account].[AccountID] = [Inserted].[AccountID]
        )
        -
        (
            Select ISNULL(Sum([Deleted].[Amount]),0)
            From [Deleted] 
            Where [Account].[AccountID] = [Deleted].[AccountID]
        )
END

Embora isso pareça estar funcionando, tenho dúvidas:

  1. O gatilho segue o princípio ACID do banco de dados relacional? Existe alguma chance de uma inserção ser confirmada, mas o gatilho falhar?
  2. Minhas declarações IFe UPDATEparecem estranhas. Existe alguma maneira melhor de atualizar a [Account]linha correta?
sql-server trigger
  • 1 1 respostas
  • 5505 Views

1 respostas

  • Voted
  1. Best Answer
    Paul White
    2014-04-23T18:13:25+08:002014-04-23T18:13:25+08:00

    1. O gatilho segue o princípio ACID do banco de dados relacional? Existe alguma chance de uma inserção ser confirmada, mas o gatilho falhar?

    Esta pergunta é parcialmente respondida em uma pergunta relacionada à qual você vinculou. O código do gatilho é executado no mesmo contexto transacional da instrução DML que o fez disparar, preservando a parte atômica dos princípios ACID que você mencionou. A instrução de acionamento e o código do acionador são bem-sucedidos ou falham como uma unidade.

    As propriedades ACID também garantem que toda a transação (incluindo o código do acionador) deixará o banco de dados em um estado que não viola nenhuma restrição explícita ( Consistente ) e quaisquer efeitos consolidados recuperáveis ​​sobreviverão a uma falha do banco de dados ( Durável ).

    A menos que a transação circundante (talvez implícita ou de confirmação automática) esteja em execução no SERIALIZABLEnível de isolamento , a propriedade Isolated não é garantida automaticamente. Outra atividade de banco de dados simultânea pode interferir na operação correta de seu código de gatilho. Por exemplo, o saldo da conta pode ser alterado por outra sessão depois de lê-lo e antes de atualizá-lo - uma clássica condição de corrida.

    2. Minhas declarações IF e UPDATE parecem estranhas. Existe alguma maneira melhor de atualizar a linha [Conta] correta?

    Existem razões muito boas para a outra pergunta à qual você vinculou não oferecer nenhuma solução baseada em gatilho. O código de gatilho projetado para manter uma estrutura desnormalizada sincronizada pode ser extremamente complicado de acertar e testar corretamente. Mesmo pessoas muito avançadas do SQL Server com muitos anos de experiência lutam com isso.

    Manter um bom desempenho ao mesmo tempo em que preserva a exatidão em todos os cenários e evita problemas como impasses adiciona dimensões extras de dificuldade. Seu código de gatilho está longe de ser robusto e atualiza o saldo de todas as contas , mesmo que apenas uma única transação seja modificada. Existem todos os tipos de riscos e desafios com uma solução baseada em gatilho, o que torna a tarefa profundamente inadequada para alguém relativamente novo nessa área de tecnologia.

    Para ilustrar alguns dos problemas, mostro alguns exemplos de código abaixo. Esta não é uma solução rigorosamente testada (os gatilhos são difíceis!) e não estou sugerindo que você a use como outra coisa senão um exercício de aprendizado. Para um sistema real, as soluções não-gatilho têm benefícios importantes, portanto, você deve revisar cuidadosamente as respostas para a outra pergunta e evitar completamente a ideia do gatilho.

    Tabelas de exemplo

    CREATE TABLE dbo.Accounts
    (
        AccountID integer NOT NULL,
        Balance money NOT NULL,
    
        CONSTRAINT PK_Accounts_ID
        PRIMARY KEY CLUSTERED (AccountID)
    );
    
    CREATE TABLE dbo.Transactions
    (
        TransactionID integer IDENTITY NOT NULL,
        AccountID integer NOT NULL,
        Amount money NOT NULL,
    
        CONSTRAINT PK_Transactions_ID
        PRIMARY KEY CLUSTERED (TransactionID),
    
        CONSTRAINT FK_Accounts
        FOREIGN KEY (AccountID)
        REFERENCES dbo.Accounts (AccountID)
    );
    

    prevenirTRUNCATE TABLE

    Gatilhos não são acionados por TRUNCATE TABLE. A tabela vazia a seguir existe apenas para evitar que a Transactionstabela seja truncada (ser referenciada por uma chave estrangeira evita o truncamento da tabela):

    CREATE TABLE dbo.PreventTransactionsTruncation
    (
        Dummy integer NULL,
    
        CONSTRAINT FK_Transactions
        FOREIGN KEY (Dummy)
        REFERENCES dbo.Transactions (TransactionID),
    
        CONSTRAINT CHK_NoRows
        CHECK (Dummy IS NULL AND Dummy IS NOT NULL)
    );
    

    Definição de gatilho

    O código de gatilho a seguir garante que apenas as entradas de conta necessárias sejam mantidas e usa SERIALIZABLEsemântica lá. Como um efeito colateral desejável, isso também evita os resultados incorretos que podem resultar se um nível de isolamento de controle de versão de linha estiver em uso. O código também evita a execução do código do gatilho se nenhuma linha for afetada pela instrução de origem. A tabela temporária e RECOMPILEa dica são usadas para evitar problemas no plano de execução do acionador causados ​​por estimativas de cardinalidade imprecisas:

    CREATE TRIGGER dbo.TransactionChange ON dbo.Transactions 
    AFTER INSERT, UPDATE, DELETE 
    AS
    BEGIN
    IF @@ROWCOUNT = 0 OR
        TRIGGER_NESTLEVEL
        (
            OBJECT_ID(N'dbo.TransactionChange', N'TR'),
            'AFTER', 
            'DML'
        ) > 1 
        RETURN;
    
        SET NOCOUNT, XACT_ABORT ON;
    
        CREATE TABLE #Delta
        (
            AccountID integer PRIMARY KEY,
            Amount money NOT NULL
        );
    
        INSERT #Delta
            (AccountID, Amount)
        SELECT 
            InsDel.AccountID,
            Amount = SUM(InsDel.Amount)
        FROM 
        (
            SELECT AccountID, Amount
            FROM Inserted
            UNION ALL
            SELECT AccountID, $0 - Amount
            FROM Deleted
        ) AS InsDel
        GROUP BY
            InsDel.AccountID;
    
        UPDATE A
        SET Balance += D.Amount
        FROM #Delta AS D
        JOIN dbo.Accounts AS A WITH (SERIALIZABLE)
            ON A.AccountID = D.AccountID
        OPTION (RECOMPILE);
    END;
    

    teste

    O código a seguir usa uma tabela de números para criar 100.000 contas com saldo zero:

    INSERT dbo.Accounts
        (AccountID, Balance)
    SELECT
        N.n, $0
    FROM dbo.Numbers AS N
    WHERE
        N.n BETWEEN 1 AND 100000;
    

    O código de teste abaixo insere 10.000 transações aleatórias:

    INSERT dbo.Transactions
        (AccountID, Amount)
    SELECT 
        CONVERT(integer, RAND(CHECKSUM(NEWID())) * 100000 + 1),
        CONVERT(money, RAND(CHECKSUM(NEWID())) * 500 - 250)
    FROM dbo.Numbers AS N
    WHERE 
        N.n BETWEEN 1 AND 10000;
    

    Usando a ferramenta SQLQueryStress , executei este teste 100 vezes em 32 threads com bom desempenho, sem deadlocks e resultados corretos. Eu ainda não recomendo isso como nada além de um exercício de aprendizado.

    • 13

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