某 ERP 系統需維護「入庫單(income)」、「出庫單(outlay)」、「庫存餘額表(balance)」。每當入/出庫單增刪改時,自動同步更新餘額表。對比「應用層代碼控制」與「資料庫觸發器」兩種方案:
| 維度 | 應用層控制 | 資料庫觸發器 |
|---|---|---|
| 資料庫訪問次數 | 多次往返 | 單次事務內完成 |
| 可移植性 | 高(業務邏輯集中) | 低(綁定特定資料庫) |
| 維護複雜度 | 高(易漏改、事務邊界難控) | 低(集中在資料庫層) |
| 資料一致性風險 | 高(分散式事務、並發衝突) | 低(ACID 保證) |
考慮到庫存業務對一致性極度敏感,且系統長期鎖定 Oracle,最終採用觸發器方案。
CREATE TABLE income (
id NUMBER PRIMARY KEY,
pay_amount NUMBER NOT NULL -- 入庫金額
);
CREATE TABLE outlay (
id NUMBER PRIMARY KEY,
outlay_amount NUMBER NOT NULL -- 出庫金額
);
CREATE TABLE balance (
id NUMBER PRIMARY KEY,
balance NUMBER DEFAULT 0 -- 庫存餘額
);
CREATE OR REPLACE TRIGGER tri_add
AFTER INSERT OR DELETE OR UPDATE ON income
FOR EACH ROW
DECLARE
v_delta NUMBER;
BEGIN
IF DELETING THEN
v_delta := -NVL(:OLD.pay_amount, 0);
ELSIF UPDATING THEN
v_delta := NVL(:NEW.pay_amount, 0) - NVL(:OLD.pay_amount, 0);
ELSE -- INSERTING
v_delta := NVL(:NEW.pay_amount, 0);
END IF;
UPDATE balance
SET balance = NVL(balance, 0) + v_delta
WHERE id = :NEW.id; -- 假設單一餘額記錄,實際可依業務擴展
END;
/
CREATE OR REPLACE TRIGGER tri_cut
AFTER INSERT OR DELETE OR UPDATE ON outlay
FOR EACH ROW
DECLARE
v_delta NUMBER;
BEGIN
IF DELETING THEN
v_delta := NVL(:OLD.outlay_amount, 0); -- 刪除出庫單 = 回庫存
ELSIF UPDATING THEN
v_delta := NVL(:OLD.outlay_amount, 0) - NVL(:NEW.outlay_amount, 0);
ELSE -- INSERTING
v_delta := -NVL(:NEW.outlay_amount, 0); -- 新增出庫單 = 扣庫存
END IF;
UPDATE balance
SET balance = NVL(balance, 0) + v_delta
WHERE id = :NEW.id;
END;
/
Oracle 提供三個布林函數,互斥且僅一個為 TRUE,配合 IF-ELSIF-ELSE 精確分流。
INSERT::OLD 全 NULL,:NEW 為新值。DELETE::OLD 為舊值,:NEW 全 NULL。UPDATE:兩者皆有值,核心邏輯 = 新值 - 舊值(入庫)或 舊值 - 新值(出庫)。欄位允許 NULL 時,NVL(col, 0) 避免算術運算產生 NULL 導致餘額錯誤。
保證每筆 DML 逐行觸發,支援批量操作(如 INSERT SELECT)正確累加。
COMPOUND TRIGGER 統一管理。IF NOT g_recursive THEN ... END IF;。DISABLE TRIGGER → 批量更新餘額 → ENABLE TRIGGER。balance 表的 UPDATE 權限,避免授予過高權限。觸發器將「庫存扣減/回補」這類強一致性邏輯下沉到資料庫核心層,消除了應用層分散式事務的複雜度。雖然犧牲了可移植性,但在 Oracle 為主力的企業級系統中,是極具性價比的架構決策。