SQL Server无BEFORE触发器,须用AFTER UPDATE+RAISERROR+ROLLBACK+RETURN模拟拦截;非办公时间判断不可用BETWEEN 22 AND 6(因22>6恒假),应改用>=22 OR

直接说结论:SQL Server 没有 BEFORE 触发器,必须用 AFTER UPDATE + RAISERROR + ROLLBACK TRANSACTION + RETURN 四件套组合模拟拦截,且时间判断必须基于 GETDATE()(服务器本地时间),不能用 BETWEEN 写跨零点区间。
DATEPART(HOUR, GETDATE()) 判断非办公时间为什么不能用 BETWEEN
常见错误是写 DATEPART(HOUR, GETDATE()) BETWEEN 22 AND 6——这个表达式永远为假,因为 BETWEEN 要求左值 ≤ 右值,而 22 > 6。SQL Server 不支持跨零点的区间语法。
- 正确逻辑是拆成两段:
DATEPART(HOUR, GETDATE()) >= 22 OR DATEPART(HOUR, GETDATE()) ,覆盖 22:00–23:59 和 00:00–05:59 - 如果业务要求精确到分钟(如 22:30 禁写),改用
CONVERT(TIME, GETDATE()) NOT BETWEEN '09:00' AND '17:59'更直观 - 别用
HOUR(GETDATE()):函数名不标准,SQL Server 官方只认DATEPART(HOUR, ...),HOUR()是兼容模式下的别名,不推荐在生产触发器里用
AFTER UPDATE 触发器里怎么真正中止更新而不留后患
只抛错不回滚,UPDATE 依然会提交;只回滚不退出,后续语句(比如审计日志插入)可能继续执行,导致数据不一致。
- 必须用
IF EXISTS (SELECT 1 FROM inserted WHERE ...)判断,而不是IF ...:这样才能兼容批量更新(一次改多行),避免漏拦某几行 -
RAISERROR('非业务时间禁止修改', 16, 1):错误级别 16 是用户可捕获的标准级别;别用 20+,否则客户端可能当 SQL Server 崩溃处理 -
ROLLBACK TRANSACTION必须紧跟在RAISERROR后,且不能包在ELSE或嵌套IF里,否则在已有事务上下文中可能只回滚内层 - 最后加
RETURN或END,显式终止触发器剩余逻辑,防止误写审计日志、调用存储过程等副作用
为什么 GETDATE() 是对的,但你可能用错了
GETDATE() 返回的是 SQL Server 实例所在操作系统的本地时间,不是 UTC,也不是你应用服务器或运维终端的时区——这恰恰是对的,前提是数据库服务器时区已设为业务所在地标准时区(如 China Standard Time)。
- 服务器时间不准(NTP 未启用或偏差 >5 分钟),会导致 5:58 的更新被放过,或 6:02 的合法操作被误杀
- 若数据库服务器在 UTC,但业务按北京时间(UTC+8)定义“非办公时间”,则不能直接用
GETDATE(),得用DATEADD(HOUR, 8, GETDATE())手动偏移(不推荐),更稳妥的是统一把服务器时区改成 CST 并启用 NTP -
SYSDATETIMEOFFSET()能返回带时区的时间,但它始终反映服务器设置,不随连接 SESSION 变化;在触发器里用它反而增加复杂度,无必要
最易被忽略的一点:这个触发器只拦 UPDATE 和 INSERT,但完全不拦 TRUNCATE TABLE 和 BULK INSERT ——它们绕过 DML 触发器。真要堵死,得配合权限控制(如撤掉 ADMINISTER BULK OPERATIONS 和 ALTER 权限),或者另加 DDL 触发器监控 TRUNCATE。










