大流量下应避免直接 COUNT(*):需采用预计算+缓存兜底策略,定时任务统计写入Redis,业务读缓存并设过期;同步更新改用队列异步处理;Redis计数需防漏计、设过期、定期校验;分表后聚合查询须手动UNION或原生SQL。

大流量下直接 COUNT(*) 会拖垮数据库
ThinkPHP 默认用 count() 查询总条数,高并发时每个请求都扫表或走索引扫描,MySQL 的行锁、Buffer Pool 压力、CPU 升温会立刻显现。尤其当统计条件带 WHERE status=1 AND created_at > '2024-01-01' 这类非主键范围时,执行计划容易退化成全索引扫描。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 把实时总数拆成「预计算 + 缓存兜底」:用定时任务(如 Laravel Scheduler 或 Linux cron)每 5 分钟跑一次
SELECT COUNT(*) FROM order WHERE ...,结果写入 Redis 的stat:order:today键 - 业务代码里改用
$count = $redis->get('stat:order:today') ?: 0;,不查库;缓存失效时(比如管理员手动触发重算),再 fallback 到 DB 查询并更新缓存 - 避免在控制器里写
OrderModel::where(...)->count()—— ThinkPHP 的查询构造器不会自动帮你加缓存层
用事务 + 队列做数据同步,别信“一次 insert 就完事”
用户下单后立刻要更新销量、分类汇总、排行榜,如果全在 HTTP 请求内同步执行 update goods set sales = sales + 1 和 insert into stat_daily (date, cat_id, sales) values (...),失败一个就全滚,且 DB 写压力集中。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 核心业务(如下单)只写主表,成功后发消息到队列(如 Redis List 或 Kafka),由独立消费者进程处理统计更新
- 消费者用 ThinkPHP 的
Db::transaction()包裹多张统计表的INSERT ... ON DUPLICATE KEY UPDATE,保证单次同步原子性 - 给每条统计任务加唯一 ID(如
order_123456_cat_789),防止重复消费导致数据翻倍 —— ThinkPHP 的queue:work默认不保证 exactly-once
Redis 计数器不是万能的,要注意原子性和过期策略
很多人用 $redis->incr('stat:pv') 做页面访问量,但没考虑:并发 incr 可能漏计(Redis 是单线程,但 PHP-FPM 多进程可能因网络延迟/重试造成覆盖)、key 永不过期占内存、没做持久化导致重启丢数。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用
INCRBY替代多次INCR,比如每次记录 100 次 PV 才$redis->incrBy('stat:pv', 100),降低 Redis QPS - 所有统计 key 必须设
EX过期时间,例如$redis->setex('stat:uv:20240520', 86400, 0),否则 Redis 内存迟早爆掉 - 关键指标(如订单数)不能只靠 Redis,每天凌晨用
SELECT COUNT(*) FROM order WHERE created_at >= '2024-05-20'校验并修正,脚本写进application/command/CheckStat.php
分表后 count() 和 sum() 查询必须改写,ThinkPHP 不会自动路由
订单表按月分表(order_202405, order_202406),用 OrderModel::where('status', 1)->sum('amount') 会只查当前模型绑定的那张表(通常是 order),结果为 0 或报错 Table 'db.order' doesn't exist。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 手动拼 UNION 查询:
Db::name('order_202405')->union(['order_202406', 'order_202407'])->sum('amount'),注意 ThinkPHP 6.1+ 才支持数组形式 union - 更稳的方式是用原生 SQL:
Db::query("SELECT SUM(amount) FROM (SELECT amount FROM order_202405 UNION ALL SELECT amount FROM order_202406) t") - 千万别依赖模型的
table()动态切换 —— 它只影响单次查询,count()、sum()等聚合方法内部会重新生成查询,可能绕过你设的 table



















