必须用MySQL原生JSON语法操作,先确认字段类型为json,再用->>安全查询或JSON_SET/JSON_REPLACE更新;类型不符则语法失效,where('info->status','active')因返回带引号字符串导致比较失败。

直接查或改 JSON 字段里的某个值,必须用 MySQL 原生语法层操作,不能依赖 ORM 默认行为——否则 where('info->status', 'active') 这种写法会返回带引号的字符串 "active",和 PHP 里裸字符串 'active' 比较永远为 false。
确认字段确实是 JSON 类型,不是 TEXT/VARCHAR
这是所有操作的前提。类型不对,后续语法全失效。
- 执行
SHOW COLUMNS FROM user LIKE 'info';,检查Type列是否为json;如果不是,->>和JSON_SET都会报错,比如FUNCTION database.JSON_UNQUOTE does not exist - 若字段是
text或varchar,只能退回到whereRaw('JSON_EXTRACT(info, "$.email") = ?', [$email]),但注意$email必须是合法 JSON 字符串,例如"\"test@example.com\""(双引号加转义) - 升级存量系统时,可用
ALTER TABLE user MODIFY info JSON;转换字段类型,但前提是现有数据全部合法 JSON,否则会中断
用 ->> 安全取值并用于 WHERE 条件
->> 是 JSON_UNQUOTE(JSON_EXTRACT()) 的简写,它返回无引号纯值,可直接参与比较。
- 正确写法:
where('info->>$.email', 'test@example.com'),MySQL 返回test@example.com(不带引号) - 错误写法:
where('info->$.email', 'test@example.com'),返回"test@example.com",PHP 中与'test@example.com'比较结果为 false - 嵌套路径也一样:
where('info->>$.address.city', 'Shanghai'),支持多层对象和数组索引,如info->>$.tags[0]
用 JSON_SET/JSON_REPLACE 局部更新,避免整字段覆盖
整字段写入(如 update(..., ['info' => [...]]))会丢失原有结构,且在高并发下易引发竞态;局部更新更安全、高效。
-
JSON_SET(info, '$.email', 'new@b.com'):键存在则替换,不存在则新增 -
JSON_REPLACE(info, '$.email', 'new@b.com'):只替换已存在的键,不存在则静默忽略 - 多个键可一次更新:
JSON_SET(info, '$.email', 'a@b.com', '$.age', 26) - 更新数组元素:
JSON_SET(info, '$.skills[0]', 'Go'),索引越界会自动扩容
查 JSON 数组是否含某值,必须先 json_encode 再传参
whereJsonContains 在 ThinkPHP 等框架中容易误用,底层生成的 SQL 若参数未 JSON 编码,会导致 JSON_CONTAINS(tags, php) 这类非法语句,MySQL 直接报错。
- 用户输入
$tag = 'frontend',必须先处理:$tagJson = json_encode($tag, JSON_UNESCAPED_UNICODE);→ 得到"frontend" - 再调用:
whereRaw('JSON_CONTAINS(tags, ?, "$.skills")', [$tagJson]),强制限定路径,防止跨字段误匹配 - 务必前置
whereNotNull('tags'),因为JSON_CONTAINS(NULL, ...)返回NULL,查询结果为空
最常被跳过的一步是验证虚拟列 + 索引是否真生效:即使建了 info->>'$.status' 的生成列并加了索引,若定义时用了 -> 而非 ->>,或类型声明过大(如 VARCHAR(255) 存 "active"),索引就形同虚设。


















