合理热度值设计需引入时间衰减因子,如hot_score = play_count * POW(0.98, DATEDIFF(NOW(), created_at)),并配合索引、预计算、Redis缓存ID列表及游标分页保障性能与稳定性。

热度值怎么设计才合理
直接用播放量 play_count 排序会出问题:一个 5 年前的老视频可能有 100 万播放,但最近一周没人看;而一个新视频 24 小时内涨了 5 万播放,实际热度更高。所以得加时间衰减因子。
常见做法是用「加权热度」公式,比如:hot_score = play_count / (1 + days_since_created * 0.3)
或者更平滑的指数衰减:hot_score = play_count * pow(0.98, days_since_created)
- 别用纯整数除法(如
play_count / days),PHP 里7 / 3是2.333...,但若字段类型是INT且没显式转FLOAT,MySQL 可能截断 - 如果视频表没有
created_at字段,先补上:ALTER TABLE videos ADD COLUMN created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP - 避免在
ORDER BY里写复杂表达式(如LOG(play_count + 1) * EXP(-HOUR(NOW() - created_at)/720)),会拖慢查询、无法走索引
SQL 层直接排序最省资源
除非业务要求实时动态算分(比如叠加点赞、分享、完播率),否则优先在数据库层完成排序。PHP 只负责取数据,不自己 usort() 整个列表——10 万条记录全拉到 PHP 再排,内存和时间都爆炸。
SELECT id, title, play_count, created_at,
play_count * POW(0.98, DATEDIFF(NOW(), created_at)) AS hot_score
FROM videos
WHERE status = 'published'
ORDER BY hot_score DESC
LIMIT 20;
-
POW()在 MySQL 5.7+ 和 MariaDB 10.2+ 支持;低版本可用POWER()替代 - 务必给
status加索引,否则WHERE先全表扫描 - 如果还要支持「按周热度」「按日热度」切换,别用
CASE WHEN套大表达式,建议拆成不同 SQL 或加预计算列(如week_hot_score)
PHP 侧缓存与更新策略
热度排序结果变化没那么快,每小时更新一次足够。硬扛实时请求,数据库压力大,用户也感知不到毫秒级差异。
- 用 Redis 缓存排序 ID 列表:
$redis->zRevRange('video:hot:weekly', 0, 19),配合ZADD每小时批量更新 - 不要缓存完整视频数据(含封面 URL、描述等),只缓存
id序列,查出来再IN ()查详情——避免缓存膨胀和数据不一致 - 如果某视频突然爆火(比如被大 V 转发),加个轻量钩子:检测
play_count单小时增长 > 5000,就触发该条目的hot_score强制重算并刷新缓存
注意分页时的稳定性
用 ORDER BY hot_score DESC LIMIT 20 OFFSET 40 分页,在高并发下容易漏数据或重复——因为热度值随时间浮动,第 3 页加载时,第 1 页的新进视频可能把原第 41 名挤掉,导致翻页错位。
立即学习“PHP免费学习笔记(深入)”;
- 改用游标分页:记住上一页最后一条的
hot_score和id,下一页查WHERE hot_score - 如果必须用
OFFSET,至少加hot_score的冗余索引:ALTER TABLE videos ADD INDEX idx_hot_status (status, hot_score DESC, id) -
前端展示“热度榜”时,别显示精确到小数点后 6 位的
hot_score——用户不需要,还暴露算法细节
真实场景里,最难的不是写对那条 ORDER BY,而是定义清楚「热度」到底服务什么目标:是推新?促长尾?还是承接突发流量?这些决定了衰减系数、是否加入互动权重、要不要人工置顶干预。算法可以调,但目标模糊,越调越乱。
