yii模型查询慢的根本原因是数据库缺乏索引、未使用预加载和未实现覆盖查询;应通过慢查询日志定位sql,用explain分析执行计划,按最左前缀原则创建组合索引,强制with()预加载关联数据,并清理长期未使用的索引。

Yii模型查询慢到用户刷新三次才加载出来,不是代码写得差,而是数据库在裸奔——没索引、没预加载、没覆盖查询,三者齐发,再快的服务器也扛不住。
查慢查询日志定位瓶颈SQL
先别急着建索引,打开MySQL慢查询日志,把执行时间超过1秒的SQL全捞出来:SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1;。
重点盯住ActiveRecord生成的SELECT语句,尤其是带JOIN、WHERE和ORDER BY的复合查询;【不看执行计划就加索引,等于蒙眼贴膏药】。
用EXPLAIN跑一遍最慢那条SQL,观察type是否为ALL或index,key是否为NULL——这两项同时出现,说明当前完全没走索引。
给WHERE字段加单列索引
方法一:直接在MySQL命令行执行
CREATE INDEX idx_user_status ON user (status);
方法二:用Yii迁移命令生成(推荐,便于团队同步)
yii migrate/create add_index_to_user_status → 在up()里写$this->createIndex('idx_user_status', 'user', 'status'); → 执行yii migrate。
注意:status这种低选择性字段(比如只有0/1/2三个值),单列索引效果有限,必须配合其他条件才生效;若WHERE里还有created_at,优先考虑组合索引。
用组合索引解决多条件查询
第一步:按最左前缀原则排列字段顺序。例如查询语句是WHERE status = ? AND created_at > ? ORDER BY created_at DESC,则索引字段顺序必须是(status, created_at),不能反过来。
第二步:执行建索引语句
CREATE INDEX idx_user_status_created ON user (status, created_at);
第三步:验证是否命中——再次EXPLAIN原SQL,确认key显示为idx_user_status_created,且rows显著下降。
第四步:如果查询还包含SELECT id, username, email,且这三个字段恰好都在索引里,可升级为覆盖索引,避免回表:在原索引末尾追加INCLUDE (id, username, email)(MySQL 8.0+支持)或直接建(status, created_at, id, username, email)。
关联查询必须用with()预加载
控制器里写User::find()->where(['status' => 1])->all(),再循环中调$user->profile->avatar,就是N+1典型现场——1次主查+100次profile查+100次avatar查。
正确写法:改用User::find()->where(['status' => 1])->with(['profile', 'profile.avatar'])->all(),一条SQL拉完全部关联数据。
深层嵌套如order→items→product→category,必须写成Order::find()->with(['items.product.category'])->all();漏掉任意一级,下一级就会触发懒加载,性能断崖下跌。
删除无用索引释放I/O压力
运行SELECT * FROM sys.schema_unused_indexes;(需启用performance_schema),找出三个月内零命中的索引。
逐个执行DROP INDEX idx_old_test ON user;,每删一个,观察慢查询数量是否下降。
特别注意:唯一索引和主键索引不在清理范围内,【误删主键索引会导致ActiveRecord findAll()直接报错退出】。











