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 SERIALIZABLE
mứ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 Transactions
bả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 SERIALIZABLE
ngữ 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à RECOMPILE
gợ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.