必须用 BEFORE INSERT/UPDATE 配合 SIGNAL SQLSTATE '45000',因为 AFTER 触发器执行时数据已落盘,SIGNAL 无法回滚主语句变更;BEFORE 阶段可在写入前校验、修改 NEW 值或中断操作,是唯一有效的拦截时机。

必须用 BEFORE UPDATE 和 BEFORE INSERT 触发器配合 SIGNAL SQLSTATE '45000',否则修改早已落盘,拦截纯属无效。
为什么不能只靠 AFTER 触发器
AFTER UPDATE 执行时,NEW 值已写入磁盘。哪怕立刻 SIGNAL 报错,非法修改也完成了。MySQL 不允许在 AFTER 阶段回滚主语句的变更——这不是限制,是机制决定的不可逆。
真正起效的只有 BEFORE 阶段:它发生在数据落盘前最后一刻,可读 OLD 和 NEW,也能用 SIGNAL 中断整个语句。
- 别信“
AFTER+ROLLBACK”能拦住修改——它只回滚触发器自身逻辑,不影响原UPDATE -
BEFORE INSERT同样关键;否则用户绕过UPDATE直接INSERT就能跳过校验 - 所有触发器都按行执行,批量操作会逐行校验,性能敏感表需压测
WEEKDAY(NOW()) 和 HOUR(NOW()) 怎么组合才不漏边界
工作日按周一为 0、周日为 6 计算,工作时间若要求 9:00–17:59,则不能用 BETWEEN 9 AND 17——它会放过 17:00:01 及之后的所有时间。
正确写法是拆开比较,并显式排除跨天或下班后场景:
-
WEEKDAY(NOW()) IN (0,1,2,3,4)表示周一至周五(含) HOUR(NOW()) >= 9 AND HOUR(NOW()) 覆盖 9:00–17:59(注意是,不是)- 若需严格到分钟,再加
MINUTE(NOW()) (冗余但显式,避免未来改逻辑时出错) - 跨天时段如 22:00–06:00 必须写成:
HOUR(NOW()) >= 22 OR HOUR(NOW()) ,不能用BETWEEN
服务器时区不对怎么办
NOW() 返回的是数据库服务器本地时区时间,不是应用所在地时间。如果服务器在 UTC,而业务要求按北京时间(UTC+8)判断,NOW() 直接用就会错 8 小时。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
解决办法是显式转换:
- 用
CONVERT_TZ(NOW(), '+00:00', '+08:00')得到北京时间后再提取星期和小时 - 对应地,
WEEKDAY(CONVERT_TZ(NOW(), '+00:00', '+08:00'))和HOUR(CONVERT_TZ(NOW(), '+00:00', '+08:00')) - 务必确认目标时区字符串有效(如
'+08:00'而非'Asia/Shanghai',后者依赖系统时区表且可能缺失) - 测试时连上数据库直接运行
SELECT NOW(), CONVERT_TZ(NOW(), '+00:00', '+08:00');看结果是否符合预期
触发器生效了,但应用没报错?
常见原因是 sql_mode 不包含 STRICT_TRANS_TABLES,导致 MySQL 在某些异常输入下静默修正而非报错,使你的 SIGNAL 条件根本不会触发。
验证方式:
- 执行
SELECT @@sql_mode;,确认返回值含STRICT_TRANS_TABLES - 若不含,需在启动配置或会话中设置:
SET sql_mode = 'STRICT_TRANS_TABLES,...'; -
SIGNAL SQLSTATE '45000'是标准用户自定义错误码,应用层应捕获该状态码而非依赖错误消息文本 - 别在触发器里查其他表或调函数——会拖慢所有写入,还可能引发死锁或权限失败
最易被忽略的一点:触发器只拦标准 SQL 写入,对 LOAD DATA INFILE、运维直连执行的脚本、ETL 工具的批量导入默认无效。防护闭环的关键,是确保所有写入口都走同一套账号权限体系,并禁用高危命令权限。










