萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 資料庫 >> MySQL 教學

MySQL DISTINCT 去重深度解析:從單列到多列、從計數到明細的四種實戰模式

DISTINCT 是最常用的去重關鍵字,但「只能返回目標列」、「多列語義誤解」、「性能陷阱」讓許多開發者踩坑。本文系統梳理 DISTINCT 四大實戰模式、等價替代方案、性能優化與避坑指南。

模式一:單列去重與計數(最基礎)

-- 返回去重後的值列表
SELECT DISTINCT `meta_key` FROM `wp_postmeta`;

-- 計算去重後行數
SELECT COUNT(DISTINCT `meta_key`) FROM `wp_postmeta`;

適用: 低基數枚舉值(狀態、類型、標籤)、快速檢查數據分布。

模式二:多列去重的語義陷阱

-- 錯誤認知:以為只按 meta_key 去重,實際按 (meta_key, post_id) 組合去重
SELECT DISTINCT `meta_key`, `post_id` FROM `wp_postmeta`;

-- 正確:只想看 meta_key 去重,但同時想帶出 post_id(任意一行)
-- MySQL 允許非聚合列不在 GROUP BY 中(舊版本/關閉 ONLY_FULL_GROUP_BY)
SELECT `meta_key`, `post_id` FROM `wp_postmeta` GROUP BY `meta_key`;

核心規則: DISTINCT col1, col2 = GROUP BY col1, col2,**所有列組合唯一才保留**。若需「按某列去重、帶出其他列任意值」,必須用 GROUP BY 或窗口函數。

模式三:GROUP BY 替代 DISTINCT(兼容舊版本、可攜帶聚合)

-- 等價於 DISTINCT meta_key
SELECT `meta_key` FROM `wp_postmeta` GROUP BY `meta_key`;

-- 進階:去重同時統計出現次數
SELECT `meta_key`, COUNT(*) AS cnt FROM `wp_postmeta` GROUP BY `meta_key` ORDER BY cnt DESC;

-- 進階:去重同時取最新記錄的其他欄位(需排序保證)
SELECT `meta_key`, `meta_value`
FROM (
    SELECT `meta_key`, `meta_value` FROM `wp_postmeta` ORDER BY `post_id` DESC
) t GROUP BY `meta_key`;

模式四:MySQL 8.0 窗口函數 ROW_NUMBER()(最強控制力)

-- 按 meta_key 分組,按 post_id 降序編號,取第一行(最新)
WITH ranked AS (
    SELECT
        `meta_key`, `meta_value`, `post_id`,
        ROW_NUMBER() OVER (PARTITION BY `meta_key` ORDER BY `post_id` DESC) AS rn
    FROM `wp_postmeta`
)
SELECT `meta_key`, `meta_value`, `post_id` FROM ranked WHERE rn = 1;

優勢: 明確控制「保留哪一行」(最新/最舊/最大/最小),語義零歧義,執行計劃優於舊式子查詢。

性能對比與優化建議

實戰案例:WordPress postmeta 表優化

場景:wp_postmeta 10 萬行,需「獲取所有不重複的 meta_key 及其最新 meta_value」。

-- 方案 A:DISTINCT + 子查詢(舊版本)
SELECT DISTINCT pm1.meta_key,
  (SELECT pm2.meta_value FROM wp_postmeta pm2
   WHERE pm2.meta_key = pm1.meta_key ORDER BY pm2.post_id DESC LIMIT 1) AS latest_value
FROM wp_postmeta pm1;

-- 方案 B:GROUP BY + 子查詢(同理)
-- 方案 C:8.0 窗口函數(推薦)
WITH ranked AS (
  SELECT meta_key, meta_value,
         ROW_NUMBER() OVER (PARTITION BY meta_key ORDER BY post_id DESC) AS rn
  FROM wp_postmeta
)
SELECT meta_key, meta_value FROM ranked WHERE rn = 1;

方案 C 單掃描、無相關子查詢、可並行,效能提升 10 倍以上。

避坑清單

  1. DISTINCT 後跟 * 會對**所有列**組合去重,極少是想要的。
  2. COUNT(DISTINCT col) 在大表極慢(需建臨時表排序),改用 HyperLogLog (Redis) 或預計算彙總表。
  3. 字符集不同導致「視覺相同實際不同」:utf8mb4 vs latin1,統一字符集排序規則。
  4. GROUP BY 隱式排序(5.7+ 已移除),需顯式 ORDER BY 保證順序。

去重看似簡單,實則考驗對「業務鍵定義」、「數據分布」、「執行引擎行為」的理解。掌握四大模式,根據版本、數據量、語義精度選擇最優方案,才是資料庫工程師的專業素養。

copyright © 萬盛學電腦網 all rights reserved
方案執行機制適用場景優化建議
DISTINCT內部臨時表 + 排序去重單列、小結果集加索引 INDEX(meta_key) 避免 filesort
GROUP BY同 DISTINCT,可攜帶聚合需統計/帶出其他列同上,覆蓋索引 INDEX(meta_key, meta_value)
ROW_NUMBER()窗口函數,單掃描 + 排序精確控制保留行、大數據分區剪枝 + 索引順序匹配 ORDER BY
GROUP_CONCAT(DISTINCT ...)聚合字符串需將去重值拼成單字符串注意 group_concat_max_len (預設 1024)