先删缓存再改库,避免读到旧值;修改前执行delete('key')再save();关联更新须统一事务、校验外键、复用查询条件,确保四者咬合。

先删缓存再改库,别反过来
查完数据立刻改,又没同步清缓存,就容易让其他请求读到旧值——这不是并发问题,是操作顺序错了。Yii里最稳妥的做法是:修改前先执行 Yii::$app->cache->delete('key'),再调用 save() 或 updateAll()。
常见错误现象:用户资料页显示旧邮箱,刷新后才变新;后台改完状态,列表页仍显示“待审核”。原因往往是 set() 覆盖缓存早于事务提交,数据库还没落盘,缓存已更新。
- 强一致性场景必须删缓存,不能依赖过期时间
- 键名要和查询时用的一致,比如
user_profile_123,别漏掉ID拼接逻辑 - 若用事务包裹修改操作,删缓存这步也得放在事务内,否则可能删完崩在save()前,导致缓存缺失但数据未变
查出来就改?小心ActiveRecord对象状态残留
用 User::findOne($id) 查出模型后直接改属性再 save(),看似简单,但若中间有其他代码碰过这个实例(比如日志、钩子、事件监听),dirtyAttributes 可能被污染,导致不该更新的字段也被写进数据库。
使用场景:后台编辑页提交、API接口做部分字段更新。
- 明确指定要更新的字段:
$user->setAttributes(['status' => 2], false),第二个参数false表示不校验场景,避免意外触发规则 - 更安全的做法是用
updateAll()直接发SQL:User::updateAll(['status' => 2], ['id' => $id]),绕过模型层状态管理 - 如果必须走模型,改完后手动重置脏状态:
$user->clearErrors(); $user->refresh();,防止下次 save() 带上历史变更
批量更新时,where条件必须严格对齐查询条件
先用 User::find()->where(['status' => 1])->asArray()->all() 查一批ID,再用这些ID去 updateAll(),结果发现有些没更新上——大概率是查询时用了软删除标记(如 is_deleted = 0),但 updateAll 没带上,导致把已逻辑删除的记录也改了,或漏掉本该改的。
参数差异:查询用的 where 是业务语义,更新用的 where 是数据事实,两者不等价。
- 批量更新的 where 条件应完全复刻查询条件,包括软删除、租户ID、状态组合等
- 避免用查出来的ID数组拼
['in', 'id', $ids],而应直接复用原查询对象:User::updateAll(['status' => 2], (new \yii\db\Query())->where(['status' => 1])) - 执行前加日志:
Yii::info('Updating '.count($ids).' users');,方便事后核对数量是否匹配
关联数据更新,别只改主表忽略外键约束
订单状态从“待支付”改成“已取消”,顺手把关联的支付记录也设为失效,但没检查支付记录是否已被其他流程锁定或软删除——结果订单改成功了,支付记录更新失败,事务回滚不完整,留下不一致状态。
性能影响:跨表更新不加事务或不设超时,可能引发行锁等待,拖慢整个接口。
- 所有关联更新必须包在同一个事务里:
Yii::$app->db->transaction(function ($db) { ... }) - 外键字段更新前,先查是否存在(尤其软删除表):
Payment::findOne(['order_id' => $orderId, 'is_deleted' => 0]) - 若用
updateAll()更新关联表,务必确认目标表有对应索引,否则WHERE order_id = ?会全表扫描,锁表风险高
真正难的不是写对一行 update,而是保证查询条件、缓存键、事务边界、外键约束这四者始终咬合。漏掉任意一环,脏数据就会在某个低峰时段悄悄冒头,还不好复现。


















