应使用 getdate() 获取服务器本地时间,再用 datepart(hour, getdate()) >= 22 or datepart(hour, getdate())

必须用 BEFORE 类型的触发器逻辑(SQL Server 实际靠 AFTER + RAISERROR + ROLLBACK TRANSACTION 组合模拟),且时间判断必须基于服务器本地时区,否则规则形同虚设。
SQL Server 触发器里怎么正确判断非办公时间
SQL Server 没有 BEFORE 触发器,只能在 AFTER UPDATE 中立即中断。关键不是“什么时候查时间”,而是“查什么时间、怎么比”。
-
GETDATE()返回 SQL Server 所在操作系统的本地时间,不是 UTC,也不是应用层传入的时间——若数据库服务器在 UTC+8,但运维从美国西海岸连进来,GETDATE()仍按东八区算,这反而是对的;错的是你把日志里的 UTC 时间和它混着比 - 别写
DATEPART(HOUR, GETDATE()) BETWEEN 22 AND 6:这个表达式永远为假,因为 22 > 6,BETWEEN 不支持跨零点 - 正确写法是拆开:
DATEPART(HOUR, GETDATE()) >= 22 OR DATEPART(HOUR, GETDATE()) ,覆盖 22:00–23:59 和 00:00–05:59 - 如果要精确到分钟(比如 22:30 开始禁写),得加
DATEPART(MINUTE, GETDATE())判断,或直接用CONVERT(TIME, GETDATE())比对:CONVERT(TIME, GETDATE()) NOT BETWEEN '09:00' AND '17:59'
AFTER UPDATE 触发器中如何真正中止更新
只抛错不回滚,UPDATE 依然会提交。必须三者齐备:条件判断 → 报错 → 回滚 → 显式退出。
- 用
IF EXISTS (SELECT 1 FROM inserted WHERE ...)判断是否命中非法时间,而不是简单IF ...:这样能兼容批量更新(多行同时改),避免漏拦 -
RAISERROR('非业务时间禁止修改', 16, 1):错误级别 16 是用户可捕获的标准级别,别用 20 以上,否则可能被当成系统崩溃 -
ROLLBACK TRANSACTION必须紧跟在RAISERROR后面,且不能被 ELSE 分支包裹;否则嵌套事务里可能只回滚内层,外层继续提交 - 加上
RETURN或END显式终止后续逻辑,防止误执行其他语句(比如审计日志写入)
为什么应用层校验不够,还得加触发器
触发器是最后一道 SQL 接口级防线,但它的盲区必须清楚:
- 它拦不住
BULK INSERT、OPENROWSET、SSIS 包导入等绕过常规 DML 的操作——这些需靠权限控制(如收回ADMINISTER BULK OPERATIONS) - 它不拦截
TRUNCATE TABLE:这个命令不走触发器,要用 DDL 触发器或权限限制(撤掉ALTER权限) - 高频写入表上启用该触发器有性能损耗,尤其是带
inserted子查询的判断;建议只对核心表(如payments、user_profiles)启用 - 服务器时间若未同步 NTP,偏差超 5 分钟就可能导致合法操作被误杀——上线前必须确认
SELECT GETDATE()和业务期望时间一致
最常被忽略的一点:触发器里的 GETDATE() 是语句开始执行时的时间戳,不是每行检查时动态刷新的。如果一个 UPDATE 影响 10 万行,整个过程耗时 3 分钟,它仍只用最初那个时间点做判断——这既是简化,也是约束。你要的不是“实时逐行校验”,而是“这次操作是否发起于非办公时段”。











