最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MySQL中DISTINCT語句去重優(yōu)化機制詳解

 更新時間:2026年06月29日 08:27:03   作者:鴿芷咕  
本文深入解析了SQL中DISTINCT的優(yōu)化機制,介紹了內(nèi)SQL內(nèi)內(nèi)核如何自動將DISTINCT改寫為GROUPBY或LIMIT以提高查詢效率,通過具體案例展示了優(yōu)化器如何通過語義推理確定目標列是否被常量固定,從而實現(xiàn)高效去重

寫 SQL 的人,對 DISTINCT 實在太熟悉了。統(tǒng)計不重復(fù)的值、列出唯一組合,第一反應(yīng)都是加個 DISTINCT。語法簡單,意思也清楚,可數(shù)據(jù)量一上去,它就可能悄悄變成拖慢查詢的那個點。這篇文章想拆解的,是數(shù)據(jù)庫內(nèi)核給 DISTINCT 做的一套自動優(yōu)化,不用你改一行 SQL,內(nèi)核自己就把低效去重換成了高效查詢。原理并不復(fù)雜,但它正好能講清楚一件挺有意思的事:優(yōu)化器是怎么從一堆條件里"想明白"結(jié)果唯一的。

一、DISTINCT 憑什么慢

先看最樸素的一條:

SELECT DISTINCT a, b FROM s1;

意思是從 s1 里把那些不重復(fù)的 (a,b) 組合挑出來。要是內(nèi)核沒什么特殊處理,執(zhí)行路徑基本是固定的:全表掃一遍,再排序或者建個哈希表,然后在這上面去掉重復(fù),輸出。

數(shù)據(jù)量一大,掃描和排序這兩步哪個都不便宜。更可惜的是另一種情況,查詢里明明帶著很強的過濾條件,目標列的值早就被鎖死了,結(jié)果集其實最多就一行???DISTINCT 不懂這些,它照樣掃完全表、排完序、再去一次重,整段去重,成了純粹的空轉(zhuǎn)。

二、內(nèi)核想了兩條路

這種空轉(zhuǎn),內(nèi)核想了辦法對付,歸納起來就兩條路。

第一條,把 DISTINCT 改寫成 GROUP BY。GROUP BY 這條路平時就跑得比較成熟,背后站著鍵值消除和并行執(zhí)行兩樣現(xiàn)成的本事。鍵值消除說穿了也簡單,分組鍵里要是包含主鍵,主鍵已經(jīng)能唯一確定一行了,分組自然可以化簡。

-- 改寫前
SELECT DISTINCT a, b FROM s1;
-- 改寫后
SELECT a, b FROM s1 GROUP BY a, b;

第二條更狠,把 DISTINCT 或 GROUP BY 改寫成 LIMIT 1。要是能判定目標列已經(jīng)被常量卡死、結(jié)果最多一行,完整去重就完全沒必要了,找到一條滿足條件的,馬上返回。

-- 改寫前
SELECT a, b FROM s1 WHERE a=1 AND b=1 GROUP BY a, b;
-- 改寫后
SELECT a, b FROM s1 WHERE a=1 AND b=1 LIMIT 1;

第一條好理解。第二條的關(guān)鍵和難點,全在"要是能判定"這幾個字上,目標列到底有沒有被常量卡住,往往不是一眼能看出來的。下面重點拆這一條。

三、目標列被"釘死",是怎么回事

所謂目標列被常值固定,意思就是經(jīng)過分析能確定,目標列的取值已經(jīng)是具體常量了。一旦所有目標列都成了常量,結(jié)果集頂多一行,去重自然就多余了。

常量可能從幾個地方冒頭。WHERE 里直接給的,比如 a=1。JOIN 等值條件里間接推導(dǎo)出來的,比如 s1.a = s2.b。幾個謂詞之間互相傳遞出來的,A 等于 B,B 又等于 C,那 A 自然就等于 C。還有一種最省事的,目標列本身就是常量,連列都沒引用。

這背后其實是編譯原理里的兩門老手藝,一個叫常量傳遞,也有人叫常量傳播,另一個叫謂詞傳遞。

