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
<h3>MySQL 5.7 及以下必须用自连接或变量模拟排名</h3>
<p>老版本不支持窗口函数,硬靠 <code>GROUP BY</code> + <code>LIMIT</code> 无法跨分组取 Top N。常见错误是写成 “每个分类取一条”,或者误用子查询关联后没控制数量。</p>
<p>可行但需谨慎的做法是用相关子查询统计“比当前记录点击量高的同分类记录数”:</p>
- 该数量
- 性能差:对每行都查一次同分类更高点击的记录数,数据量大时明显变慢
- 存在并列时可能返回超过 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 ) <h3>避免 GROUP BY + MAX() 的典型误解</h3> <p>很多人直觉写 <code>SELECT category, MAX(click_count), title FROM articles GROUP BY category</code>,结果 title 完全不可控——MySQL 允许这种语法(sql_mode 不严格时),但 <code>title</code> 是任意一条匹配行的值,不是点击量最高那条的 title。</p> <p>根本原因:<code>GROUP BY</code> 后非聚合字段的值属于未定义行为。即使加了 <code>ORDER BY click_count DESC</code>,也不能保证 <code>title</code> 对应的是排序首行。</p> <p>所以这类写法必须配合窗口函数或子查询,不能依赖 <code>GROUP BY</code> 直接提取关联字段。</p> <h3>索引对性能影响极大,尤其在子查询场景</h3> <p>无论用窗口函数还是子查询,<code>(category, click_count)</code> 这个联合索引几乎是必需的。否则:</p>
- 窗口函数执行时无法高效分区排序,可能触发临时表和 filesort
- 子查询中每次查找同分类更高点击记录,会变成全表扫描
如果表有千万级数据,没这个索引,上面任一方案都可能跑几分钟甚至超时。别只盯着写法,先确认 EXPLAIN 显示用了 type=ref 或 range,key 列指向该联合索引。
另外,如果 click_count 允许 NULL,注意 ORDER BY click_count DESC 会把 NULL 排最前(默认行为),可能干扰 Top 3 结果。必要时加 WHERE click_count IS NOT NULL 过滤。











