Sinkronisasi menggunakan pemicu


11

Saya memiliki persyaratan yang mirip dengan diskusi sebelumnya di:

Saya punya dua tabel, [Account].[Balance]dan [Transaction].[Amount]:

CREATE TABLE Account (
      AccountID    INT
    , Balance      MONEY
);

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

Ketika ada sisipan, perbarui atau hapus [Transaction]tabel, yang [Account].[Balance]harus diperbarui berdasarkan pada [Amount].

Saat ini saya memiliki pemicu untuk melakukan pekerjaan ini:

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

Meskipun ini tampaknya berhasil, saya punya pertanyaan:

  1. Apakah pemicunya mengikuti prinsip ACID database relasional? Apakah ada kemungkinan penyisipan dilakukan tetapi pemicu gagal?
  2. Pernyataan IFdan saya UPDATEterlihat aneh. Apakah ada cara yang lebih baik untuk memperbarui [Account]baris yang benar ?

Jawaban:


13

1. Apakah pemicunya mengikuti prinsip ACID database relasional? Apakah ada kemungkinan penyisipan dilakukan tetapi pemicu gagal?

Pertanyaan ini sebagian dijawab dalam pertanyaan terkait yang Anda tautkan. Kode pemicu dijalankan dalam konteks transaksional yang sama dengan pernyataan DML yang menyebabkannya menyala, mempertahankan bagian Atom dari prinsip-prinsip ACID yang Anda sebutkan. Pernyataan pemicu dan kode pemicu berhasil atau gagal sebagai satu unit.

Properti ACID juga menjamin seluruh transaksi (termasuk kode pemicu) akan membuat database dalam keadaan yang tidak melanggar kendala eksplisit ( Konsisten ) dan setiap efek komitmen yang dapat dipulihkan akan selamat dari kerusakan database ( Tahan Lama ).

Kecuali transaksi di sekitarnya (mungkin implisit atau komit otomatis) berjalan pada SERIALIZABLEtingkat isolasi , properti Terisolasi tidak dijamin secara otomatis. Aktivitas database bersamaan lainnya dapat mengganggu operasi kode pemicu Anda dengan benar. Misalnya, saldo akun dapat diubah dengan sesi lain setelah Anda membacanya dan sebelum Anda memperbaruinya - kondisi balapan klasik.

2. Pernyataan IF dan UPDATE saya terlihat aneh. Apakah ada cara yang lebih baik untuk memperbarui baris [Akun] yang benar?

Ada beberapa alasan bagus mengapa pertanyaan lain yang Anda tautkan tidak menawarkan solusi berbasis pemicu. Kode pemicu yang dirancang untuk menjaga agar struktur denormalized tersinkronisasi dapat sangat sulit untuk dilakukan dengan benar dan diuji dengan benar. Bahkan orang-orang SQL Server yang sangat canggih dengan pengalaman bertahun-tahun berjuang dengan ini.

Mempertahankan kinerja yang baik sekaligus menjaga kebenaran di semua skenario dan menghindari masalah seperti deadlock menambah dimensi kesulitan tambahan. Kode pemicu Anda sangat kuat, dan memperbarui saldo setiap akun meskipun hanya satu transaksi yang dimodifikasi. Ada segala macam risiko dan tantangan dengan solusi berbasis pemicu, yang membuat tugas ini sangat tidak cocok untuk seseorang yang relatif baru di bidang teknologi ini.

Untuk menggambarkan beberapa masalah, saya menunjukkan beberapa kode contoh di bawah ini. Ini bukan solusi yang telah teruji ketat (pemicunya sulit!) Dan saya tidak menyarankan Anda menggunakannya sebagai apa pun selain latihan pembelajaran. Untuk sistem nyata, solusi non-pemicu memiliki manfaat penting, jadi Anda harus hati-hati meninjau jawaban untuk pertanyaan lain , dan menghindari ide pemicu sepenuhnya.

Tabel sampel

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

Mencegah TRUNCATE TABLE

Pemicu tidak dipecat oleh TRUNCATE TABLE. Tabel kosong berikut ada murni untuk mencegah Transactionstabel terpotong (dirujuk oleh kunci asing mencegah pemotongan tabel):

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

Definisi Pemicu

Kode pemicu berikut memastikan hanya entri akun yang perlu dipelihara, dan menggunakan SERIALIZABLEsemantik di sana. Sebagai efek samping yang diinginkan, ini juga menghindari hasil yang salah yang mungkin terjadi jika tingkat isolasi versi baris digunakan. Kode juga menghindari mengeksekusi kode pemicu jika tidak ada baris yang dipengaruhi oleh pernyataan sumber. Tabel dan RECOMPILEpetunjuk sementara digunakan untuk menghindari pemicu masalah rencana pelaksanaan yang disebabkan oleh perkiraan kardinalitas yang tidak akurat:

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;

Pengujian

Kode berikut menggunakan tabel angka untuk membuat 100.000 akun dengan saldo nol:

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

Kode tes di bawah ini menyisipkan 10.000 transaksi acak:

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;

Menggunakan alat SQLQueryStress , saya menjalankan tes ini 100 kali pada 32 utas dengan kinerja yang baik, tanpa deadlock, dan hasil yang benar. Saya masih tidak merekomendasikan ini sebagai apa pun selain latihan belajar.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.