3.1 常量傳遞:把已經(jīng)知道的值往前推

SELECT DISTINCT a, b FROM s1 WHERE a=1 AND b=1;

優(yōu)化器會把 WHERE 拆成一棵邏輯表達式樹:

          AND
         /   \
       a=1   b=1

從這棵樹一眼能讀出來,a 這會兒是常量 1,b 也是常量 1。兩個目標列都釘死了,(a,b) 這個組合頂多一種取值,就是 (1,1)。結(jié)果集的行數(shù)被推理卡在了"至多一行",于是可以放心改寫成 LIMIT 1。改寫之后,掃描時撞見第一條滿足條件的記錄就返回,排序和去重節(jié)點整個消失。

3.2 謂詞傳遞:從等值關(guān)系里擠出約束

常量傳遞只管那種直接賦值的。可真實查詢里,約束常常藏得很深,得靠等值關(guān)系一層層往下挖。它靠的是個叫等價類的概念,說白了就是,彼此相等的一組列歸在同一類里,這一類里只要有誰被綁到了常量,整組人也就全跟著綁了。

SELECT s1.a, s2.b
FROM s1 INNER JOIN s2 ON s1.a = s2.b AND s1.a = 5
GROUP BY s1.a, s2.b;

推演過程是這樣。第一步,從 s1.a = s2.b 看出 s1.a 和 s2.b 屬于同一個等價類。第二步,s1.a = 5 又把這個等價類整體綁到了常量 5 上。第三步,靠謂詞傳遞就能得出,s2.b 也必然等于 5。

s1.a = s2.b          ┐
                     ├─ 等價類 { s1.a, s2.b }
s1.a = 5   ──────────┘ 常量 5 擴散到全體成員
                     ⇒ s2.b 也等于 5

這里有個細節(jié)值得注意,原始 SQL 里誰也沒直接說"s2.b 等于某個常量",這個結(jié)論是優(yōu)化器自己推出來的。兩個目標列都被固定之后,分組去重就能改寫成 LIMIT 1。

3.3 說穿了,它是一道證明題

這套判斷,可以當(dāng)成一道證明題來看。已知條件是 WHERE 的全部謂詞、JOIN 的全部等值條件、目標列的常量。推理規(guī)則就是常量傳遞、謂詞傳遞、等價類合并。要證的結(jié)論是,每個目標列是不是都能被綁到一個具體常量上。用偽代碼描述,大概是這樣:

function 可否改寫為_LIMIT_1(查詢 Q):
    常量綁定表 = {}
    # 1. 常量傳遞:記下 WHERE 里的直接賦值
    for 謂詞 in Q.WHERE:
        if 謂詞 形如 (列 = 常量):
            常量綁定表[列] = 常量
    # 2. 謂詞傳遞:建等價類,常量向同類成員擴散
    等價類 = 由所有等值條件合并而成()
    for 類 in 等價類:
        if 類中任一成員 已在 常量綁定表:
            把該常量綁定擴散給類內(nèi)全部成員
    # 3. 逐個檢查目標列,看是不是全部被卡死
    for 目標列 in Q.目標列:
        if 目標列 不在 常量綁定表:
            return False          # 有一列沒固定,改寫不安全
    return True                   # 全部固定,可以放心改寫

結(jié)論成立,"結(jié)果唯一"就是個被嚴格證明出來的事實,不是拍腦袋猜的。

四、改寫的安全底線:語義等價

任何 SQL 改寫的前提,都是不能改變結(jié)果。DISTINCT 要變 GROUP BY,再變 LIMIT 1,每一步都在動 SQL 的結(jié)構(gòu)。可真實業(yè)務(wù)里的語句,常常帶著 JOIN、子查詢、聚合,亂改一下結(jié)果就錯了。所以內(nèi)核必須立一套很嚴的約束條件,只有能證明"改寫前后結(jié)果完全一致"的時候才動手。這一下,就把干這活的難度從"會套規(guī)則",提到了"得會做證明"。

五、實際收益有多大

