Tetikleyicileri kullanarak senkronizasyon


11

Önceki tartışmalara benzer bir gereksinimim var:

İki tablo var [Account].[Balance]ve [Transaction].[Amount]:

CREATE TABLE Account (
      AccountID    INT
    , Balance      MONEY
);

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

[Transaction]Tabloda bir ekleme, güncelleme veya silme olduğunda, tabloya [Account].[Balance]göre güncellenmelidir [Amount].

Şu anda bu işi yapmak için bir tetikleyici var:

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

Bu işe yarıyor gibi görünse de, sorularım var:

  1. Tetikleyici ilişkisel veritabanının ACID ilkesini izliyor mu? Bir ekin işlenme şansı var mı, ancak tetikleyici başarısız mı?
  2. Benim IFve UPDATEifadeler garip görünüyor. Doğru [Account]satırı güncellemenin daha iyi bir yolu var mı ?

Yanıtlar:


13

1. Tetik, ilişkisel veritabanının ACID ilkesini izliyor mu? Bir ekin işlenme şansı var mı, ancak tetikleyici başarısız mı?

Bu soru, bağlantı verdiğiniz ilgili bir soruda kısmen yanıtlanmıştır . Tetikleyici kod, söz konusu ACID ilkelerinin Atomik kısmını koruyarak, tetiklenmesine neden olan DML ifadesiyle aynı işlem bağlamında yürütülür . Tetikleme deyimi ve tetikleme kodu, bir birim olarak hem başarılı hem de başarısız olur.

ASİT özellikleri aynı zamanda herhangi bir belirgin kısıtlar (ihlal etmeyen bir durumda veritabanı bırakacaktır (tetikleyici kodu dahil) bütün işlem garanti Tutarlı ) ve her türlü kazanılabilir taahhüt etkiler (veritabanı kazasında hayatta kalacaktır Dayanıklı ).

Çevredeki (belki örtük veya otomatik taahhüt) işlem de çalıştığı sürece SERIALIZABLEyalıtım düzeyine , İzole özelliği olan değil , otomatik olarak garanti. Diğer eşzamanlı veritabanı etkinliği, tetikleme kodunuzun doğru çalışmasını engelleyebilir. Örneğin, hesap bakiyesi, okuduktan ve güncellemeden önce başka bir oturumla değiştirilebilir - klasik bir yarış koşulu.

2. IF ve UPDATE ifadelerim garip görünüyor. Doğru [Hesap] satırını güncellemenin daha iyi bir yolu var mı?

Bağlandığınız diğer sorunun tetikleyici çözüm sunmaması için çok iyi nedenler var . Denormalize bir yapıyı senkronize tutmak için tasarlanmış tetik kodu, doğru almak ve düzgün bir şekilde test etmek için son derece zor olabilir . Uzun yıllara dayanan deneyime sahip çok gelişmiş SQL Server kullanıcıları bile bununla mücadele ediyor.

Tüm senaryolarda doğruluğu korumak ve çıkmaz kilitler gibi sorunlardan kaçınmak gibi iyi performansı sürdürmek, ekstra zorluk boyutları ekler. Tetik kodunuz sağlam olmaya yakın değildir ve yalnızca tek bir işlem değiştirilse bile her hesabın bakiyesini günceller . Tetik tabanlı bir çözümle her türlü risk ve zorluk vardır, bu da görevi bu teknoloji alanına nispeten yeni olan biri için derinden uygun hale getirir.

Bazı sorunları göstermek için, aşağıda bazı örnek kod göstereceğim. Bu titizlikle test edilmiş bir çözüm değildir (tetikleyiciler zordur!) Ve bunu bir öğrenme egzersizi dışında bir şey olarak kullanmanızı önermiyorum. Gerçek bir sistem için, tetikleyici olmayan çözümlerin önemli faydaları vardır, bu nedenle diğer sorunun cevaplarını dikkatlice gözden geçirmeli ve tetikleyici fikrinden tamamen kaçınmalısınız.

Örnek tablolar

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)
);

önleme TRUNCATE TABLE

Tetikleyiciler tarafından tetiklenmez TRUNCATE TABLE. Aşağıdaki boş tablo yalnızca Transactionstablonun kısaltılmasını önlemek için mevcuttur (yabancı bir anahtar tarafından başvurulması tablonun kısalmasını önler):

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)
);

Tetikleyici Tanımı

Aşağıdaki tetikleyici kodu, yalnızca gerekli hesap girişlerinin korunmasını sağlar ve SERIALIZABLEorada semantik kullanır . Arzu edilen bir yan etki olarak, bu aynı zamanda bir satır versiyonlama izolasyon seviyesi kullanımdaysa ortaya çıkabilecek yanlış sonuçları da önler . Kod ayrıca, kaynak ifadeden hiçbir satır etkilenmediğinde tetikleyici kodunun yürütülmesini de önler. Geçici tablo ve RECOMPILEipucu, yanlış kardinalite tahminlerinin neden olduğu tetikleyici yürütme planı sorunlarını önlemek için kullanılır:

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;

Test yapmak

Aşağıdaki kod, sıfır bakiyeye sahip 100.000 hesap oluşturmak için bir sayı tablosu kullanır :

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

Aşağıdaki test kodu 10.000 rastgele işlem ekler:

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;

SQLQueryStress aracını kullanarak, bu testi iyi işlenmiş, kilitlenmemiş ve doğru sonuçları olan 32 iş parçacığında 100 kez çalıştırdım. Bunu hala bir öğrenme alıştırması dışında bir şey olarak önermiyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.