mysql json字段需显式声明类型并手动处理序列化,否则存为字符串导致json函数失效;查询须用expression绕过绑定,更新应先读再改后写,yii 3的jsoncolumn不自动编码写入。

MySQL 5.7+ 的 JSON 字段在 Yii Model 中怎么存取
直接用 json 类型字段,Yii 2.0.14+ 和 Yii 3 都支持自动序列化,但前提是 Model 层得「显式声明」字段类型,否则它会当普通字符串处理——存进去是字符串,取出来还是字符串,JSON 函数(如 JSON_EXTRACT)查不到结构化数据。
常见错误现象:SELECT * FROM table WHERE JSON_CONTAINS(data, '"admin"', '$.roles') 查不到结果,但字段里明明有 {"roles": ["admin"]} ——根本原因是 Yii 没告诉数据库这是 JSON,MySQL 把它当 TEXT 存了。
- 在 Migration 中建表时,必须用
$this->json()(Yii 2)或$table->json('data')(Yii 3),不能写$this->text('data') - Model 的
rules()里加['data', 'safe']或明确指定['data', 'filter', 'filter' => 'json_encode'](仅限简单场景) - 更稳妥的做法:重写 Model 的
attributes()或用afterFind()/beforeSave()手动处理序列化,尤其当字段含嵌套对象、空数组或null值时
Yii 2 中用 JsonExpression 写原生 JSON 查询
想在 where 条件里用 MySQL JSON 函数,比如查某个键是否存在、匹配数组元素,不能靠 ActiveRecord 的 andFilterWhere 直接拼字符串——会被转义或引号包死,导致 SQL 报错 Invalid JSON text 或返回空。
正确做法是用 yii\db\Expression 包一层,让 Yii 跳过参数绑定,把表达式原样塞进 SQL:
$query = MyModel::find()
->where(new \yii\db\Expression("JSON_CONTAINS(`data`, :role, '$.roles')", [':role' => json_encode('admin')]))
->all();
-
JSON_CONTAINS第二个参数必须是 JSON 格式字符串("\"admin\""),不是 PHP 字符串,所以要用json_encode() - 别用
andFilterWhere(['like', 'data', 'admin'])——这是全文模糊匹配,不走 JSON 索引,性能差且不准 - 如果字段上建了虚拟列 + 索引(如
role_first VARCHAR(32) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(data, '$.roles[0]')))),查询会快很多,但 Model 里要额外定义该属性并加safe规则
JSON 字段更新部分键值时,别直接赋值整个数组
想只改 data 字段里的 status 键,却在 Model 里写 $model->data = ['status' => 'done'],结果把其他键(如 created_at、meta)全清掉了——因为 Yii 默认把整个属性当新值覆盖写入。
安全做法分两步:先读出原始 JSON,PHP 里修改,再写回:
$data = Json::decode($model->data); $data['status'] = 'done'; $model->data = Json::encode($data);
- 必须用
yii\helpers\Json::decode/encode,不用json_decode原生函数——前者会处理 Yii 特有的 null/NaN 兼容问题 - 如果并发高,考虑用 MySQL 的
JSON_SET或JSON_REPLACE直接执行 SQL 更新,避免读-改-写竞争 - 注意
JSON_SET会新增键,JSON_REPLACE只改已有键,选错会导致静默失败
Yii 3 中 JsonColumn 的行为差异和陷阱
Yii 3 引入了 JsonColumn 类型,看起来能自动处理序列化,但实际只在「从数据库读取后」做 decode,写入时仍按 PHP 数组原样 json_encode——这意味着如果你在 Model 层对 data 做了引用赋值(如 $model->data['user']['id'] = 123),下次 save() 会把整个数组重新 encode,可能丢失浮点精度或打乱 key 顺序。
- 不要对
JsonColumn字段做深层引用修改;应先$data = $model->data拷贝一份,改完再赋值回去 - 若字段含时间戳、小数、布尔值,检查
Json::encode()的$options参数(如JSON_PRESERVE_ZERO_FRACTION),否则1.0可能变成1 - Migration 中定义字段时,
$table->json('data')和$table->text('data')->comment('json')在底层完全不同:前者支持 JSON 函数、校验、索引;后者只是个备注,毫无意义
最常被忽略的是 MySQL 的 JSON 校验粒度——它只在 INSERT/UPDATE 时检查语法,不验证 schema。哪怕你存了个 {"name": 123}(name 应该是字符串),也不会报错,但后续所有依赖 name 是字符串的查询都会出问题。得靠 Model 层的 rules() 或数据库 CHECK 约束兜底。











