MySQL 8.0+ 用 DENSE_RANK() 按分类点击量降序排名,取 rk ≤ 3 的记录;其对并列处理为连续排名(如[100,90,90,80]→[1,2,2,3]),确保并列第3名不被截断。

用窗口函数 rank() 或 dense_rank() 排序取 Top 3
MySQL 8.0+ 支持窗口函数,这是最直接、语义最清晰的解法。关键不是用 ORDER BY + LIMIT,而是给每条记录按分类内点击量打排名,再筛选排名 ≤ 3 的行。
注意:RANK() 和 DENSE_RANK() 在处理并列时行为不同:比如点击量为 [100, 90, 90, 80],RANK() 会给出 [1,2,2,4],DENSE_RANK() 是 [1,2,2,3]。若要求“最多取 3 条”,且允许并列第 3 名挤掉第 4 名,用 DENSE_RANK() 更稳妥。
示例 SQL:
SELECT category, title, click_count
FROM (
SELECT category, title, click_count,
DENSE_RANK() OVER (PARTITION BY category ORDER BY click_count DESC) AS rk
FROM articles
) t
WHERE rk <= 3;
MySQL 5.7 及以下必须用自连接或变量模拟排名
老版本不支持窗口函数,硬靠 GROUP BY + LIMIT 无法跨分组取 Top N。常见错误是写成 “每个分类取一条”,或者误用子查询关联后没控制数量。
可行但需谨慎的做法是用相关子查询统计“比当前记录点击量高的同分类记录数”:
- 该数量
- 性能差:对每行都查一次同分类更高点击的记录数,数据量大时明显变慢
- 存在并列时可能返回超过 3 条(比如 4 条并列第一),需额外去重或加
ORDER BY ... LIMIT 3做兜底
示例(含并列容忍):
SELECT a1.category, a1.title, a1.click_count FROM articles a1 WHERE ( SELECT COUNT(*) FROM articles a2 WHERE a2.category = a1.category AND a2.click_count > a1.click_count ) < 3;
避免 GROUP BY + MAX() 的典型误解
很多人直觉写 SELECT category, MAX(click_count), title FROM articles GROUP BY category,结果 title 完全不可控——MySQL 允许这种语法(sql_mode 不严格时),但 title 是任意一条匹配行的值,不是点击量最高那条的 title。
根本原因:GROUP BY 后非聚合字段的值属于未定义行为。即使加了 ORDER BY click_count DESC,也不能保证 title 对应的是排序首行。
所以这类写法必须配合窗口函数或子查询,不能依赖 GROUP BY 直接提取关联字段。
索引对性能影响极大,尤其在子查询场景
无论用窗口函数还是子查询,(category, click_count) 这个联合索引几乎是必需的。否则:
- 窗口函数执行时无法高效分区排序,可能触发临时表和 filesort
- 子查询中每次查找同分类更高点击记录,会变成全表扫描
如果表有千万级数据,没这个索引,上面任一方案都可能跑几分钟甚至超时。别只盯着写法,先确认 EXPLAIN 显示用了 type=ref 或 range,key 列指向该联合索引。
另外,如果 click_count 允许 NULL,注意 ORDER BY click_count DESC 会把 NULL 排最前(默认行为),可能干扰 Top 3 结果。必要时加 WHERE click_count IS NOT NULL 过滤。


















