淺析MySQL查詢?nèi)ブ厥鞘褂肬NION還是DISTINCT
前言
開發(fā)過程中經(jīng)常面臨數(shù)據(jù)去重需求,大家常會糾結(jié)兩種方案:
- 使用
DISTINCT在單條結(jié)果集中完成去重; - 使用
UNION合并多條查詢并自動去重。
還有很多開發(fā)者分不清 UNION 和 UNION ALL 的巨大差異,經(jīng)常誤用導致數(shù)據(jù)庫出現(xiàn)不必要的性能消耗。
本文對比兩者原理、適用場景、性能差距,給出線上環(huán)境選型標準。
一、先理清基礎(chǔ)語法與核心行為
1. DISTINCT
作用:對單條SQL的結(jié)果集進行去重。
SELECT DISTINCT user_id FROM `user_login_log` WHERE `date` = CURDATE();
執(zhí)行邏輯:數(shù)據(jù)庫取出所有滿足條件的數(shù)據(jù),按照指定字段進行分組對比,剔除重復行,保留唯一記錄。
2. UNION 與 UNION ALL(重點區(qū)分)
-- UNION:合并結(jié)果 + 自動去重 + 排序 SELECT user_id FROM `user` WHERE status = 1 UNION SELECT user_id FROM `app_key` WHERE status = 1; -- UNION ALL:僅簡單縱向拼接,**不去重、不排序** SELECT user_id FROM `user` WHERE status = 1 UNION ALL SELECT user_id FROM `app_key` WHERE status = 1;
很多人踩坑:以為 UNION = UNION ALL,二者性能差距極大。
UNION = UNION ALL + DISTINCT + 排序操作
二、底層實現(xiàn)原理對比
DISTINCT 原理
在結(jié)果集內(nèi)部構(gòu)建臨時內(nèi)存哈希表或者排序緩沖區(qū),遍歷數(shù)據(jù)消除本行內(nèi)重復記錄。
數(shù)據(jù)量較小使用內(nèi)存;數(shù)據(jù)量大超過緩沖區(qū)限制,則落地磁盤臨時文件,性能斷崖下跌。
UNION 原理
- 分別執(zhí)行前后兩條子查詢;
- 使用
UNION ALL把所有數(shù)據(jù)縱向匯總; - 全局執(zhí)行一次DISTINCT排序去重。
簡單公式:
UNION = UNION ALL + DISTINCT
三、核心性能結(jié)論
- 如果業(yè)務需要合并多條SQL結(jié)果并且去重:可以使用
UNION; - 如果多條SQL合并,原始數(shù)據(jù)不存在重復,優(yōu)先使用
UNION ALL,不要用 UNION; - 如果只是單表/單條查詢內(nèi)部去重,不要使用 UNION,直接使用
DISTINCT; - 杜絕濫用 UNION 實現(xiàn)單條SQL內(nèi)部去重,屬于完全錯誤用法。
四、場景分類實戰(zhàn)分析
場景1:單條查詢內(nèi)部去除重復數(shù)據(jù)
? DISTINCT
需求:查詢當日登錄日志里所有活躍用戶ID,同一用戶多條登錄記錄只展示一次。
-- 正確寫法 SELECT DISTINCT user_id FROM user_login_log WHERE `date` = CURDATE(); -- ? 錯誤示范,沒必要強行拆分UNION SELECT user_id FROM user_login_log WHERE `date` = CURDATE() UNION SELECT user_id FROM user_login_log WHERE `date` = CURDATE();
強行使用UNION會執(zhí)行兩次相同查詢,掃描雙倍數(shù)據(jù),額外執(zhí)行全局去重,資源翻倍浪費。
場景2:多條獨立查詢結(jié)果合并,需要全局去重
需求:從用戶表、密鑰表兩處查詢user_id,合并結(jié)果,同一個user_id只保留一條。
方案A UNION
SELECT user_id FROM `user` WHERE username = 'demo' UNION SELECT user_id FROM `app_key` WHERE access_key = 'demo_key';
方案B UNION ALL + 外層DISTINCT
SELECT DISTINCT user_id FROM (
SELECT user_id FROM `user` WHERE username = 'demo'
UNION ALL
SELECT user_id FROM `app_key` WHERE access_key = 'demo_key'
) t;
重點:方案A 和方案B哪個更快?
絕大多數(shù)情況下:UNION ALL + 外層DISTINCT 性能 ≥ UNION
原因:
UNION默認會附帶排序行為;
而外層DISTINCT優(yōu)化器可以選擇哈希去重,不一定強制排序,優(yōu)化空間更大。
追求穩(wěn)定高性能,推薦統(tǒng)一使用 UNION ALL + DISTINCT 寫法,避免UNION隱性排序帶來開銷。
場景3:多條查詢合并,明確不存在重復數(shù)據(jù)
直接使用 UNION ALL,不要使用UNION,不要額外加DISTINCT
省去全局比較、排序、去重的巨大開銷。
SELECT id FROM `user` LIMIT 100 UNION ALL SELECT id FROM `app_key` LIMIT 100;
五、高頻誤區(qū)匯總
誤區(qū)1:UNION 和 DISTINCT 可以隨意互相替換
不能替換。
DISTINCT作用于單查詢內(nèi)部;
UNION作用于多條查詢合并之后,適用場景邊界完全不同。
誤區(qū)2:UNION去重性能優(yōu)于 UNION ALL + DISTINCT
恰恰相反。
UNION強制執(zhí)行排序去重;UNION ALL只做拼接,把去重選擇權(quán)交給外層,優(yōu)化器擁有更多優(yōu)化策略。
誤區(qū)3:少量數(shù)據(jù),隨便寫無所謂
在測試環(huán)境少量數(shù)據(jù)看不出差距;當結(jié)果集上萬、十萬級別,UNION額外排序會直接引發(fā)慢查詢,線上極易爆出性能故障。
誤區(qū)4:不知道UNION自帶排序,導致不必要的消耗
MySQL UNION規(guī)范:合并完成后會執(zhí)行排序操作;如果你不需要排序,不要使用UNION。
六、索引層面額外優(yōu)化提示
DISTINCT查詢盡量建立覆蓋索引,避免大量回表;
-- 示例:利用索引直接完成去重,無需讀取原始數(shù)據(jù)表 CREATE INDEX idx_date_user ON user_login_log(`date`,user_id);
UNION ALL拆分多條查詢時,每條子查詢務必保證可以正常命中索引;
如果最終只需要獲取第一條匹配記錄,可以每層子查詢增加 LIMIT 實現(xiàn)短路查詢,減少掃描行數(shù)。
七、選型決策清單(線上直接套用)
- 僅單條SQL內(nèi)部去重 → 使用
DISTINCT - 多條SQL結(jié)果合并,存在重復且需要去重 → 優(yōu)先:
UNION ALL + 外層DISTINCT - 多條SQL結(jié)果合并,確認無重復 → 使用
UNION ALL - 禁止:單條查詢場景強行拆分使用UNION做去重
- 禁止:能用UNION ALL的場景隨意使用UNION
八、驗證手段
使用 EXPLAIN 觀察執(zhí)行計劃:
- UNION:通常能看到
Using temporary; Using filesort(臨時表+文件排序) - UNION ALL:沒有全局排序與臨時表,執(zhí)行計劃更加簡潔
總結(jié)一句話:去重工具沒有絕對好壞,分清場景再選擇;能使用 UNION ALL 就不要使用 UNION,能避免全局排序就盡量避免。
延伸業(yè)務小案例(你項目常用場景)
根據(jù)賬號、郵箱、密鑰多條件檢索用戶ID:
-- 最優(yōu)寫法
SELECT DISTINCT user_id FROM (
SELECT user_id FROM `user` WHERE username = 'demo'
UNION ALL
SELECT user_id FROM `user` WHERE email = 'demo@test.com'
UNION ALL
SELECT user_id FROM `app_key` WHERE access_key = 'demo_key'
) tmp;
相比直接寫三條UNION,性能更好,也是線上檢索場景標準寫法。
到此這篇關(guān)于淺析MySQL查詢?nèi)ブ厥鞘褂肬NION還是DISTINCT的文章就介紹到這了,更多相關(guān)MySQL查詢?nèi)ブ貎?nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于MySQL繞過授予information_schema中對象時報ERROR 1044(4200)錯誤
這篇文章主要介紹了關(guān)于MySQL繞過授予information_schema中對象時報ERROR 1044(4200)錯誤,本文給大家分享解決方法,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10
MySQL啟動失敗報錯:mysqld.service failed to run 
在日常運維中,MySQL 作為廣泛應用的關(guān)系型數(shù)據(jù)庫,其穩(wěn)定性和可用性至關(guān)重要,然而,有時系統(tǒng)升級或配置變更后,MySQL 服務可能會出現(xiàn)無法啟動的問題,本文針對某次實際案例進行深入分析和處理,需要的朋友可以參考下2024-12-12
mysql創(chuàng)建的外鍵無法保存的原因以及處理辦法
這篇文章主要介紹了mysql創(chuàng)建的外鍵無法保存的原因以及處理辦法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09
MySQL業(yè)務數(shù)據(jù)量增長到單表成為瓶頸時的解決方案
文章詳細介紹了MySQL在單表數(shù)據(jù)量增長到瓶頸時的解決方案,包括應急與優(yōu)化、架構(gòu)升級和終極解決方案,本文結(jié)合實例代碼給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2025-12-12