落到真實執(zhí)行計劃上,改寫的效果很直觀。下面是兩組最小化測試的數(shù)據(jù),具體數(shù)字會隨數(shù)據(jù)規(guī)模和硬件變化。

路徑一,DISTINCT 改寫成 GROUP BY,查詢時間從大約 464 毫秒降到了 249 毫秒,收益主要來自更成熟的并行執(zhí)行和鍵值裁剪。

路徑二,改寫成 LIMIT 1,效果尤其明顯。簡單場景從大約 30 毫秒降到 0.03 毫秒,帶 JOIN 的復(fù)雜場景從大約 12 毫秒降到 0.08 毫秒,差不多三個數(shù)量級的提升。

-- 改寫前執(zhí)行計劃(節(jié)選):掃描 + 排序去重
--   Sort
--     Sort Key: a, b
--     ->  Seq Scan on s1

-- 改寫后執(zhí)行計劃(節(jié)選):命中即返回,去重節(jié)點消失
--   Limit
--     ->  Seq Scan on s1
--           Filter: ((a = 1) AND (b = 1))

排序和去重這些節(jié)點,從執(zhí)行計劃里整個抹掉,這就是改寫落在執(zhí)行層面的樣子。

六、看懂了這一點,也就看懂了現(xiàn)代優(yōu)化器

傳統(tǒng)優(yōu)化器靠的是"比代價"。它把若干候選執(zhí)行計劃列出來,挨個估個代價,CPU、IO、內(nèi)存各多少,然后挑最便宜的那個。它會挑,但不會"想",規(guī)則庫里沒有的能力,它變不出來,代價算得再準,也只會在幾種排列里挑個相對不那么差的。

現(xiàn)代優(yōu)化器則額外多了一手"做推理"。它在算代價之前,先判定一件事,這個操作到底要不要發(fā)生。DISTINCT 改寫成 LIMIT 1 就是這種"先推理、后算賬"的典型,它不是把去重做得更快,而是判定去重這個動作根本不用做。

七、知識擴展

在 MySQL 中,DISTINCT 的執(zhí)行原理本質(zhì)上是排序去重臨時表去重。如果數(shù)據(jù)量較大,這兩個操作都會帶來顯著的 I/O 和內(nèi)存開銷(文件排序、創(chuàng)建隱式臨時表)。

優(yōu)化的核心邏輯是:讓 DISTINCT 操作盡可能利用索引(有序性),或通過改寫 SQL 邏輯來避免對全表進行無謂的排序去重。

以下是 7 條高效的優(yōu)化技巧與實戰(zhàn)方案:

1. 黃金法則:利用索引規(guī)避排序(Index Skip Scan)

這是最高效的優(yōu)化方式。如果 DISTINCT 的字段有索引,MySQL 可以利用索引的有序性,直接掃描索引去重,無需創(chuàng)建臨時表

  • 場景:查詢所有不重復(fù)的 category_id。
  • 優(yōu)化前SELECT DISTINCT category_id FROM products;(全表掃描,創(chuàng)建臨時表)
  • 優(yōu)化后ALTER TABLE products ADD INDEX idx_category (category_id);
  • 原理:MySQL 執(zhí)行 松散索引掃描(Loose Index Scan),跳過索引中重復(fù)的鍵值,僅讀取每組第一個值,性能極高。

注意:如果 DISTINCT 涉及多個字段(如 SELECT DISTINCT col1, col2),必須建立 (col1, col2) 的聯(lián)合索引才能觸發(fā)松散掃描。

2. 邏輯改寫:用 EXISTS 替代 DISTINCT(針對多表關(guān)聯(lián))

當(dāng)使用 JOIN 查詢時,DISTINCT 通常是為了消除因“一對多”關(guān)系產(chǎn)生的重復(fù)主表數(shù)據(jù)。這種情況非常消耗資源,因為你先關(guān)聯(lián)了龐大的數(shù)據(jù)集,再去重。

場景:查詢“下過訂單的所有用戶”。

優(yōu)化前(低效)

SELECT DISTINCT u.id, u.name 
FROM users u 
JOIN orders o ON u.id = o.user_id;

(MySQL 需關(guān)聯(lián)所有數(shù)據(jù),再對結(jié)果集去重)

