TP6一万并发稳住QPS 8000+的关键是“不查库”与“查得聪明”协同:缓存穿透业务层、连接不抢、查询不拖、写不堵。

TP6 一万并发不是靠“调优”撑住的,是靠“不查库”和“查得聪明”协同出来的——QPS 8000+ 的底线,建立在缓存穿透业务层、连接不抢、查询不拖、写不堵这四个刚性条件上。
缓存必须穿透到业务逻辑层,不能只配个 Redis 驱动就完事
很多人把 Cache::get() 往控制器里一塞,以为高并发就解决了。结果发现 Redis 压力不大,PHP 进程却卡死——问题出在没穿透到真正高频读的路径里。
- 本地缓存兜底:对开关、字典、配置类数据,用
apcu或file驱动,ttl设为 0(永不过期)或秒级,避免每次请求都打 Redis - Redis 主缓存必须带参数维度:比如订单状态接口
/api/order?user_id=123&type=paid,key 要设成order:status:v2:user_id:123:type:paid,别用泛化的order:123 - 主动失效比被动过期更关键:用户支付成功后,立刻执行
Cache::delete('order:status:v2:user_id:123:type:paid'),而不是等 5 分钟 TTL 到期 - 空值缓存必须设短
ttl(如 60 秒):否则Cache::get('user:12345')返回null后被长期缓存,直接触发缓存穿透
Db::connect() 没连接池,但能靠 persistent + 主动探测减少争抢
TP6 的 Db 类不提供连接池,SQLSTATE[HY000] [1040] Too many connections 是高频报错。这不是 MySQL 配置太小,而是 PHP 层反复创建/销毁连接导致的雪崩式争抢。
- 强制启用长连接:
'params' => [\PDO::ATTR_PERSISTENT => true],同时确认 MySQL 的wait_timeout≥ 300 - 禁用读写分离:
'deploy' => 0,除非你真有中间件控制路由;全量走deploy=1会额外多建一套连接 - 查询前加探测:
Db::connect('mysql')->query('SELECT 1'),放在中间件里统一做,避免拿到已断连接再执行业务 SQL - 事务必须显式结束:
Db::transaction()内不能包含 HTTP 请求、日志写入等耗时操作,否则连接被占超时,后续请求全卡住
游标分页 + 预加载,砍掉 N+1 和 LIMIT OFFSET 性能黑洞
美食商城里“附近商家”“今日热销”这类接口,一上来就是 ORDER BY created_at DESC LIMIT 0,20,并发一上去,OFFSET 越大越慢,MySQL 直接扫表。这不是索引能救的。
立即学习“PHP免费学习笔记(深入)”;
- 改用游标分页:用上一页最后一条记录的
id或created_at作为下一页起点,例如WHERE id < 12345 ORDER BY id DESC LIMIT 20 - 关联查询必须预加载:查商品列表时,别在循环里调
$item->category,改用FoodGoods::with('category')->select() - 禁止在 where 条件里用函数:
WHERE DATE(created_at) = '2026-10-21'会让索引失效,改写成WHERE created_at >= '2026-10-21 00:00:00' AND created_at < '2026-10-22 00:00:00' - 高频统计聚合走 Redis 计数器:比如“某商品已售数量”,别每次
SELECT COUNT(*) FROM order_item WHERE goods_id = ?,改用$redis->incr('goods:sold:123')
异步分流不是选配,是扛住峰值的保险丝
下单成功后发短信、写日志、更新搜索索引……这些动作如果同步执行,一个请求就要等 300ms,一万并发就是 3 秒排队延迟。TP6 不支持原生协程,只能靠外部解耦。
- 消息队列必须介入核心路径:用
Redis List或RabbitMQ接收订单创建事件,Worker 进程异步处理后续动作 - Redis 记账替代数据库记流水:比如“用户今日下单次数”,先
$redis->incr('user:order_count:123:20261021'),再异步落库,避免写锁 - 库存扣减必须走原子操作:
$redis->decr('goods:stock:123')返回负数则回滚,别先查再减,那是并发漏洞 - 所有异步任务必须有失败重试机制:Redis 消息消费失败后推入
retry:order_notify队列,用定时任务每分钟捞一次
最易被忽略的是缓存版本号和本地缓存广播——改了商品模型字段却不更新缓存 key 的 v2,所有实例本地缓存还在用旧结构解析 JSON;多台服务器之间本地缓存失效不同步,导致同一用户在不同节点看到不同状态。这些细节不处理,前面所有优化都会在凌晨三点集体失效。



















