本文目录导读:

- 方案一:MySQL / 数据库表(最常用)
- 方案二:Redis Sorted Set(高性能推荐)
- 方案三:文件存储(JSON / CSV)
- 方案四:Memcached(简单缓存)
- 方案五:PostgreSQL 窗口函数(复杂查询)
- 对比总结表
- 最佳实践建议
在 PHP 中存储排名数据,主要取决于数据量、实时性要求以及是否需要持久化,以下是几种常见的方案,从简单到高级:
MySQL / 数据库表(最常用)
适用场景:中小型项目、需要事务支持、需要关联用户信息。
直接排序(数据量小)
直接从数据库读取所有分数,用 ORDER BY 排序。
SELECT user_id, score FROM user_scores ORDER BY score DESC;
PHP 处理:
$rank = 1;
foreach ($users as $user) {
echo "第 {$rank} 名: {$user['user_id']} - {$user['score']}";
$rank++;
}
存储排名值(数据量中等)
在用户表里加一个 rank 字段,或者单独建一个 user_ranks 表。
-- 建表
CREATE TABLE user_ranks (
user_id INT PRIMARY KEY,
rank INT,
score DECIMAL(10,2),
updated_at TIMESTAMP
);
-- 更新排名(MySQL 8.0+ 用窗口函数)
UPDATE user_ranks ur
JOIN (
SELECT id, RANK() OVER (ORDER BY score DESC) as rnk
FROM users
) tmp ON ur.user_id = tmp.id
SET ur.rank = tmp.rnk;
Redis Sorted Set(高性能推荐)
适用场景:高并发、实时排行榜、游戏积分榜、秒杀活动榜单。
核心数据结构:ZADD / ZREVRANGE
// 1. 添加/更新分数(score 设为分数,member 设为用户ID)
$redis->zAdd('rank:global', 100, 'user:1001');
$redis->zAdd('rank:global', 250, 'user:1002');
$redis->zAdd('rank:global', 180, 'user:1003');
// 2. 获取排行榜(从高到低)
$rankList = $redis->zRevRange('rank:global', 0, 99, true);
// 返回 ['user:1002'=>250, 'user:1003'=>180, 'user:1001'=>100]
// 3. 获取某个用户的排名(从0开始,+1得到名次)
$rank = $redis->zRevRank('rank:global', 'user:1002') + 1; // 输出 1
// 4. 获取某个用户的分数
$score = $redis->zScore('rank:global', 'user:1003'); // 输出 180
优势:O(log N) 时间复杂度,支持超大排名;自带排序功能;天然支持分布式。
文件存储(JSON / CSV)
适用场景:单机小应用、临时数据、没有数据库的环境。
// 保存
$data = [
['user_id' => 1, 'score' => 100],
['user_id' => 2, 'score' => 250],
['user_id' => 3, 'score' => 180],
];
file_put_contents('ranking.json', json_encode($data));
// 读取并排序
$data = json_decode(file_get_contents('ranking.json'), true);
usort($data, function($a, $b) {
return $b['score'] <=> $a['score'];
});
Memcached(简单缓存)
如果仅需要内存中的排名,不需要持久化,可以用 Memcached 的 append 或直接数组处理,但 Memcached 不支持排序,通常需要配合数据库或自己写算法,不建议用于排名存储。
PostgreSQL 窗口函数(复杂查询)
如果是 PostgreSQL,可以直接用 SQL 计算排名,高效且无需额外存储:
SELECT
user_id,
score,
RANK() OVER (ORDER BY score DESC) as rank,
DENSE_RANK() OVER (ORDER BY score DESC) as dense_rank
FROM user_scores;
对比总结表
| 方案 | 数据量 | 实时性 | 持久化 | 查询速度 | 开发复杂度 |
|---|---|---|---|---|---|
| MySQL 直接查询 | < 10万 | 一般 | 慢(需索引) | ||
| MySQL 预存排名 | < 100万 | 一般 | 快 | ||
| Redis ZSET | 百万+ | 极快 | ✅(需RDB/AOF) | 极快 | |
| 文件存储 | < 1万 | 慢 | 慢 | ||
| PostgreSQL 窗口函数 | 百万+ | 快 | 中 |
最佳实践建议
-
如果用到 Redis,通常配合 MySQL 双写:
- Redis 负责高并发读取和实时排名。
- MySQL 负责持久化存储和后台管理。
- 通过异步任务将 MySQL 数据同步到 Redis。
-
相同的分数如何处理?
- 使用
ZRANK时,Redis 按字典序排序,相同分数排名会有先后。 - 如果想实现并列排名,可以在 Redis 中额外存
rank:equal(用有序集合记录各分数的排名),或者在应用层处理。
- 使用
-
分页排名:用
zRevRange(key, start, stop)实现高效分页。
如果你告诉我你的具体场景(如:用户量多少、排名更新频率、是否需要并列排名等),我可以为你推荐最合适的存储结构。