必须用 before 触发器拦截非法更新,mysql 用 before update + signal 抛错,sql server 用 after update + raiserror + rollback,postgresql 用 before update + raise exception,且须统一处理时区与跨零点逻辑。

必须用 BEFORE 触发器,AFTER 类型完全无效——数据已写入,报错也撤不回。
MySQL 中用 BEFORE UPDATE + SIGNAL SQLSTATE '45000' 拦截
MySQL 不支持在触发器里靠 RETURN 或静默赋值中断操作,唯一可靠方式是抛错。常见错误是只写 IF ... THEN SELECT 1; 或 SET @msg = 'off-hours';,这等于没拦。
- 时间判断必须避开
BETWEEN 22 AND 6这种跨零点写法,否则永远不生效:改用HOUR(NOW()) >= 22 OR HOUR(NOW()) - 工作日判断推荐
WEEKDAY(NOW()) NOT IN (5,6)(周一=0,周日=6),别用DAYOFWEEK()(它返回 1=周日)导致逻辑翻转 - 若需精确到分钟(比如禁写从 22:30 开始),补上
MINUTE(NOW()):例如(HOUR(NOW()) = 22 AND MINUTE(NOW()) >= 30) OR HOUR(NOW()) > 22 OR HOUR(NOW()) - 触发器内禁止查当前表(ERROR 1442),所以规则必须硬编码,不能从配置表读取时段
SQL Server 中用 AFTER UPDATE + RAISERROR + ROLLBACK 三件套
SQL Server 没有 BEFORE,只能“事后补救”,但三步缺一不可:判断 → 报错 → 回滚 → 显式退出。漏掉任何一步都会让非法修改成功落库。
- 小时提取用
DATEPART(HOUR, GETDATE())(返回 0–23),跨零点时段写成DATEPART(HOUR, GETDATE()) >= 22 OR DATEPART(HOUR, GETDATE()) -
RAISERROR('Off-hours update denied', 16, 1)错误级别必须是 16;级别 10 不中断执行,级别 20+ 会断开连接 -
ROLLBACK TRANSACTION必须紧跟RAISERROR后,且不能包在ELSE块里——否则嵌套事务可能只回滚内层 - 批量更新时,要用
IF EXISTS (SELECT 1 FROM inserted WHERE DATEPART(HOUR, GETDATE()) >= 22 OR DATEPART(HOUR, GETDATE()) 判断,而不是简单 <code>IF,否则只检查第一行
PostgreSQL 中用 BEFORE UPDATE + RAISE EXCEPTION
PostgreSQL 的 NOW() 带时区,比对时间建议用 CURRENT_TIME(只取时间部分),避免时区转换干扰。用 EXTRACT(DOW FROM CURRENT_TIMESTAMP) 判断星期几(0=周日),别依赖本地化设置。
- 小时判断统一用
EXTRACT(HOUR FROM CURRENT_TIME),跨零点写成EXTRACT(HOUR FROM CURRENT_TIME) >= 22 OR EXTRACT(HOUR FROM CURRENT_TIME) - 时间段比对更直观:用
CURRENT_TIME = '22:00',字符串格式必须匹配TIME类型 - 触发器函数必须
RETURN NEW,漏掉这句字段不会更新;用RETURN NULL可中止操作,但需确保函数定义为RETURNS TRIGGER - 函数语言必须是
plpgsql,sql函数不支持NEW行变量
真正难的不是写条件,而是所有写入口都得经过它——运维直连、ETL 工具、LOAD DATA、COPY 都绕过触发器。当前时间是 23:15,正好在禁写时段,但如果你的数据库服务器时区设的是 UTC,而业务按北京时间(UTC+8)要求,那现在其实是 07:15,该放行。时区错位是最容易被忽略的致命点。











