面对“Invalid JSON text”错误提示,是不是瞬间不知所措?别担心,MySQL 8.0其实早就为你预留了排查利器——一个名为JSON_VALID()的批量校验函数,能够悄无声息地帮你精准定位所有问题行。关键是它不会抛出异常中断查询,比逐条尝试insert或select的方式要优雅得多。
用JSON_VALID()批量筛查非法JSON值
只需在WHERE条件中嵌套JSON_VALID(),返回0即表示该行的JSON语法存在非法问题。若要扫描整个表,可以这样写:SELECT id, json_col FROM table_name WHERE JSON_VALID(json_col) = 0;。若同时需要清理空字符串或NULL,再追加一个条件:WHERE json_col = '' OR json_col IS NULL。请注意,JSON_VALID('')返回0,而JSON_VALID(NULL)返回NULL——因此空值必须单独处理。
如果字段允许为空但业务逻辑上不允许为空,建议建表时直接添加CHECK约束(MySQL 8.0.16及以上版本支持):CHECK (json_col IS NOT NULL AND JSON_VALID(json_col))。这样就能从源头杜绝脏数据进入数据库。
报错信息里“at position 0”通常意味着什么
位置0报错,基本意味着“连JSON的基本格式都不符合”。常见原因包括以下几种:空字符串''、纯空白字符(空格或换行符)、字符串形式的'null'(注意并非JSON字面量null),或者根本没有传递值(例如MyBatis-Plus没有设置默认值导致字段变为'')。
几个关键区别需要掌握:JSON_VALID('')返回0,JSON_VALID('null')返回1(合法),而JSON_VALID("null")返回0(单引号包裹双引号属于非法格式)。在Java端使用Jackson序列化时,务必确保对象非空再转换为字符串;若可能为空,应显式写成{}或[],不要传入空串。在MyBatis-Plus中,为JSON字段添加@TableField(fill = FieldFill.INSERT)并配合自动填充逻辑,能够有效避免空字符串入库。
结合Last_SQL_Error反向定位复制或导入失败源
如果主从复制卡住,或者LOAD DATA过程中报错,且Last_SQL_Error中提示“position 0”,那么直接去查看对应SQL的VALUES部分——十有八九是某一行JSON字段被拼成了''或'{...'(缺少结尾括号)。
定位方法也很直接:执行SHOW BINLOG EVENTS IN 'xxx' FROM yyy LIMIT 1(从Exec_Master_Log_Pos往前推)找到原始SQL,然后将报错SQL复制到本地,用SELECT JSON_VALID(?), ?逐一测试参数,哪个参数为空或格式畸形,结果便一目了然。另外,当从库版本低于主库时,JSON_VALID()行为保持一致,但CAST(... AS JSON)可能因sql_mode差异提前失败,这一点需要特别留意。
别依赖应用层校验,数据库约束才是最后一道防线
开发环境本地测试没有问题,一上线就报错?多半是因为测试数据没有覆盖到空值路径。仅靠代码中写StringUtils.isNotBlank()远远不够——MySQL对JSON的校验可比应用层严格得多。
最优实践是在建表时直接添加CHECK约束:feature_data JSON CHECK (JSON_VALID(feature_data) AND feature_data != '')。千万不要使用TEXT字段存储JSON再手动解析,那样既失去自动校验能力,也会丧失->操作符和JSON索引的优势。如果数据库中已经存在脏数据,可以先执行UPDATE ... SET json_col = COALESCE(NULLIF(json_col, ''), '{}') WHERE JSON_VALID(json_col) = 0;兜底处理,然后再添加上约束。
话说回来,真正棘手的并非语法错误,而是那些JSON_VALID()返回1、但后续JSON_EXTRACT()取不到值的“合法垃圾”——例如{"name": null}或{}。这类问题数据库无法处理语义层面,只能依靠业务逻辑层和团队约定来解决。毕竟,工具能够帮你拦截格式错误,却无法阻止业务上的“合理空值”。
