必须先确认字段类型为json,再用json_valid()校验;非json类型需cast或正则预筛;结构化校验用json_schema_valid()配合schema;取值优先用->>避免隐式转换;修改json须用json_set等函数而非字符串拼接。

触发器里直接用 JSON_VALID(NEW.json_col) 就行,但必须先确认字段类型是 JSON
很多人在触发器里写 IF NOT JSON_VALID(NEW.json_data) THEN ... 却发现不生效,甚至报错 Invalid JSON text —— 这往往不是函数写错了,而是 json_data 字段本身是 VARCHAR 或 TEXT 类型。MySQL 触发器中对非 JSON 类型字段调用 JSON_VALID() 会强制尝试解析字符串,一旦含非法转义、多余逗号或 Unicode BOM,就直接中断执行并报错,导致 INSERT/UPDATE 失败。
所以第一步永远是:查表结构,确认字段定义是 json_data JSON,而不是 json_data TEXT。如果是后者,JSON_VALID() 在触发器里会变得不可靠,建议先用 CAST(NEW.json_data AS JSON) 尝试转换,再捕获异常(但注意:MySQL 触发器不支持 DECLARE ... HANDLER,所以 CAST 失败仍会中断)。
- 推荐做法:建表时就用
JSON类型,避免后期补救 - 存量系统若无法改字段类型,可在触发器开头加
IF NEW.json_data REGEXP '^[[:space:]]*\{.*\}[[:space:]]*$' OR NEW.json_data REGEXP '^[[:space:]]*\[.*\][[:space:]]*$' THEN ...做粗略格式预筛(不能替代JSON_VALID,但能避开明显非法字符串) -
JSON_VALID()返回0表示解析失败,1表示语法合法(不保证语义合理)
校验 JSON 内容是否符合业务规则,得用 JSON_SCHEMA_VALID() 配合预存的 Schema
只检查“是不是合法 JSON”远远不够。比如订单扩展字段要求必须有 "status" 且值为 "paid" 或 "shipped",还要求 "items" 是非空数组、每个元素含 "sku" 和 "qty"。这种规则靠 JSON_EXTRACT() + 多层 IF 判断既啰嗦又易漏,还难维护。
MySQL 8.0.17+ 支持用 JSON_SCHEMA_VALID() 做结构化校验。你需要提前把校验规则存成常量(比如存在 SET @order_schema = '{...}'),或更稳妥地存在一张配置表里,然后在触发器中引用:
IF NOT JSON_SCHEMA_VALID(@order_schema, NEW.order_ext) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'order_ext does not match schema'; END IF;
- Schema 必须是合法 JSON 对象,且符合 JSON Schema Draft-04 标准(MySQL 不支持 Draft-07+)
- 触发器里不能直接写多行 JSON 字符串字面量(容易引号嵌套出错),建议用
CONCAT()拼接,或从SELECT schema_def FROM json_schemas WHERE name = 'order'查询获取 - 校验失败不会自动抛错,必须显式
SIGNAL,否则静默跳过
提取并判断 JSON 字段里的关键键值,优先用 ->> 而不是 JSON_EXTRACT()
触发器里常要取 NEW.config->>"$.timeout" 然后和阈值比较。这里别用 JSON_EXTRACT(NEW.config, "$.timeout"),它返回带引号的 JSON 字符串(如 "30"),跟数字 30 比较会隐式转成 0,导致逻辑错乱。
-
->等价于JSON_EXTRACT(),返回 JSON 类型值(带引号) -
->>等价于JSON_UNQUOTE(JSON_EXTRACT()),返回去引号后的 SQL 值(字符串或数字) - 如果取的是数字字段(如
"retry_count": 3),必须用->>才能得到可计算的整数;否则NEW.config->"$.retry_count" > 5实际是在比字符串"3"和5,结果恒为FALSE - 取布尔值也一样:
NEW.feature->>"$.enabled"返回'true'或'false'字符串,需配合IS TRUE或= 'true'判断
触发器中修改 JSON 字段内容,只能用 JSON_SET() / JSON_REPLACE(),不能直接赋值字符串
有人想在触发器里补全缺失字段:SET NEW.meta = '{"version": "1.0", "created_at": "' + NOW() + '"}'; —— 这样写在 JSON 类型列上会直接报错 Data too long for column 'meta' 或 Invalid JSON text,因为 MySQL 会把拼接结果当普通字符串塞进去,不经过 JSON 解析。
正确做法是用 JSON 函数构造:
SET NEW.meta = JSON_SET(
COALESCE(NEW.meta, '{}'),
'$.version', '1.0',
'$.created_at', NOW()
);
-
COALESCE(NEW.meta, '{}')防止NEW.meta为 NULL 导致整个表达式返回 NULL -
JSON_SET()会插入新键或覆盖旧键;JSON_REPLACE()只覆盖已存在键,不存在就静默忽略;JSON_INSERT()只插入不存在的键 - 所有参数中的路径(如
'$.version')必须用单引号包裹,且路径字符串本身不能含未转义的单引号 - 触发器中不能用
JSON_MERGE_PATCH()修改嵌套对象——它会把整个子结构替换成新对象,容易误删同级其他字段
UPDATE 同一张表),所以所有 JSON 校验和修正都必须通过 SET NEW.xxx = ... 完成,不能退而求其次去查配置表再更新自己——那会直接报错 Can't update table 't' in stored function/trigger。











