yii_debug未关闭是页面加载慢的首要原因,它会触发sql记录、堆栈快照等调试钩子,单请求多耗20–30ms;需确保index.php中明确定义为false且后续配置不覆盖,并验证filetarget是否仍在写日志。

YII_DEBUG 没真关,日志写满磁盘也卡
页面加载慢的第一嫌疑不是数据库,是 YII_DEBUG 还开着。它不只是开关变量,而是整套调试钩子:SQL 记录、堆栈快照、变量 dump 全在每个请求里跑一遍。实测单请求多耗 20–30ms,100 QPS 就是 2–3 秒纯等待。
关键不是“设了没”,而是“有没有被覆盖”。检查 index.php 开头是否为:defined('YII_DEBUG') or define('YII_DEBUG', false);
且后续 config 文件里不能再出现 YII_DEBUG => true —— 特别是本地开发配置被误带进生产环境时,极易踩坑。
- 用
Yii::getLogger()->getTargets()看是否还有FileTarget在写日志;高频日志 I/O 是隐形瓶颈 - 确认 PHP 的
opcache.enable=1且opcache.revalidate_freq=0,否则 opcache 会反复校验文件,反而拖慢 autoload
schemaCache 配了等于没配,元数据查到库崩
ActiveRecord 每次 new 模型,都会去 information_schema 查表结构。一个列表页加载 10 个模型,就是 10 条无意义 SELECT。不启用 schema 缓存,等于每天白跑几百次系统表查询。
常见错误是只设 schemaCacheDuration,却漏掉 enableSchemaCache => true 和 schemaCache 指向一个真实可用的缓存组件。
- db 组件里必须明确写:
'enableSchemaCache' => true -
'schemaCache' => 'cache'—— 这个cache得是已启用的组件(比如 Redis),否则自动退化为无缓存 - 手动执行一次
Yii::$app->db->getSchema()->getTableSchema('user'),再查 Redis 或 APCu 里有没有以schemacache:开头的 key,验证是否生效
FileCache 在生产环境就是定时炸弹
默认的 FileCache 在高并发下会触发 flock() 文件锁,请求排队等锁,响应时间呈指数增长。这不是性能差,是设计定位错——它只适合开发或极低流量场景。
换成 yii\redis\Cache 不是改个 class 名就行,还得配对:
- 确保 Redis 服务可达,
hostname、port、database都正确 - 加
'keyPrefix' => 'prod_'隔离环境,避免开发/测试缓存污染 - 如果还在用
DbCache,立刻在cache表上建索引:CREATE INDEX idx_cache_key ON cache (cache_key);,否则查缓存比查业务表还慢
N+1 查询藏得深,循环里写 $order->user->name 就是罪魁
列表页里每条记录都触发关联访问,就是典型的 N+1:1 次主查 + N 次关联查。哪怕只有 20 条数据,也多发 20 次 SQL。这不是 Yii 的问题,是加载时机没控制好。
必须在控制器里显式预加载,而不是靠 getter 触发懒加载:
- 把
Order::find()->all()改成Order::find()->with(['user', 'items.product'])->all() - 深层关联如
order→user→profile→avatar,优先用asArray()返回数组,避免对象构造开销 - 别信
joinWith()能一劳永逸——分页时可能重复主表记录,反而更难调
缓存没预热,首屏永远慢半拍
缓存按需生成,首次访问就得跑全链路:DB 查询 → 模板渲染 → 缓存写入。用户看到的就是几百毫秒甚至秒级延迟。这不是缓存没用,是它根本没提前就位。
缓存预热不是可选项,是上线必做动作:
- 写个控制台命令,比如
console/controllers/CacheController.php,用Yii::$app->cache->set()把热门商品、分类树、首页 Banner 等一次性塞进去 - 配合 cron 每小时跑一次:
0 */1 * * * /usr/bin/php /path/to/yii cache/warmup - 如果部署单机,直接在
config/web.php的bootstrap里加个cachePreloader组件,启动时自动拉热点数据
缓存键命名要带业务上下文,比如 "category:tree:v2",避免不同版本逻辑混用;失效策略也得和业务节奏对齐,别设 duration = 0 后忘了手动 invalidate() —— 页面就永远卡在旧数据上。











