必须用 before 触发器拦截;postgresql 中用 current_time 比对时间范围、extract(dow from current_timestamp)判周几,时区不匹配需显式转换;mysql 须用 signal 抛错中断,禁用 rollback 或静默赋值。

必须用 BEFORE 触发器,AFTER 或 INSTEAD OF 都拦不住——数据在触发器运行前就已落盘或语义已被接管。
PostgreSQL 中必须用 CURRENT_TIME 和 EXTRACT(DOW FROM CURRENT_TIMESTAMP)
误用 NOW() 或 CURRENT_TIMESTAMP 做小时比对,容易因时区隐式转换失败或类型不匹配报错 ERROR: operator does not exist: timestamp with time zone >= text。正确做法是:
-
CURRENT_TIME直接返回TIME WITHOUT TIME ZONE,适合字符串时间范围比对,例如CURRENT_TIME BETWEEN '09:00' AND '17:59' - 星期判断用
EXTRACT(DOW FROM CURRENT_TIMESTAMP)(0=周日,6=周六),别用EXTRACT(ISODOW FROM ...)(1=周一),否则逻辑翻转 - 若数据库时区为
UTC,但业务按北京时间执行,必须先转换:(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Shanghai')::TIME - 禁止在函数里查表、调用含
SELECT的自定义函数,否则高并发下易触发40001 Serialization Failure
MySQL 中必须用 SIGNAL SQLSTATE '45000',不能靠 ROLLBACK 或赋值回填
MySQL 触发器内 ROLLBACK 无效,SET NEW.col = OLD.col 是静默覆盖,应用层收不到错误,会误以为更新成功。唯一可靠中断方式是抛错:
- 用
WEEKDAY(NOW())(0=周一,6=周日)配合HOUR(NOW()),避免DAYOFWEEK()(1=周日)导致条件写反 - 跨零点时段(如夜间禁写)不能写
BETWEEN 22 AND 6,必须拆成HOUR(NOW()) >= 22 OR HOUR(NOW()) - 若需精确到分钟(如禁写从 22:30 开始),补上
MINUTE(NOW()):例如(HOUR(NOW()) = 22 AND MINUTE(NOW()) >= 30) OR HOUR(NOW()) > 22 OR HOUR(NOW()) - 确认 MySQL 版本 ≥ 5.5 且
sql_mode含STRICT_TRANS_TABLES,否则SIGNAL可能被忽略
SQL Server 中必须用 AFTER UPDATE + RAISERROR + ROLLBACK TRANSACTION 三件套
SQL Server 没有标准 BEFORE,只能事后补救,但漏掉任一环节都会让非法修改生效:
- 用
DATEPART(HOUR, GETDATE())提取小时(0–23),DATEPART(WEEKDAY, GETDATE())返回值依赖SET DATEFIRST,建议显式设为SET DATEFIRST 1(周一为 1)再判断 -
RAISERROR错误级别必须 ≥ 16,级别 10 不中断执行;消息后必须紧跟ROLLBACK TRANSACTION,不能包在IF...ELSE的ELSE块里 - 批量更新时,要检查
inserted表是否含违规行:IF EXISTS (SELECT 1 FROM inserted WHERE DATEPART(HOUR, GETDATE()) >= 22 OR DATEPART(HOUR, GETDATE()) - 注意:
GETDATE()是服务器本地时间,若服务器在 UTC,业务按北京时间,则需先转换:CONVERT(TIME, GETDATE() AT TIME ZONE 'UTC' AT TIME ZONE 'China Standard Time')
真正难的不是写出触发器语法,而是确保它对所有写入口生效——运维直连、ETL 工具、旧版脚本只要绕过应用层,就可能跳过校验;而触发器本身又不能查配置表、不能调用耗时函数,所有规则必须硬编码且兼容高并发。当前时间是 2026 年 10 月 1 日星期四 14:19,正好落在工作时段内,但上线前务必用跨零点、跨周末、跨时区的测试用例压测。