優(yōu)化后(高效)

SELECT u.id, u.name 
FROM users u 
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);

(半連接(Semi-Join)機制,一旦找到一條匹配記錄立即返回,無需排序去重)

3. 邏輯改寫:用 GROUP BY 替代 DISTINCT(特定場景)

在某些復(fù)雜查詢(如涉及聚合函數(shù))或特定 MySQL 版本中,GROUP BY 的優(yōu)化器路徑可能更優(yōu),且顯式分組更容易利用索引。

場景:查詢指定條件下的唯一用戶 ID。

寫法對比

-- 方式一
SELECT DISTINCT user_id FROM orders WHERE status = 1;

-- 方式二(通常等價且更利于索引排序)
SELECT user_id FROM orders WHERE status = 1 GROUP BY user_id;

如果 (status, user_id) 有聯(lián)合索引,GROUP BY 可以直接利用索引順序完成分組,無需回表。

4. 利用 SQL_BIG_RESULT / SQL_SMALL_RESULT 提示優(yōu)化器

MySQL 允許你向優(yōu)化器提供提示,告知它結(jié)果集的大小,從而決定使用內(nèi)存臨時表還是磁盤臨時表。

  • 場景:結(jié)果集非常大(如幾百萬行)。
  • 語法SELECT SQL_BIG_RESULT DISTINCT user_id FROM huge_table;
  • 作用:強制 MySQL 直接使用磁盤臨時表(基于文件排序)而不是內(nèi)存臨時表,避免內(nèi)存不足時頻繁轉(zhuǎn)換到磁盤,從而提升吞吐量。

5. 縮小數(shù)據(jù)范圍:先過濾,再去重

將 DISTINCT 放在最內(nèi)層子查詢中,先利用索引縮小數(shù)據(jù)范圍,再對外層進行去重,可以極大減少臨時表的大小。

優(yōu)化前SELECT DISTINCT user_id FROM logs;

優(yōu)化后

SELECT DISTINCT user_id 
FROM (
    SELECT user_id FROM logs 
    WHERE create_time > '2026-01-01' 
      AND create_time < '2026-06-01'
) AS recent_logs;

6. 參數(shù)調(diào)優(yōu):增大臨時表閾值

如果無法完全避免臨時表,可以通過調(diào)整配置參數(shù)來降低磁盤 I/O 開銷:

  • tmp_table_size:內(nèi)存臨時表的最大大小。
  • max_heap_table_size:用戶創(chuàng)建的內(nèi)存表最大大小。
  • 建議:在內(nèi)存充足的情況下適當(dāng)調(diào)大這兩個值(例如設(shè)為 256M),可以有效避免臨時表“溢出”到磁盤,從而大幅提升 DISTINCT 性能。

7. 特殊場景:使用 DISTINCT 與 LIMIT 結(jié)合

如果你只需要前 N 個不重復(fù)的值,LIMIT 配合索引可以讓 MySQL 在找到滿足條件的 N 條記錄后立即停止掃描。

  • 示例SELECT DISTINCT category_id FROM products LIMIT 10;
  • 前提category_id 必須有索引。MySQL 會按索引順序掃描,找到 10 個不同的值后直接返回,極大地減少了掃描行數(shù)。

八、寫在最后

DISTINCT 優(yōu)化這件事,表面是讓去重變快,骨子里是優(yōu)化器在做邏輯推理。它用常量傳遞把已知值推到目標列上,用謂詞傳遞從等值關(guān)系里擠出隱藏的約束,再證明結(jié)果唯一,最后用 LIMIT 1 把去重整個短路掉。傳統(tǒng)優(yōu)化器和現(xiàn)代優(yōu)化器的差距,也就在這兒,一個會算代價,一個還會做證明。

