觸發器與預存程序互調在技術上可行,但極易導致:
CREATE TRIGGER trg_AfterInsert_Orders
ON dbo.Orders
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
-- 檢查是否真有資料插入
IF NOT EXISTS (SELECT 1 FROM inserted) RETURN;
-- 呼叫預存程序,傳遞必要參數
DECLARE @OrderID INT;
SELECT @OrderID = OrderID FROM inserted; -- 單列插入場景
EXEC usp_ProcessNewOrder @OrderID = @OrderID;
-- 多列插入需游標或集合處理,略
END;
inserted/deleted 為虛擬表,僅在觸發器內可見預存程序無法直接呼叫觸發器。觸發器只能由對應表的 DML 事件自動觸發。
CREATE PROCEDURE usp_UpdateOrderStatus
@OrderID INT,
@NewStatus TINYINT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRAN;
UPDATE dbo.Orders
SET Status = @NewStatus,
UpdatedAt = GETDATE()
WHERE OrderID = @OrderID;
-- 此 UPDATE 會自動觸發 trg_AfterUpdate_Orders (若存在)
COMMIT TRAN;
END;
NESTED TRIGGERS) 伺服器選項預設開啟,可透過 sp_configure 'nested triggers', 0 關閉EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'nested triggers', 0; RECONFIGURE;
CREATE TRIGGER trg_Safe_Update
ON dbo.TableA
AFTER UPDATE
AS
BEGIN
-- @@NESTLEVEL 返回目前觸發器巢狀層級 (1=最外層)
IF @@NESTLEVEL > 1 RETURN; -- 只處理最外層觸發
-- 或檢查特定欄位是否已被觸發器更新過
IF UPDATE(LastModifiedByTrigger) RETURN;
UPDATE dbo.TableB
SET SomeCol = 'Value',
LastModifiedByTrigger = 1 -- 標記欄位防重入
WHERE ID IN (SELECT ID FROM inserted);
END;
IF TRIGGER_NESTLEVEL(OBJECT_ID('trg_Safe_Update'), 'AFTER', 'DML') > 1
RETURN;
-- 觸發器只發事件,不處理業務
CREATE TRIGGER trg_OrderCreated
ON dbo.Orders
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.DomainEvents (EventType, Payload, CreatedAt)
SELECT 'OrderCreated',
(SELECT OrderID, CustomerID, TotalAmount FROM inserted FOR JSON PATH),
GETDATE();
END;
-- 獨立背景工作處理事件
-- 應用層/Service Broker/外部訊息佇列 消費 DomainEvents 表
usp_InsertOrder, usp_UpdateOrder)-- 即時聚合改用索引視圖
CREATE VIEW vw_OrderSummary WITH SCHEMABINDING AS
SELECT CustomerID,
COUNT_BIG(*) AS OrderCount,
SUM(TotalAmount) AS TotalSpent
FROM dbo.Orders
GROUP BY CustomerID;
CREATE UNIQUE CLUSTERED INDEX IX_vw_OrderSummary ON vw_OrderSummary(CustomerID);
-- 查詢自動走索引視圖,零維護成本
CREATE EVENT SESSION TriggerMonitor ON SERVER
ADD EVENT sqlserver.module_start(
WHERE object_type = 'TR' -- TR = Trigger
),
ADD EVENT sqlserver.module_end(
WHERE object_type = 'TR'
)
ADD TARGET package0.event_file(SET filename = N'TriggerMonitor');
SELECT
t.name AS TriggerName,
OBJECT_NAME(t.parent_id) AS TableName,
t.type_desc,
OBJECT_DEFINITION(t.object_id) AS Definition
FROM sys.triggers t
WHERE t.is_ms_shipped = 0;
SELECT @@NESTLEVEL (會話級) / sp_configure 'nested triggers' (伺服器級)| 場景 | 推薦做法 | 禁止做法 |
|---|---|---|
| 核心業務流程 | 預存程序統一入口 | 觸發器中執行業務邏輯 |
| 跨表資料同步 | 預存程序內顯式 UPDATE / 索引視圖 | 觸發器呼叫預存程序做同步 |
| 審計日誌 | 觸發器寫入審計表 / CDC / 觸發器發事件 | 應用層手寫日誌 (易漏) |
| 強制完整性 (複雜 CHECK) | 觸發器阻擋 / 預存程序驗證 | 應用層驗證 (可繞過) |
| 外部整合 (Web Service, Email) | Service Broker / 背景作業 / 應用層 | 觸發器/預存程序直接呼叫外部 |
觸發器與預存程序各有其設計初衷:觸發器是資料層的被動守衛,預存程序是業務層的主動執行者。尊重邊界、單向依賴 (應用層 → 預存程序 → 觸發器),才能構建清晰、可控、高效能的資料庫系統。