必须用 BEFORE UPDATE 触发器配合 RAISE_APPLICATION_ERROR 拦截非业务时间修改,且需用 OR 判断跨零点时段(如 EXTRACT(HOUR FROM SYSDATE) >= 22 OR EXTRACT(HOUR FROM SYSDATE)
Oracle 里不能靠
RAISE_APPLICATION_ERROR单独拦住非业务时间的修改——必须配合BEFORE UPDATE触发器,且不能依赖SYSDATE的小时部分做跨零点判断。BEFORE UPDATE 触发器是唯一能真正拦截的时机
Oracle 不允许在
AFTER UPDATE中阻止数据写入:行已改完,再报错也晚了。只有BEFORE UPDATE能在语句执行前掐断流程。
常见错误是写了AFTER还加RAISE_APPLICATION_ERROR,结果 UPDATE 照常提交,只多打一行错误日志。
触发器必须声明为FOR EACH ROW,否则无法访问:NEW和:OLD;但如果你只是校验时间、不关心具体哪行,也可以用BEFORE UPDATE ON table_name(语句级),不过更推荐行级——兼容性好、逻辑清晰。HOUR(SYSDATE) BETWEEN 22 AND 6 是错的,得拆成 OR 表达式
Oracle 没有原生跨零点的
BETWEEN支持。HOUR(SYSDATE) BETWEEN 22 AND 6永远为假,因为 22 > 6。
正确写法是:EXTRACT(HOUR FROM SYSDATE) >= 22 OR EXTRACT(HOUR FROM SYSDATE) 。
注意:EXTRACT(HOUR FROM ...)返回的是 0–23 的整数,覆盖了 0:00–5:59,和>= 22(22:00–23:59)合起来才是完整夜间区间。
如果要精确到分钟(比如 22:30 开始禁写),得组合:EXTRACT(HOUR FROM SYSDATE) = 22 AND EXTRACT(MINUTE FROM SYSDATE) >= 30或EXTRACT(HOUR FROM SYSDATE) > 22。RAISE_APPLICATION_ERROR 必须出现在 IF 判断之后,且不能漏掉 ROLLBACK?不,Oracle 里不用写
这是和 SQL Server 最关键的区别:
Oracle 的RAISE_APPLICATION_ERROR在BEFORE触发器中会自动终止当前语句并回滚该行的变更(对单行操作而言),无需手动ROLLBACK;
但如果外层有应用开启的事务(比如 PL/SQL 块里显式BEGIN...END包裹了 UPDATE),触发器报错后事务仍处于活动状态,应用需自己处理异常并决定是否ROLLBACK;
所以重点不是“要不要写 ROLLBACK”,而是确保应用层捕获ORA-20001类错误并响应;
示例片段:CREATE OR REPLACE TRIGGER tr_block_offhours<br> BEFORE UPDATE ON orders<br> FOR EACH ROW<br>DECLARE<br> v_hour NUMBER := EXTRACT(HOUR FROM SYSDATE);<br>BEGIN<br> IF v_hour >= 22 OR v_hour RAISE_APPLICATION_ERROR(-20001, '非业务时间禁止修改订单数据');<br> END IF;<br>END;时区和服务器时间偏差是最大盲区
SYSDATE返回的是数据库实例所在操作系统的本地时间,不是 UTC,也不随客户端时区变。
常见坑:
— 数据库服务器在东八区,但 DBA 从美国西海岸用 SQL*Plus 连接,SYSDATE仍是北京时间;这本身没问题,但若你的“业务时间”定义是按 UTC 22:00–06:00,则必须用SYSTIMESTAMP AT TIME ZONE 'UTC'提取小时;
— 服务器 NTP 同步失效,时间慢了 8 分钟,会导致凌晨 5:58 的合法更新被放过;
— Oracle 12c+ 支持DBTIMEZONE和SESSIONTIMEZONE,但SYSDATE始终绑定操作系统,别指望ALTER SESSION SET TIME_ZONE能改变它;
上线前务必用SELECT SYSDATE FROM DUAL对照系统时间,误差超过 30 秒就得调 NTP。












