MySQL 5.7+ 中 whereJsonContains 用于查询 JSON 字段是否包含指定值,要求字段类型为 JSON、路径语法正确(如 'options->tags')、数据为合法 JSON 格式,且不走索引需配合生成列优化性能。

MySQL 5.7+ 直接查 JSON 字段的 whereJsonContains 怎么用
只要数据库是 MySQL 5.7 或更高版本,Eloquent 原生支持 whereJsonContains、whereJsonLength、whereJsonDoesntContain 这类方法,不用手写 raw 查询。它底层调用的是 MySQL 的 JSON_CONTAINS() 函数,要求字段类型是 json(不是 text 或 longtext)。
常见错误是字段用了 text 类型却硬套 JSON 查询——会静默失败或返回空结果,因为 MySQL 不识别其为合法 JSON。
-
whereJsonContains('options->tags', 'vue'):查options字段里tags数组是否包含字符串"vue"(注意引号,值必须是完整 JSON 值) -
whereJsonContains('meta->permissions', ['read', 'write']):查permissions数组是否同时包含这两个字符串(顺序无关) - 如果字段名带点(如
user.profile),不能直接写user.profile->name,得用user->profile->name或改用whereRaw
Laravel 9+ 的 whereJsonContains 不生效?检查这三点
不是语法写错,而是环境或数据结构不匹配。最常踩的坑是 JSON 数据本身格式不对。
- 数据库存的是字符串
"[\"vue\",\"laravel\"]"(转义双引号),而不是真正的 JSON 数组["vue","laravel"]——whereJsonContains会返回空,因为 MySQL 拒绝解析非法 JSON - Eloquent 模型没加
$casts = ['options' => 'array'],导致存入时没走 JSON 编码,实际存的是序列化字符串(a:2:{i:0;s:4:"vue";i:1;s:8:"laravel";}),这种根本不是 JSON - 查询路径用了单引号:
'options->'tags'',PHP 解析后变成options->'tags',MySQL 报错Invalid JSON path expression;必须统一用双引号或不加引号(如options->tags)
PostgreSQL 怎么查 JSONB 字段?别硬套 MySQL 写法
PostgreSQL 用的是 jsonb 类型和 @> 操作符,Eloquent 的 whereJsonContains 在 PG 下会自动编译成 jsonb @> ?,但前提是字段定义为 jsonb,且数据是标准 JSONB 格式。
立即学习“PHP免费学习笔记(深入)”;
如果你手动写了 whereRaw("meta @> ?", ['{"role": "admin"}']),要注意:传入的必须是 JSON 字符串字面量,不是 PHP 数组。Laravel 自动处理了这个转换,所以优先用 Eloquent 方法而非 raw。
- 查嵌套对象:
whereJsonContains('meta->settings->theme', 'dark')在 PG 和 MySQL 都可用,但 PG 要求settings和theme都存在,否则报错;MySQL 更宽松,路径不存在时返回 false - 想查 key 是否存在(不管值),用
whereRaw("meta ? 'role'")(PG)或whereRaw("JSON_EXTRACT(meta, '$.role') IS NOT NULL")(MySQL) - PG 对大小写敏感,
'Admin'≠'admin';MySQL 默认也敏感,除非字段用 utf8mb4_bin 排序规则
复杂 JSON 查询性能差?加生成列 + 索引才是正解
单纯在 JSON 字段上建索引(比如 INDEX(options))对 MySQL 5.7+ 没用,JSON 类型不支持普通 B-Tree 索引。必须用生成列(generated column)把要查的路径“抽出来”,再给它加索引。
例如经常按 options->$.status 查询,就在迁移里加:
DB::statement("ALTER TABLE users ADD COLUMN options_status VARCHAR(20) AS (JSON_UNQUOTE(JSON_EXTRACT(options, '$.status'))) STORED");
DB::statement("CREATE INDEX idx_users_options_status ON users(options_status)");
之后就可以用 where('options_status', 'active'),速度接近普通字段查询。别指望 whereJsonContains 走索引——它永远全表扫描,只是语法方便而已。
PostgreSQL 可以直接在 jsonb 上建 GIN 索引:CREATE INDEX idx_meta_gin ON users USING GIN (meta),但只加速 @>、? 这类操作,对 ->> 提取后比较仍需配合表达式索引。



