以上就是MySQL中DISTINCT語句去重優(yōu)化機制詳解的詳細內(nèi)容,更多關(guān)于MySQL DISTINCT優(yōu)化的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 淺談MySQL數(shù)據(jù)庫表鎖了怎么解鎖

    淺談MySQL數(shù)據(jù)庫表鎖了怎么解鎖

    在使用 MySQL 數(shù)據(jù)庫時,有時候會發(fā)生某個表被鎖住的情況,這可能會導(dǎo)致其他用戶無法對該表進行讀寫操作,影響系統(tǒng)的正常運行,本文主要介紹了淺談MySQL數(shù)據(jù)庫表鎖了怎么解鎖,感興趣的可以了解一下
    2023-10-10
  • mysql使用instr達到in(字符串)的效果

    mysql使用instr達到in(字符串)的效果

    本文主要介紹了mysql使用instr達到in(字符串)的效果,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • MySQL?DDL從入門到精通,包含索引視圖分區(qū)表等全操作解析

    MySQL?DDL從入門到精通,包含索引視圖分區(qū)表等全操作解析

    本文詳細介紹DDL(數(shù)據(jù)定義語言)的核心概念、分類及常見操作,涵蓋創(chuàng)建、修改、刪除數(shù)據(jù)庫對象的方法,以及InnoDB存儲引擎的的高級特性,通過實例解析,幫助讀者掌握高效管理數(shù)據(jù)庫的技術(shù)與策略,感興趣的朋友跟隨小編一起看看吧
    2026-06-06
  • MySQL中表的幾種連接方式

    MySQL中表的幾種連接方式

    這篇文章主要給大家介紹了關(guān)于MySQL中表的幾種連接方式,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • 更新至MySQL 5.7.9的詳細教程

    更新至MySQL 5.7.9的詳細教程

    文章介紹了MySQL 5.7.9 GA版本的更新過程和一些常見警告的解決方法,包括設(shè)置`secure-file-priv`參數(shù)、跳過SSL連接、使用`skip-networking`代替`skip-name-resolve`等,感興趣的朋友一起看看吧
    2025-02-02
  • Mysql Sql語句注釋大全

    Mysql Sql語句注釋大全

    這篇文章主要介紹了Mysql Sql語句注釋大全,需要的朋友可以參考下
    2017-07-07
  • 在MySQL中操作克隆表的教程

    在MySQL中操作克隆表的教程

    這篇文章主要介紹了在MySQL中操作克隆表的教程,是Python入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下
    2015-05-05
  • MySQL中On duplicate key update的實現(xiàn)示例

    MySQL中On duplicate key update的實現(xiàn)示例

    ON DUPLICATE KEY UPDATE是一種MySQL的語法,它在插入新數(shù)據(jù)時,如果遇到唯一鍵沖突,則會執(zhí)行更新操作,而不是拋出異?;蚝雎栽摋l數(shù)據(jù),下面就具體來介紹一下如何使用
    2025-08-08
  • MySQL聯(lián)合索引功能與用法實例分析

    MySQL聯(lián)合索引功能與用法實例分析

    這篇文章主要介紹了MySQL聯(lián)合索引功能與用法,結(jié)合具體實例形式分析了聯(lián)合索引的概念、功能、具體使用方法與相關(guān)注意事項,需要的朋友可以參考下
    2017-09-09
  • MySQL8.0保姆級安裝教程(含安裝包獲取方式)

    MySQL8.0保姆級安裝教程(含安裝包獲取方式)

    這篇文章主要介紹了MySQL8.0保姆級安裝教程的相關(guān)資料,MySQL8.0安裝包通常包括一系列文件,這些文件是安裝和運行MySQL數(shù)據(jù)庫系統(tǒng)所必需的,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2026-04-04

最新評論

定襄县| 休宁县| 靖西县| 松阳县| 浪卡子县| 兴海县| 高碑店市| 文登市| 绥宁县| 碌曲县| 西盟| 如皋市| 正宁县| 津南区| 宜兰市| 尚志市| 乌拉特中旗| 普宁市| 德令哈市| 宜都市| 裕民县| 潍坊市| 金沙县| 黑山县| 江口县| 沽源县| 新昌县| 阿克苏市| 贵南县| 崇阳县| 汉源县| 靖西县| 灵璧县| 博客| 平凉市| 建始县| 饶河县| 渝北区| 余姚市| 甘谷县| 金昌市|