直接让 MySQL 聚合统计栏目点赞总数比 PHP 循环累加快得多;单查用 COUNT(*) + WHERE category_id = ?,批量查用 LEFT JOIN 子查询分组统计,并加索引和 Redis 缓存优化性能。

直接查数据库比用 PHP 循环累加快得多
栏目点赞总数不是靠 PHP 逐条读取再 array_sum() 算出来的——那样在数据量稍大(比如几千条内容)时就明显卡顿。正确做法是让 MySQL 直接聚合统计,PHP 只负责取结果。
假设你有两张表:category(栏目)和 like_log(点赞日志),其中 like_log.category_id 关联栏目,且每条记录代表一次有效点赞(去重逻辑应在写入时保证,比如联合唯一索引 (user_id, category_id))。
- 用
COUNT(*)而非COUNT(category_id),避免 NULL 干扰(只要字段非空,两者等价;但习惯上更安全) - 加
WHERE status = 1过滤掉软删除或待审核的点赞(如果业务有这类状态) - 务必给
category_id加索引,否则GROUP BY或单查某栏目的 COUNT 会全表扫描
查单个栏目的点赞数:用 WHERE + COUNT(*)
最常见场景:渲染某个栏目页时显示“已获 XXX 人点赞”。别查出所有点赞记录再 count,直接聚合:
SELECT COUNT(*) AS total FROM like_log WHERE category_id = ? AND status = 1
PHP 中用 PDO 预处理即可:
立即学习“PHP免费学习笔记(深入)”;
$stmt = $pdo->prepare("SELECT COUNT(*) AS total FROM like_log WHERE category_id = ? AND status = 1");
$stmt->execute([$categoryId]);
$total = (int) $stmt->fetchColumn();
- 参数必须绑定,防止 SQL 注入;
$categoryId应为整型,强转(int)是基础防御 - 不要用
SELECT *再 PHP 里 count 数组——哪怕只有几十行,也是多余开销 - 如果该查询被高频访问(如首页栏目列表),考虑加 Redis 缓存,key 可设为
cat:like_count:{$categoryId},过期时间按业务热度定(比如 60 秒)
批量查多个栏目的点赞数:用 LEFT JOIN 或子查询
栏目列表页要同时显示每个栏目的点赞数,一次性查完比循环 N 次更高效。推荐用 LEFT JOIN 配合 GROUP BY:
SELECT c.id, c.name, COALESCE(l.cnt, 0) AS like_count FROM category c LEFT JOIN ( SELECT category_id, COUNT(*) AS cnt FROM like_log WHERE status = 1 GROUP BY category_id ) l ON c.id = l.category_id
注意点:
-
COALESCE(l.cnt, 0)确保没点赞的栏目也显示 0,而不是 NULL - 子查询先按
category_id分组统计,避免主表category行数多时产生笛卡尔积 - 如果栏目数极少((SELECT COUNT(*) FROM like_log WHERE category_id = c.id))也行,但可读性差、MySQL 优化器有时不友好
缓存失效和计数一致性怎么处理
点赞操作是高频写,直接每次更新都刷数据库 COUNT 是错的——COUNT 是派生值,不该进事务主流程。正确策略是:写操作只改 like_log,读时查缓存或兜底查库。
- 用户点赞/取消赞后,立刻
DEL对应栏目的 Redis 缓存 key(如cat:like_count:123),下次读自动回源 - 不要用 Redis 的
INCR/DECR维护总数——除非你能确保每次点赞/取消都 100% 成功且不重复,而实际中网络超时、重复提交、前端绕过校验都会导致偏差 - 可跑定时任务(比如每小时)用上面的 SQL 语句全量校对 Redis 和数据库的差异,仅用于兜底,不作为主逻辑
真正难的不是怎么查总数,而是让“总数”在高并发下既快又不错——关键在于把统计从写路径剥离,交给读时聚合或异步缓存维护。











