資料庫層面記錄 INSERT/UPDATE/DELETE 操作,獨立於應用程式,確保完整稽核軌跡。適用於合規、安全調查、資料異動追蹤。
CREATE TABLE dbo.AuditLog (
AuditID BIGINT IDENTITY(1,1) PRIMARY KEY,
TableName SYSNAME NOT NULL,
Operation CHAR(1) NOT NULL CHECK (Operation IN ('I','U','D')), -- Insert/Update/Delete
RowID VARCHAR(100), -- 主鍵值或 ROWID
ChangedBy SYSNAME NOT NULL DEFAULT SUSER_SNAME(),
ChangedAt DATETIME2(3) NOT NULL DEFAULT SYSDATETIME(),
OldValues XML, -- 異動前資料
NewValues XML, -- 異動後資料
AppName NVARCHAR(128) DEFAULT APP_NAME(),
ClientHost NVARCHAR(128) DEFAULT HOST_NAME()
);
CREATE TRIGGER trg_Orders_Audit
ON dbo.Orders
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
DECLARE @op CHAR(1) = CASE
WHEN EXISTS(SELECT * FROM inserted) AND EXISTS(SELECT * FROM deleted) THEN 'U'
WHEN EXISTS(SELECT * FROM inserted) THEN 'I'
ELSE 'D' END;
-- 組合 XML (排除大型欄位如 image/varbinary(max))
INSERT INTO dbo.AuditLog (TableName, Operation, RowID, OldValues, NewValues)
SELECT
'Orders',
@op,
COALESCE(i.OrderID, d.OrderID),
(SELECT * FROM deleted d2 WHERE d2.OrderID = COALESCE(i.OrderID, d.OrderID) FOR XML AUTO, TYPE),
(SELECT * FROM inserted i2 WHERE i2.OrderID = COALESCE(i.OrderID, d.OrderID) FOR XML AUTO, TYPE)
FROM inserted i
FULL JOIN deleted d ON i.OrderID = d.OrderID;
END;
-- 為所有使用者表自動產生觸發器
SELECT 'CREATE TRIGGER trg_' + TABLE_NAME + '_Audit
ON ' + TABLE_SCHEMA + '.' + TABLE_NAME + '
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
DECLARE @op CHAR(1) = CASE
WHEN EXISTS(SELECT * FROM inserted) AND EXISTS(SELECT * FROM deleted) THEN ''U''
WHEN EXISTS(SELECT * FROM inserted) THEN ''I''
ELSE ''D'' END;
INSERT INTO dbo.AuditLog (TableName, Operation, RowID, OldValues, NewValues)
SELECT
''' + TABLE_NAME + ''',
@op,
COALESCE(i.' + COLUMN_NAME + ', d.' + COLUMN_NAME + '),
(SELECT * FROM deleted d2 WHERE d2.' + COLUMN_NAME + ' = COALESCE(i.' + COLUMN_NAME + ', d.' + COLUMN_NAME + ') FOR XML AUTO, TYPE),
(SELECT * FROM inserted i2 WHERE i2.' + COLUMN_NAME + ' = COALESCE(i.' + COLUMN_NAME + ', d.' + COLUMN_NAME + ') FOR XML AUTO, TYPE)
FROM inserted i
FULL JOIN deleted d ON i.' + COLUMN_NAME + ' = d.' + COLUMN_NAME + ';
END;'
FROM INFORMATION_SCHEMA.COLUMNS c
JOIN INFORMATION_SCHEMA.TABLES t ON c.TABLE_NAME = t.TABLE_NAME AND c.TABLE_SCHEMA = t.TABLE_SCHEMA
WHERE t.TABLE_TYPE = 'BASE TABLE'
AND t.TABLE_NAME <> 'AuditLog'
AND COLUMNPROPERTY(OBJECT_ID(c.TABLE_SCHEMA + '.' + c.TABLE_NAME), c.COLUMN_NAME, 'IsIdentity') = 1
AND c.DATA_TYPE NOT IN ('xml', 'image', 'text', 'ntext', 'varbinary', 'geometry', 'geography')
GROUP BY TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME;
| 方案 | 優點 | 缺點 | 版本需求 |
|---|---|---|---|
| CDC (Change Data Capture) | 非同步、低影響、完整異動擷取 | 需 Enterprise Edition、設定較複雜 | SQL 2008+ Ent |
| Temporal Tables | 自動保留歷史、時間旅行查詢 | 儲存空間大、結構變更需遷移 | SQL 2016+ |
| Extended Events + 檔案目標 | 極低開銷、彈性過濾 | 需後處理解析、非交易一致 | SQL 2008+ |
| 應用層 Event Sourcing | 業務語意豐富、可重放 | 架構改變大、需團隊配合 | 無 |