業務需求:某些核心欄位(實名信息、聯繫方式、密碼、賬戶餘額)一旦寫入**永不允許修改**,但允許更新其他欄位。MySQL 觸發器可在行級攔截 UPDATE,將核心欄位強制恢復為舊值,實現「字段級 WORM(Write Once Read Many)」。
DROP TRIGGER IF EXISTS `trg_members_immutable`; DELIMITER // CREATE TRIGGER `trg_members_immutable` BEFORE UPDATE ON `members` FOR EACH ROW BEGIN -- 強制恢復核心欄位為舊值 SET NEW.`name` = OLD.`name`; SET NEW.`cellphone` = OLD.`cellphone`; SET NEW.`email` = OLD.`email`; SET NEW.`password` = OLD.`password`; END// DELIMITER ;
BEFORE UPDATE:在寫入存儲引擎前修改 NEW 行,無額外 I/O。FOR EACH ROW:逐行生效,批量 UPDATE 同樣攔截。CREATE TABLE `account` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user` VARCHAR(50) NOT NULL DEFAULT '0',
`cash` DECIMAL(18,2) NOT NULL DEFAULT '0.00'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 業務只能 INSERT 流水,餘額由觸發器/應用層事務維護
INSERT INTO `account` (`user`, `cash`) VALUES ('neo', -10), ('neo', -5), ('neo', 30), ('neo', -20);
-- 防篡改觸發器
DROP TRIGGER IF EXISTS `trg_account_cash_immutable`;
DELIMITER //
CREATE TRIGGER `trg_account_cash_immutable`
BEFORE UPDATE ON `account`
FOR EACH ROW
BEGIN
SET NEW.`cash` = OLD.`cash`; -- 任何直接 UPDATE cash 皆無效
END//
DELIMITER ;
CREATE TABLE `audit_log` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`table_name` VARCHAR(64) NOT NULL,
`row_id` BIGINT NOT NULL,
`attempted_column` VARCHAR(64) NOT NULL,
`old_value` TEXT,
`new_value` TEXT,
`user_host` VARCHAR(128),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
DROP TRIGGER IF EXISTS `trg_members_audit`;
DELIMITER //
CREATE TRIGGER `trg_members_audit`
BEFORE UPDATE ON `members`
FOR EACH ROW
BEGIN
IF NEW.`name` != OLD.`name` THEN
INSERT INTO `audit_log` (`table_name`,`row_id`,`attempted_column`,`old_value`,`new_value`,`user_host`)
VALUES ('members', OLD.`id`, 'name', OLD.`name`, NEW.`name`, USER());
END IF;
IF NEW.`cellphone` != OLD.`cellphone` THEN
INSERT INTO `audit_log` ... VALUES ('members', OLD.`id`, 'cellphone', OLD.`cellphone`, NEW.`cellphone`, USER());
END IF;
-- 其他核心欄位同理
-- 最後仍需恢復舊值
SET NEW.`name` = OLD.`name`;
SET NEW.`cellphone` = OLD.`cellphone`;
SET NEW.`email` = OLD.`email`;
SET NEW.`password` = OLD.`password`;
END//
DELIMITER ;
INSERT audit_log 失敗,原 UPDATE 也會失敗(報錯),需確保審計表寫入極高可用。DROP/TRUNCATE 權限)和備份策略。CREATE TRIGGER 同步到從庫,行級觸發邏輯在從庫重放 UPDATE 時同樣生效,保持一致。BEFORE UPDATE,按創建順序執行,注意順序依賴。| 方案 | 實現難度 | 性能 | 靈活性 | 推薦場景 |
|---|---|---|---|---|
| 觸發器強制恢復 | 低 | 極高 | 中(列級硬編碼) | 核心欄位少、極少變更 |
| 應用層攔截(Service 層) | 中 | 高 | 高(可配置規則) | 欄位多、規則復雜、需審計 |
| 資料庫權限(列級 GRANT) | 低 | 極高 | 低(MySQL 不支援列級 UPDATE 權限) | 不適用 |
| 視圖 + INSTEAD OF 觸發器 | 高 | 中 | 高 | 需對外暴露只寫介面 |
觸發器方案以「零業務改造、微秒級開銷、強制生效」成為核心欄位防篡改的首選利器。