Đồng bộ hóa bằng cách sử dụng kích hoạt


11

Tôi có một yêu cầu tương tự như các cuộc thảo luận trước đây tại:

Tôi có hai bảng [Account].[Balance][Transaction].[Amount]:

CREATE TABLE Account (
      AccountID    INT
    , Balance      MONEY
);

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

Khi có một chèn, cập nhật hoặc xóa so với [Transaction]bảng, thì [Account].[Balance]nên cập nhật dựa trên [Amount].

Hiện tại tôi có một kích hoạt để làm công việc này:

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

Mặc dù điều này dường như đang hoạt động, tôi có câu hỏi:

  1. Có kích hoạt theo nguyên tắc ACID của cơ sở dữ liệu quan hệ? Có bất kỳ cơ hội chèn có thể được cam kết nhưng kích hoạt thất bại?
  2. Tôi IFUPDATEbáo cáo trông lạ. Có cách nào tốt hơn để cập nhật [Account]hàng đúng không?

Câu trả lời:


13

1. Trình kích hoạt có tuân theo nguyên tắc ACID của cơ sở dữ liệu quan hệ không? Có bất kỳ cơ hội chèn có thể được cam kết nhưng kích hoạt thất bại?

Câu hỏi này được trả lời một phần trong một câu hỏi liên quan mà bạn liên kết đến. Mã kích hoạt được thực thi trong cùng bối cảnh giao dịch với câu lệnh DML khiến nó kích hoạt, bảo toàn phần Nguyên tử của các nguyên tắc ACID mà bạn đề cập. Câu lệnh kích hoạt và mã kích hoạt đều thành công hoặc thất bại dưới dạng một đơn vị.

Các thuộc tính ACID cũng đảm bảo toàn bộ giao dịch (bao gồm mã kích hoạt) sẽ khiến cơ sở dữ liệu ở trạng thái không vi phạm bất kỳ ràng buộc rõ ràng nào ( Phù hợp ) và mọi hiệu ứng đã cam kết có thể phục hồi sẽ tồn tại khi xảy ra sự cố cơ sở dữ liệu ( Bền ).

Trừ khi xung quanh (có lẽ ngầm hoặc cam kết tự động) giao dịch đang chạy ở các SERIALIZABLEmức độ cách ly , các Isolated tài sản là không tự động được bảo đảm. Hoạt động cơ sở dữ liệu đồng thời khác có thể can thiệp vào hoạt động chính xác của mã kích hoạt của bạn. Ví dụ: số dư tài khoản có thể được thay đổi bởi một phiên khác sau khi bạn đọc nó và trước khi bạn cập nhật nó - một điều kiện cuộc đua cổ điển.

2. Các câu lệnh IF và CẬP NHẬT của tôi trông lạ. Có cách nào tốt hơn để cập nhật hàng [Tài khoản] chính xác không?

Có những lý do rất chính đáng cho câu hỏi khác mà bạn liên kết không đưa ra bất kỳ giải pháp dựa trên kích hoạt nào. Mã kích hoạt được thiết kế để giữ cho cấu trúc không chuẩn hóa được đồng bộ hóa có thể cực kỳ khó khăn để lấy đúng và kiểm tra đúng cách. Ngay cả những người SQL Server rất tiên tiến với nhiều năm kinh nghiệm đấu tranh với điều này.

Duy trì hiệu suất tốt đồng thời với việc duy trì tính chính xác trong tất cả các tình huống và tránh các vấn đề như bế tắc làm tăng thêm khó khăn. Mã kích hoạt của bạn không ở đâu mạnh mẽ và cập nhật số dư của mọi tài khoản ngay cả khi chỉ một giao dịch được sửa đổi. Có tất cả các loại rủi ro và thách thức với một giải pháp dựa trên kích hoạt, khiến cho nhiệm vụ này không phù hợp với ai đó tương đối mới đối với lĩnh vực công nghệ này.

Để minh họa một số vấn đề, tôi hiển thị một số mã mẫu bên dưới. Đây không phải là một giải pháp được kiểm tra nghiêm ngặt (kích hoạt là khó!) Và tôi không đề nghị bạn sử dụng nó như bất cứ điều gì khác ngoài một bài tập học tập. Đối với một hệ thống thực, các giải pháp không kích hoạt có lợi ích quan trọng, vì vậy bạn nên xem xét cẩn thận các câu trả lời cho câu hỏi khác và tránh hoàn toàn ý tưởng kích hoạt.

Bảng mẫu

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

Ngăn chặn TRUNCATE TABLE

Kích hoạt không bị sa thải bởi TRUNCATE TABLE. Bảng trống sau tồn tại hoàn toàn để ngăn Transactionsbảng bị cắt (được tham chiếu bởi khóa ngoại ngăn ngăn cắt bảng):

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

Định nghĩa kích hoạt

Mã kích hoạt sau đây đảm bảo chỉ các mục nhập tài khoản cần thiết được duy trì và sử dụng SERIALIZABLEngữ nghĩa ở đó. Là một tác dụng phụ mong muốn, điều này cũng tránh được kết quả không chính xác có thể xảy ra nếu sử dụng mức cô lập phiên bản hàng. Mã này cũng tránh thực thi mã kích hoạt nếu không có hàng nào bị ảnh hưởng bởi câu lệnh nguồn. Bảng tạm thời và RECOMPILEgợi ý được sử dụng để tránh các vấn đề về kế hoạch thực hiện kích hoạt gây ra bởi ước tính số lượng thẻ không chính xác:

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;

Kiểm tra

Đoạn mã sau sử dụng bảng số để tạo 100.000 tài khoản với số dư bằng không:

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

Mã kiểm tra bên dưới chèn 10.000 giao dịch ngẫu nhiên:

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;

Sử dụng công cụ SQLQueryStress , tôi đã chạy thử nghiệm này 100 lần trên 32 luồng với hiệu suất tốt, không có bế tắc và kết quả chính xác. Tôi vẫn không đề nghị điều này như bất cứ điều gì khác ngoài một bài tập học tập.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.