是依赖解析阶段SAT求解器穷举版本组合所致,主因包括循环依赖(如myorg/core←myorg/api←myorg/core)、宽泛约束(如"^1.0")、xdebug启用拖慢5–10倍、require-dev干扰及composer.lock缺失导致重算。

Composer安装时大量CPU占用,几乎总是依赖解析阶段(Resolving dependencies)在本地穷举版本组合,不是网络卡、也不是磁盘慢——它在用 PHP 硬算 SAT(布尔可满足性)问题,一旦约束松散或存在循环引用,就会陷入指数级回溯。
为什么composer install会触发高 CPU 计算
很多人误以为composer install只是“解压+软链”,其实只要composer.lock缺失、损坏,或你运行的是composer update,Composer 就必须重新跑一遍依赖求解器(Solver)。这个求解器本质是回溯式搜索:对每个包的每个可用版本,尝试代入约束条件(PHP 版本、其他包版本范围、稳定性标记等),验证是否全局兼容。约束越宽(如"^1.0"、"*")、依赖树越深、require-dev 中包越多,搜索空间就越大。
- 一个含 80 个 require 包的 Laravel 项目,
update可能需评估数万种组合 -
xdebug开启时,函数调用钩子会让 Solver 慢 5–10 倍,CPU 占用持续 95%+ 是典型信号 - PHP CLI 默认不启用
opcache.enable_cli=1,但若意外开了,反而拖慢 Composer 自身类加载(尤其在旧版中)
composer show -t里反复出现同一包就是循环依赖实锤
真正导致 CPU 拉满到死锁的,不是“慢”,而是强循环依赖:比如myorg/core require myorg/api:^2.0,而myorg/api又 require myorg/core:^1.2,且两个版本范围有交集。Solver 会不断尝试core v1.5 → api v2.1 → core v1.7 → api v2.3...,永远找不到终止点。
- 执行
composer show -t --no-dev --ignore-platform-reqs | grep -E "myorg/core|myorg/api",如果看到路径反复折叠(如myorg/core ← myorg/api ← myorg/core),就是闭环 -
composer depends --tree myorg/core能直接输出完整链路,但前提是该包已存在于composer.lock中;否则先删vendor/和composer.lock,再跑composer update --dry-run -v看卡在哪一行 - 别信
--ignore-platform-reqs能绕过问题——它只会让 Solver 尝试更多非法组合,死得更慢
临时禁用xdebug比换镜像更能立竿见影
换国内镜像(如https://mirrors.aliyun.com/composer/)只加速下载阶段,对“Resolving dependencies”毫无帮助。而xdebug是本地 CPU 密集型任务的最大拖累,禁用后常能从 10 分钟降到 30 秒内。
- 安全禁用方式:
php -d zend_extension= -d xdebug.mode=off composer install(不是php -n,避免丢失其他必需扩展) - CI 环境中,设
XDEBUG_MODE=off环境变量即可(PHP 8.1+) - 确认是否生效:
php -m | grep xdebug应无输出;或php -r "echo extension_loaded('xdebug') ? 'on' : 'off';" - 并发下载(
parallel-downloads)对解析阶段零影响,调高它只会让“Installing dependencies”阶段更抢资源
composer update不该出现在生产部署流程里
update是唯一会触发完整 SAT 求解的操作,内存和 CPU 双重高压。而install只要composer.lock完好,就只是按图索骥——开销低两个数量级。
- CI/CD 中永远用
composer install --no-interaction --no-scripts --no-dev,靠 lock 文件保证一致性 - 真要更新某个包,用
composer update vendor/package(不带版本号),它会复用 lock 中其他包的已知兼容版本,不重算全局 - 删
composer.lock再install≈主动触发全量update,极易引入不兼容变更(如guzzlehttp/guzzle从 v7 升 v8) - 团队协作时,
composer.lock必须提交 Git,否则每人install结果都可能不同
最易被忽略的一点:Solver 的行为完全由composer.json中的约束表达式驱动,而不是包本身。一个写成"dev-main"的 require,可能比十个稳定版更消耗资源——因为它强制 Solver 去 fetch 元数据、校验分支状态、并参与所有回溯路径。稳定性和性能,往往就藏在那一行"^2.3"里。


















