首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> MySQL 資料庫 >> MySQL 綜合教學

MySQL 取每組第一條記錄:三種 GROUP BY 寫法效能對比

     編輯:MySQL 綜合教學

「按用戶分組,取每組最新的一筆記錄」是經典分組 Top-N 問題。本文對比三種常見 MySQL 寫法,分析執行計畫差異,給出效能最優建議。

測試場景

表 member_payment:id (PK)、member_id、amount、create_time。需求:每個 member_id 取 id 最大(最新)的一筆,限制 10 組。

寫法一:子查詢排序後 GROUP BY(最直觀,效能最差)

SELECT * FROM (
    SELECT * FROM member_payment ORDER BY id DESC
) t GROUP BY member_id LIMIT 10;

執行計畫:Using temporary; Using filesort —— 內層全表排序產生臨時表,外層再分組,雙重開銷。資料量大時極慢。

寫法二:子查詢取 MAX(id) 再 LEFT JOIN(推薦,效能穩健)

SELECT s.* 
FROM (
    SELECT MAX(id) AS id FROM member_payment GROUP BY member_id LIMIT 10
) t
LEFT JOIN member_payment s ON t.id = s.id;

執行計畫:

  • 衍生表 t:GROUP BY member_id 走 idx_member_id 掃描,取各組 MAX(id)(索引最右值),極快。
  • 外層 LEFT JOIN:按主鍵 id 精準回表 10 次,ref 類型,零臨時表。

寫法三:EXISTS 相關子查詢(語意清晰,效能次優)

SELECT * FROM member_payment mp
WHERE EXISTS (
    SELECT 1 FROM (
        SELECT MAX(id) AS id FROM member_payment GROUP BY member_id LIMIT 10
    ) t WHERE t.id = mp.id
);

執行計畫:類似寫法二,但優化器可能將 EXISTS 轉為 SEMI JOIN,在某些版本上比 LEFT JOIN 略慢。

效能總結表

寫法臨時表Filesort掃描行數推薦等級
排序後 GROUP BY是是全表❌ 避免
MAX(id) + LEFT JOIN否否索引掃描 + 10 次主鍵查找✅ 首選
EXISTS 子查詢否否索引掃描 + 10 次主鍵查找✅ 次選

進階:MySQL 8.0 窗口函數寫法(最標準、可控)

SELECT * FROM (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY member_id ORDER BY id DESC) AS rn
    FROM member_payment
) t WHERE rn = 1 LIMIT 10;
  • 語意明確:PARTITION BY 分組,ORDER BY 排序,ROW_NUMBER()=1 取第一筆。
  • 可輕鬆擴展為「取每組前 3 筆」(rn <= 3)。
  • 執行計畫走 Window Function,無臨時表,效能與寫法二持平。

索引建議

為支援上述所有寫法,請建立複合索引:

CREATE INDEX idx_member_id_id ON member_payment (member_id, id DESC);

此索引同時服務 GROUP BY member_id 取 MAX(id) 及窗口函數分組排序。

結語

拒絕「先排序再 GROUP BY」的反模式。採用 MAX(pk) + JOIN 或 窗口函數 ROW_NUMBER(),配合 (group_col, pk DESC) 複合索引,即可獲得穩定高效的分組 Top-N 查詢。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved