必须用 SIGNAL SQLSTATE '45000' 强制中断事务,否则判断逻辑无效;需为 INSERT/UPDATE/DELETE 分别创建触发器,统一使用 WEEKDAY() 与 HOUR() 拆分比较避开边界陷阱,并确保 MySQL ≥ 5.5 且 sql_mode 含 STRICT_TRANS_TABLES。

必须用 SIGNAL SQLSTATE '45000' 强制中断,否则判断逻辑完全无效——MySQL 触发器不会自动拒绝操作,不抛异常就等于放行。
BEFORE INSERT/UPDATE/DELETE 中必须显式调用 SIGNAL
常见错误是只做 IF 判断然后 SELECT 或赋值变量,比如:
IF HOUR(NOW()) NOT BETWEEN 9 AND 17 THEN SELECT 'blocked'; -- ❌ 直接报错 1415:Not allowed to return a result set from a trigger END IF;
即使改成 SET @msg = 'blocked';,只要没 SIGNAL,语句照样执行成功。
-
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Off-hours write denied';是唯一可靠方式 - MySQL 版本必须 ≥ 5.5,且
sql_mode包含STRICT_TRANS_TABLES,否则SIGNAL可能被静默忽略 - 不能在触发器里写
ROLLBACK:MySQL 明确禁止在触发器中执行事务控制语句
时间判断要避开 WEEKDAY() 和 HOUR() 的边界陷阱
WEEKDAY(NOW()) 返回 0(周一)到 6(周日),HOUR(NOW()) 是整点小时数(0–23)。直接套用 BETWEEN 容易漏掉关键时间段。
例如想拦住“周五 17:00 之后、周一 9:00 之前”,以下写法有漏洞:
WEEKDAY(NOW()) IN (5,6) OR HOUR(NOW()) NOT BETWEEN 9 AND 17 -- ❌ 周五 17:30 仍返回 HOUR=17,会放过
正确做法是拆开比较:
- 排除周末:
WEEKDAY(NOW()) IN (5,6) - 工作日限制时段:
HOUR(NOW()) 17(即 9:00 前、17:00 后) - 若需精确到分钟(如允许至 17:59),补上:
(HOUR(NOW()) = 17 AND MINUTE(NOW()) > 59)(逻辑上虽不可能,但显式写出更防误读)
必须为每种 DML 操作单独建触发器
只建 BEFORE UPDATE 触发器,拦不住 INSERT 新记录或 DELETE 旧数据。非工作时间的风险来自所有写入路径。
- 对关键表(如
orders、payments),至少创建三个触发器:trg_block_offhours_insert、trg_block_offhours_update、trg_block_offhours_delete -
BEFORE INSERT和BEFORE UPDATE可访问NEW;BEFORE DELETE只能用OLD,注意字段引用差异 - 三者的时间判断逻辑必须完全一致,避免某类操作漏检
触发器不是万能的,得知道它不管什么
它只拦截标准 SQL 接口的 INSERT/UPDATE/DELETE,对这些操作无能为力:
-
LOAD DATA INFILE(MySQL)、COPY(PostgreSQL)等批量导入命令 - 直接修改磁盘文件、挂载只读介质后启动实例(需配合
--innodb-read-only=1) - 应用层绕过 ORM 手写 SQL、DBA 用
mysql客户端直连执行
真正难防的是那些不走触发器路径的写入——比如运维凌晨跑一个修复脚本,或者 ETL 工具用 REPLACE INTO 批量刷数。这时候触发器只是最后一道网,网眼大小取决于你是否意识到它根本没覆盖那些入口。


















