高频读写表必须用连接池,否则onWorkerStart中new PDO会为每个Worker创建独立连接,16个Worker即16个连接,叠加后台任务易超MySQL默认max_connections=151,触发Connection refused;正确做法是改用workerman/mysql等异步协程客户端并配置合理池大小(worker数×2~4),禁用长连接保活,弃用think-orm/eloquent等同步ORM,结合分表、分级缓存与版本化key防控击穿雪崩。

高频读写表必须用连接池,否则onWorkerStart里直接 new PDO 就会炸
Webman 的 worker 进程常驻内存,每个进程启动时如果在 onWorkerStart 里直接创建 MySQL 连接(比如 new PDO()),16 个 worker 就会建立 16 个独立连接。MySQL 默认 max_connections=151,稍加后台任务或 Redis 连接,立刻触发 Too many open files 或 Connection refused。
正确做法是用连接池替代原生 PDO:
- 改用
workerman/mysql客户端(非阻塞 + 连接复用) - 或接入
hyperf/database协程驱动(需搭配协程环境) - 配置连接池大小:一般设为 worker 数 × 2~4,避免空闲连接堆积
- 禁用长连接保活(
mysql.ping_interval设为 0),靠池子自动回收
同步 ORM 会卡死整个 worker,think-orm 和 eloquent 都不行
即使开了 16 个 worker,只要有一个请求执行了没加索引的 SELECT * 并耗时 2 秒,这个 worker 就彻底阻塞,后续所有请求排队等它——这不是并发问题,是 I/O 模型问题。
要真正释放并发能力,必须切换异步调用链:
- 放弃
think-orm、eloquent等同步封装,它们底层仍是pdo_mysql - 用
workerman/mysql的query()+ 回调,或封装成 Promise 风格 - 写操作优先走批量
INSERT ... ON DUPLICATE KEY UPDATE,减少 round-trip - 读操作加
WHERE条件 + 覆盖索引,避免Using temporary; Using filesort
按月分表不是可选项,而是高频日志类表的刚需
单表超 500 万行后,即使有索引,INSERT 和 SELECT COUNT(*) 也会明显变慢,且 ALTER TABLE 维护成本飙升。
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
Webman + ThinkORM 实现按月分表的关键点:
- 主表名固定(如
resty_log),仅作结构模板 - 运行时动态生成表名:
resty_log_202607,用date('Ym')计算 - 建表语句从
INFORMATION_SCHEMA.TABLES查询是否存在,不存在再CREATE TABLE LIKE resty_log - 查询时用 UNION ALL 合并多月数据(仅限必要场景),日常只查当月表
Redis 缓存不能只靠 setex,得做分级+过期策略对齐
高频读写场景下,缓存击穿和雪崩比性能瓶颈更致命。Webman 默认的 \support\Container::get('redis') 是单例,但没解决多实例、多 TTL、多业务隔离问题。
实操建议:
- 按业务拆 Redis 实例:用
config/redis.php配'log_cache'、'user_cache'等独立配置项 - 缓存键强制带版本号:
user:1001:v2,升级时只需改 v2 → v3,旧数据自然过期 - 写 DB 后主动删缓存,而非依赖过期——避免
setex时间一到大量请求穿透 - 热点 key 加随机过期偏移(如
3600 + rand(1,300)),防集中失效
真实压测中,最容易被忽略的是「连接池未生效」和「缓存键无版本控制」——前者让 QPS 卡在几百就上不去,后者导致凌晨缓存集体失效,DB 直接被打满。别信“配置完就万事大吉”,得用 redis-cli info clients 和 SHOW PROCESSLIST 实时看连接数与活跃线程。










