mysql 5.7+且字段类型为json才支持原生json查询;低版本或非json类型字段需用whereraw配合json_unquote(json_extract())或like,高频查询必须建虚拟列+索引。

MySQL 版本和字段类型必须先确认
ThinkPHP 对 JSON 字段的查询能力完全依赖底层 MySQL 是否支持原生 JSON 函数。执行 SELECT VERSION(),结果低于 5.7 就别用 -> 或 JSON_CONTAINS —— 这些函数根本不存在,TP 写得再对也会报错 FUNCTION JSON_CONTAINS does not exist。
字段本身也得是 JSON 类型,不是 TEXT 或 VARCHAR。用 SHOW COLUMNS FROM table_name LIKE 'data' 查看 Type 列,必须显示 json。哪怕你存的是合法 JSON 字符串,类型不对,data->'$.status' 这种语法照样无效,MySQL 会静默转成空字符串。
where('field->key', value) 能用但有坑
TP6 支持 where('info->nickname', 'ThinkPHP') 这种写法,但仅限 MySQL 5.7+ 且字段为 JSON 类型。它底层生成的是 JSON_EXTRACT(info, '$.nickname'),注意两点:
- 字符串值比较时,MySQL 返回带双引号的 JSON 字符串(如
"ThinkPHP"),所以实际匹配的是完整带引号的值;想去掉引号要用->>语法,TP 不直接支持,得换whereRaw - 数字字段如
info->score,TP 不自动绑定类型,必须手动bind('score', 95, \PDO::PARAM_INT),否则会被当字符串处理,> 90可能失效 - 路径部分(如
$.nickname)不能拼接用户输入,必须白名单校验,否则可能注入类似$."a"; DROP TABLE x; --的恶意路径
模糊搜索必须用 whereRaw + JSON_UNQUOTE
查 JSON 字段里某个键是否包含关键词(比如 info->bio 里含 “PHP”),不能用 LIKE 直接套整个字段,那会匹配到其他键的内容。正确做法是先提取、再去引号、再模糊匹配:
whereRaw('JSON_UNQUOTE(JSON_EXTRACT(info, "$.bio")) LIKE ?', ["%PHP%"])
或者更安全地用 JSON_CONTAINS(只适用于数组):
whereRaw('JSON_CONTAINS(tags, ?)', ['"PHP"']) —— 注意第二个参数必须是合法 JSON 字符串,所以 "PHP" 的双引号是 JSON 语法要求,不是 PHP 字符串引号;中文或特殊字符要先 json_encode($keyword, JSON_UNESCAPED_UNICODE) 生成,别手拼。
高频查询务必建虚拟列加索引,否则 JSON_EXTRACT 每次都全表扫描,TP 写得再简洁也没用。
解析前必须校验 JSON 合法性
TP 查询返回的 JSON 字段值仍是字符串,不是自动 json_decode 后的数组。直接 json_decode($user['info'], true) 很危险——万一数据库里存了空字符串、null 或格式错误的 JSON,json_last_error() 会返回非零值,后续代码可能崩。
建议封装一层校验逻辑:
function safeJsonDecode(string $json): ?array
{
if (empty($json) || !is_string($json)) {
return null;
}
$data = json_decode($json, true);
return json_last_error() === JSON_ERROR_NONE ? $data : null;
}
尤其要注意:MySQL 的 JSON 字段允许存 null,但 json_decode(null) 返回 null,和解码失败表现一样,得靠 json_last_error() 区分。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











