DBA 需向不同角色提供數據變更記錄:會計看 Excel、業務看報表、安全部門查事件日誌。本文示範在 SQL Server 中使用 DML Trigger 捕獲 INSERT/UPDATE/DELETE,自動寫入 Windows 事件日誌 與 文本文件,無需修改應用代碼。
USE Northwind;
GO
CREATE TABLE dbo.TableForTrigger (
TrackID INT IDENTITY(1,1) PRIMARY KEY,
LastName VARCHAR(25),
FirstName VARCHAR(25)
);
GO
將訊息加入 master..sysmessages,@with_log = 'TRUE' 確保寫入事件日誌。
EXEC sp_addmessage 50005, 10, '%s', @with_log = 'TRUE';
GO
CREATE TRIGGER trg_TableForTrigger_Ins
ON dbo.TableForTrigger
FOR INSERT
AS
BEGIN
DECLARE @Msg VARCHAR(8000);
-- 組裝:操作|表名|時間|欄位值
SELECT @Msg = 'INSERTED | TableForTrigger | '
+ CONVERT(VARCHAR(20), GETDATE(), 120) + ' | '
+ CONVERT(VARCHAR(5), TrackID) + ', ' + LastName + ', ' + FirstName
FROM inserted; -- 虛擬表,僅含本次新增行
RAISERROR(50005, 10, 1, @Msg) WITH LOG; -- 寫入 Application Event Log
END;
GO
INSERT INTO dbo.TableForTrigger (LastName, FirstName) VALUES ('Doe', 'John');
打開「事件檢視器 → Windows 日誌 → Application」,來源 MSSQLSERVER,事件 ID 50005,即可見到完整記錄。
前置條件:
EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;(需 sysadmin)。ALTER TRIGGER trg_TableForTrigger_Ins
ON dbo.TableForTrigger
FOR INSERT
AS
BEGIN
DECLARE @Msg VARCHAR(1000), @Cmd VARCHAR(2000);
SELECT @Msg = 'INSERTED | TableForTrigger | '
+ CONVERT(VARCHAR(20), GETDATE(), 120) + ' | '
+ CONVERT(VARCHAR(5), TrackID) + ', ' + LastName + ', ' + FirstName
FROM inserted;
RAISERROR(50005, 10, 1, @Msg) WITH LOG; -- 仍保留事件日誌
SET @Cmd = 'echo ' + @Msg + ' >> C:\LogTest.log';
EXEC master..xp_cmdshell @Cmd, NO_OUTPUT;
END;
GO
批次 INSERT ... SELECT 會一次觸發,inserted 含多行。需游標拆解:
ALTER TRIGGER trg_TableForTrigger_Ins
ON dbo.TableForTrigger
FOR INSERT
AS
BEGIN
DECLARE @Msg VARCHAR(1000), @Cmd VARCHAR(2000);
DECLARE cur CURSOR LOCAL FAST_FORWARD FOR
SELECT 'INSERTED | TableForTrigger | '
+ CONVERT(VARCHAR(20), GETDATE(), 120) + ' | '
+ CONVERT(VARCHAR(5), TrackID) + ', ' + LastName + ', ' + FirstName
FROM inserted;
OPEN cur;
FETCH NEXT FROM cur INTO @Msg;
WHILE @@FETCH_STATUS = 0
BEGIN
RAISERROR(50005, 10, 1, @Msg) WITH LOG;
SET @Cmd = 'echo ' + @Msg + ' >> C:\LogTest.log';
EXEC master..xp_cmdshell @Cmd, NO_OUTPUT;
FETCH NEXT FROM cur INTO @Msg;
END;
CLOSE cur; DEALLOCATE cur;
END;
GO
INSERT INTO dbo.TableForTrigger (LastName, FirstName)
SELECT LastName, FirstName FROM Employees; -- Northwind 範例表
打開 C:\LogTest.log,每行一筆記錄,含時間戳與欄位值。
-- UPDATE:比對 inserted / deleted 虛擬表
CREATE TRIGGER trg_TableForTrigger_Upd
ON dbo.TableForTrigger
FOR UPDATE
AS
BEGIN
DECLARE @Msg VARCHAR(1000);
SELECT @Msg = 'UPDATED | TableForTrigger | '
+ CONVERT(VARCHAR(20), GETDATE(), 120) + ' | '
+ 'OLD: ' + CONVERT(VARCHAR(5), d.TrackID) + ', ' + d.LastName + ', ' + d.FirstName
+ ' -> NEW: ' + CONVERT(VARCHAR(5), i.TrackID) + ', ' + i.LastName + ', ' + i.FirstName
FROM deleted d JOIN inserted i ON d.TrackID = i.TrackID;
RAISERROR(50005, 10, 1, @Msg) WITH LOG;
END;
-- DELETE:只讀 deleted
CREATE TRIGGER trg_TableForTrigger_Del
ON dbo.TableForTrigger
FOR DELETE
AS
BEGIN
DECLARE @Msg VARCHAR(1000);
SELECT @Msg = 'DELETED | TableForTrigger | '
+ CONVERT(VARCHAR(20), GETDATE(), 120) + ' | '
+ CONVERT(VARCHAR(5), TrackID) + ', ' + LastName + ', ' + FirstName
FROM deleted;
RAISERROR(50005, 10, 1, @Msg) WITH LOG;
END;
xp_cmdshell 進程創建開銷巨大,建議改用 Service Broker 異步寫入或 CDC (Change Data Capture)。xp_cmdshell 在交易外執行,寫文件失敗不會回滾主事務。EventID 流水號,定期對帳文本文件行數,發現差異即告警。| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| Trigger + Event Log | 零外部依賴、安全、標準化 | 查詢不便、保留期短 | 低頻核心表審計 |
| Trigger + xp_cmdshell | 文本易閱讀、可異地備份 | 效能差、權限鬆、不回滾 | 臨時調試、小規模 |
| CDC (Change Data Capture) | 非侵入、高效能、完整前後像 | Enterprise Edition、需啟用 Agent | 生產級審計、數據同步 |
| Temporal Tables (SQL 2016+) | 系統版本化、時間旅行查詢 | 存儲增長、結構變更受限 | 歷史追蹤、合規 |
| Extended Events | 輕量、可過濾、效能影響極小 | 學習曲線、輸出需轉換 | 高頻效能分析、審計 |