
排行榜实时更新卡顿,先看是不是在用普通 InnoDB 表做高频 UPDATE
MySQL 排行榜卡,90% 的情况不是逻辑写得烂,而是表结构和更新方式没对齐场景。InnoDB 每次 UPDATE 都要走 redo log + buffer pool + 索引树维护,如果每秒几百次用户积分变更还带 ORDER BY score DESC LIMIT 100 查,磁盘 I/O 和锁竞争立刻拉满。
实操建议:
- 把排行榜核心字段(如
user_id、score、updated_at)单独拆进一张轻量表,只保留必要索引 —— 至少删掉所有非查询用的冗余字段和索引 - 避免在排行榜表里存昵称、头像等大字段;这些信息查出来后用
JOIN users或应用层异步补全 - 如果更新频率 > 50 QPS,直接放弃对原表
UPDATE ... ORDER BY的幻想,改用“写缓冲 + 定时合并”策略(见下一条)
用 MEMORY 表做实时榜单?小心重启丢数据和内存爆掉
MEMORY 表确实快,但它是把整张表塞进内存、不落盘。一旦 MySQL 重启、OOM Killer 干掉 mysqld,或执行了 TRUNCATE TABLE,数据就没了 —— 这不是 bug,是设计如此。
实操建议:
- 只用
MEMORY表存“最终展示用”的精简榜单(比如top100视图),每天定时从主表INSERT INTO top100 SELECT ... ORDER BY score DESC LIMIT 100刷新一次 -
MAX_HEAP_TABLE_SIZE必须显式调大,默认 16MB 很容易撑爆;设成256M前先算:100 万用户 ×(4 字节user_id+ 8 字节score+ 4 字节rank)≈ 16MB,留点余量再翻倍 - 别在
MEMORY表上建 HASH 索引还用来ORDER BY—— HASH 索引不支持范围和排序,ORDER BY会强制全表扫描
score 字段没加索引?ORDER BY score DESC LIMIT N 就是全表扫
很多人以为“反正就查前 100 条”,不建索引也无所谓。错。MySQL 不知道你要的是 TopN,它得先把所有行按 score 排一遍,再取前 N —— 数据量一过 10 万,EXPLAIN 里立马出现 Using filesort。
实操建议:
- 给
score加单独索引:ALTER TABLE leaderboard ADD INDEX idx_score (score);如果还要按时间衰减(比如最近 7 天),就建联合索引:ADD INDEX idx_score_updated (score, updated_at) - 注意
score类型:用INT或BIGINT,别用DECIMAL(10,2)—— 浮点类排序性能差,且可能因精度导致相同分数排名错乱 - 如果业务允许“近似 TopN”,可以用采样查询:
SELECT * FROM leaderboard TABLESAMPLE SYSTEM (5) ORDER BY score DESC LIMIT 100(MySQL 8.0.23+ 支持)
真正扛住高并发更新的方案:异步聚合 + 缓存兜底
硬刚数据库永远是最笨的办法。真实生产环境里,没人靠纯 SQL 实时刷排行榜。核心思路是:把“写”和“读”彻底解耦。
实操建议:
- 用户积分变更不直接
UPDATE leaderboard,而是发到消息队列(如 Kafka / Pulsar),由消费者批量聚合(比如每 1 秒合并同用户的多次变更)再落地 - 前端查榜优先走 Redis:
ZADD leaderboard:week user_id score+ZREVRANGE leaderboard:week 0 99 WITHSCORES,MySQL 只作为兜底和对账源 - 如果必须强一致,用 MySQL 8.0 的
SET PERSIST sort_buffer_size = 4194304调大排序缓存,但别指望靠这个救活设计缺陷
最常被忽略的一点:排行榜的“实时”到底指什么?是秒级?毫秒级?还是用户感知不到延迟就行?把这个问题问清楚,比调任何参数都管用。











