应在BEFORE INSERT/UPDATE触发器中用IF判断经纬度是否在合法范围内:纬度∈[-90.0,90.0],经度∈[-180.0,180.0],需显式处理NULL,使用BETWEEN确保端点包含,配合SIGNAL SQLSTATE '45000'报错中断。

触发器里怎么判断经纬度是否越界
直接在 BEFORE INSERT 或 BEFORE UPDATE 触发器中做数值范围检查最稳妥,避免非法值写入表。纬度必须在 -90.0 到 90.0 之间(含端点),经度必须在 -180.0 到 180.0 之间(含端点)。浮点精度问题不用额外处理,数据库原生 DECIMAL 或 FLOAT 比较足够可靠。
常见错误是只校验正数或漏掉等于边界值的情况,比如写成 lat > 90 就放过了 90.0 这个合法值;还有人用字符串比较,结果 '-180' 和 '-90' 字典序乱套。
- 用
IF NOT (NEW.lat BETWEEN -90.0 AND 90.0)比IF NEW.lat 90.0更简洁且语义清晰 - 经度同理,但注意:有些业务允许
180.0和-180.0同时存在(国际日期变更线两侧),别误判为冲突 - 如果字段允许
NULL,需显式判断NEW.lat IS NULL,否则BETWEEN在NULL时返回UNKNOWN,导致校验失效
MySQL 中触发器报错中断插入的正确写法
MySQL 不支持 RAISE 或 THROW,得靠制造一个明确的、不可忽略的错误来中断流程。最常用的是调用不存在的存储函数,比如 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid latitude' —— 这是 MySQL 5.5+ 的标准方式,比旧式 INSERT INTO nonexistent_table 更干净可控。
容易踩的坑是把错误信息写成动态拼接(比如 CONCAT('Lat ', NEW.lat, ' out of range')),但 SIGNAL 的 MESSAGE_TEXT 不支持表达式,只能是字符串字面量或变量;若需动态内容,得先赋值给用户变量再引用。
- 必须用
SIGNAL,不要用CALL non_existent_proc()这类旁路方式,后者错误码不统一,应用层难区分 -
SQLSTATE推荐固定用'45000'(未定义异常),避免和系统错误码冲突 - 触发器里不能有事务控制语句(如
COMMIT),MySQL 会直接报错
PostgreSQL 怎么用 RAISE EXCEPTION 做同样校验
PostgreSQL 更直接:RAISE EXCEPTION 'Invalid longitude: %', NEW.lng 能自动中断并返回带参数的错误消息。它天然支持格式化和变量插值,比 MySQL 灵活得多。
但要注意:如果触发器定义在 AFTER 阶段,数据已经写入,再 RAISE 就来不及回滚了——必须用 BEFORE 触发器。另外,PostgreSQL 默认对浮点数比较是精确的,180.0 和 180 视为相等,无需额外 ROUND。
- 用
RAISE NOTICE测试时别忘了删掉,否则上线后日志刷屏 - 如果坐标字段类型是
POINT或GEOMETRY,校验逻辑要改用ST_X()/ST_Y()提取坐标,不能直接读字段 - PostgreSQL 触发器函数必须声明为
RETURNS TRIGGER,且末尾必须有RETURN NEW(BEFORE)或RETURN NULL(AFTER),少一句就编译失败
为什么不在应用层做这个校验
应用层校验挡不住直连数据库的写入(比如运维脚本、ETL 工具、其他服务绕过 API),也防不住并发场景下的竞态——两个事务同时读到合法值,各自算出新值后写入,可能共同越界。触发器是唯一能保证“每次写入必校验”的兜底手段。
但别指望它替代业务规则:比如“同一城市内两点距离不能超过 10km”这种复杂空间关系,得用 ST_DWithin 等 GIS 函数,在触发器里调用性能差,还可能锁表。纯数值范围才是触发器该干的活。
真正麻烦的是测试——本地开发环境没开 binlog 或 WAL 归档时,触发器行为可能和生产不一致;还有跨库同步工具(如 Canal、Debezium)有时会跳过触发器执行,得额外验证同步链路是否透传校验逻辑。

















