usort()是PHP中实现时间热度加权排序最可控、易调试的方式;ThinkPHP的order()不支持运行时表达式计算,必须在PHP层归一化各字段后用usort()按综合分降序排列,大数据量时需预计算或缓存分数。

usort() 是 PHP 时间热度加权排序的主力函数
直接用 usort() 处理数组级加权排序,是当前最可控、最易调试的方式。ThinkPHP 的 order() 或原生 SQL 的 ORDER BY 都不支持运行时表达式计算,硬塞 hot * 0.6 + (time - create_time) * 0.4 会报错或被当成字段名处理。
常见错误现象包括:SQLSTATE[42S22]: Column not found(字段不存在)、排序结果完全随机(比较函数返回布尔值而非整数)、老帖永远压倒新帖(未做时间归一化)。
- 必须用飞船操作符
,返回1/0/-1,不能用>或== - 时间维度不能直接减,要用相对新鲜度:比如
$a['create_time'] / $maxTime或exp(-($now - $a['create_time']) / 86400) - 所有参与加权的字段(如
hot、comments、create_time)应提前归一化到 [0,1] 区间,否则一个字段量级过大就会主导排序
热度与时间衰减公式怎么写才合理
简单相加(如 hot + (time_score * 0.3))容易失效,因为原始 hot 可能是 0–10000,而时间分是 0–1。真正可用的衰减逻辑要兼顾可读性与物理意义:
- 线性衰减适合短期榜单:用
max(0, 1 - ($now - $item['create_time']) / 604800)(7 天归零) - 指数衰减更符合传播规律:用
exp(-($now - $item['create_time']) / 172800)(2 天半衰期),需ext-bcmath或pow(M_E, -$delta / 172800) - 避免用
time() - $item['create_time']直接参与乘法——数值越大反而得分越低,逻辑反直觉 - 如果用对数热度(如
log10($item['views'] + $item['comments'] * 5 + 1)),记得加+1防止 log(0)
大数据量下 usort() 性能崩了怎么办
当 $list 超过 1000 条,usort() 的 O(n log n) 时间复杂度会明显拖慢接口响应,尤其在高并发场景下。
立即学习“PHP免费学习笔记(深入)”;
- 优先考虑预计算:在定时任务里把综合分写入数据库额外字段(如
rank_score),查询时只用order('rank_score desc') - 缓存层兜底:用 Redis 存储已排序的 ID 列表(
ZADD hot_rank $score $id),每次更新只ZINCRBY和ZREVRANGE - 不要在每次请求中重复算
$maxTime:如果数据按天分片,可查当天最大create_time缓存 5 分钟 - 前端可接受“近似实时”:对非首页榜单,允许使用 15 分钟前计算的分数,大幅降低压力
ThinkPHP 搜索器(withSearch)怎么桥接加权排序
withSearch 本身只认字段,但可以把它当作参数路由来用——把前端传来的 sort=hot_time 映射成 PHP 层的加权逻辑分支,而不是试图让框架理解公式。
- 在搜索器方法里判断:
if ($sort === 'hot_time') { return $query->whereRaw('1'); },只占位,不真排序 - 控制器里拿到
$list = $model->search(...)->select()后,再统一走usort()流程 - 别在搜索器里调
usort():它可能被复用在分页 count 查询中,导致 count 错误 - 参数校验要严格:
in_array($sort, ['hot_time', 'comment_hot', 'fresh_first']),防恶意传参触发未定义逻辑
实际落地时,最容易被忽略的是归一化步骤和缓存更新时机。热度分变,时间分也变,但两者量纲不同;一次更新没同步刷新缓存,榜单就会长期失真。与其追求“全自动”,不如明确哪部分交由定时任务跑,哪部分留给请求时轻量计算。



















