activerecord 适合标准 crud 和简单关联,复杂查询需用 query builder 或原生 sql;定义模型需继承 yii\db\activerecord 并实现 tablename()、rules()、primarykey() 等方法,主键非 id 时须显式声明,时间戳需配置 timestampbehavior;find() 返回 activequery,one()/all() 才执行查询,建议只读场景用 asarray();避免 n+1 需 with() 预加载并正确声明关联;大数据量下用 each()/batch() 分批处理,统计类操作绕过 ar 实例化,更新/删除用 updateall()/deleteall(),启用 schema 缓存和手动查询缓存提升性能。

ActiveRecord 不是万能查询工具,它适合标准 CRUD 和简单关联,复杂 JOIN、子查询、窗口函数等必须退回到 Query Builder 或原生 SQL。
怎么定义一个 AR 模型类
AR 的起点是继承 yii\db\ActiveRecord 的模型类,类名通常与数据表名对应(如表 user → 类 User),且需实现 tableName() 方法声明表名。
- 不写
tableName()会导致“Table not found”错误,即使类名匹配也不自动推导 - 字段验证逻辑统一放在
rules()中,save()会自动触发,失败时getErrors()返回具体字段错误 - 主键字段名默认为
id;若用user_id等非标主键,需在primaryKey()方法中显式返回['user_id'] - 时间戳自动填充需手动在
behaviors()中配置TimestampBehavior,AR 不默认处理created_at/updated_at
怎么查数据:find()、one()、all() 的实际区别
find() 返回的是 ActiveQuery 对象,不是数据本身;真正执行查询的是 one()(单行)或 all()(数组),中间可链式调用条件方法。
-
User::find()->where(['status' => 1])->one()返回一个User对象;->all()返回User[]数组 - 直接调用
User::findOne(123)是快捷方式,等价于find()->where(['id' => 123])->one(),但无法加额外条件 - 未加
asArray()时,每条记录都是完整对象实例,内存开销大;导出列表页或只读场景建议加->asArray()->all() -
count()、sum()、exists()这类聚合方法也基于ActiveQuery,不拉取全量数据,性能友好
怎么避免 N+1 查询问题
访问关联数据(比如 $post->author->name)时,若没预加载,每个 author 都会单独发一次 SQL,100 篇文章就查 101 次——这就是典型的 N+1。
- 用
with()预加载:Post::find()->with('author')->all(),底层生成 LEFT JOIN 或两条独立 SELECT(取决于关联类型) - 深度关联要写全路径:
with(['author', 'comments.user']),漏写comments.user仍会触发嵌套 N+1 -
with()不会自动过滤关联数据,如需带条件的关联,得用闭包形式:with(['comments' => function ($q) { $q->andWhere(['status' => 1]); }]) - 关联定义必须在模型里用
hasOne()或hasMany()显式声明,否则with()无效且无报错
批量操作和大数据量下的关键控制点
AR 默认把整张表当对象加载,面对万级数据不做节制很容易 OOM 或超时。
- 分批处理用
each()或batch():Post::find()->each(100, function ($post) { ... }),每次只实例化 100 个对象 -
batch()返回迭代器,适合配合foreach;each()自动执行回调,更简洁 - 只读统计类场景,绕过 AR 实例化:用
Post::find()->select('COUNT(*)')->scalar(),比count()更可控 - 更新大量数据别用
save()循环:改用updateAll()或deleteAll(),它们生成单条 SQL,不走模型验证和事件
最常被忽略的是缓存和 schema 缓存。表结构不变时,schemaCachingDuration 设为 3600 能省下大量元数据解析开销;而查询结果缓存必须手动套 cache(),AR 不自动缓存任何查询。











