or 與 union all的優化
在SQL開發中,我們經常會遇到這樣的情況:需要組合多個相似但略有不同的查詢結果。大多數開發者本能地使用UNION/UNION ALL來解決,這種方式直觀易懂,但在特定場景下卻隱藏著巨大的性能浪費。
本案例將從執行原理層面,深入剖析為什么在某些情況下,OR條件能比UNION ALL帶來質的性能提升。
- 第一部分:查詢dcc_sys_log表中cause條件為"對端被關閉%"的記錄
select peer_id 對端標識,
null 源域名,
null 目標域名,
alert_type 告警類型,
log_time 告警時間,
cause 告警內容,
deal_log 處理狀態,
deal_staff 處理人,
deal_time 處理時間,
remark 備注
from dcc_sys_log
where action = ‘disconnect’
and cause like ‘對端被關閉%’
and deal_log = ‘deal_log’
and alert_type = ‘alert_type’
and log_time >= TO_DATE(‘2024-08-02’, ‘YYYY-MM-DD’)
and log_time < TO_DATE(‘2024-08-03’, ‘YYYY-MM-DD’) + 1
– 第二部分:查詢dcc_ne_log表中result=0的記錄
union
(select peer_id 對端標識,
origin_host 源域名,
dest_host 目標域名,
alert_type 告警類型,
log_time 告警時間,
cause 告警內容,
deal_log 處理狀態,
deal_staff 處理人,
deal_time 處理時間,
remark 備注
from dcc_ne_log
where result = 0
and cause like ‘parser失敗%’
and deal_log = ‘deal_log’
and alert_type = ‘alert_type’
and log_time >= TO_DATE(‘2024-08-02’, ‘YYYY-MM-DD’)
and log_time < TO_DATE(‘2024-08-03’, ‘YYYY-MM-DD’) + 1)
union
– 第三部分:查詢dcc_ne_log表中result_code特定值的記錄
(select peer_id 對端標識,
origin_host 源域名,
dest_host 目標域名,
alert_type 告警類型,
log_time 告警時間,
cause 告警內容,
deal_log 處理狀態,
deal_staff 處理人,
deal_time 處理時間,
remark 備注
from dcc_ne_log
where result_code = ‘DIAMETER_UNABLE_TO_DELIVER’
and svcctx_id like ‘SR-Timeout%’
and deal_log = ‘deal_log’
and alert_type = ‘alert_type’
and log_time >= TO_DATE(‘2024-08-02’, ‘YYYY-MM-DD’)
and log_time < TO_DATE(‘2024-08-03’, ‘YYYY-MM-DD’) + 1)
union
– 第四部分:查詢dcc_sys_log表中cause條件為"接收消息異常%"的記錄
(select peer_id 對端標識,
null 源域名,
null 目標域名,
alert_type 告警類型,
log_time 告警時間,
cause 告警內容,
deal_log 處理狀態,
deal_staff 處理人,
deal_time 處理時間,
remark 備注
from dcc_sys_log
where action = ‘disconnect’
and cause like ‘接收消息異常%’
and deal_log = ‘deal_log’
and alert_type = ‘alert_type’
and log_time >= TO_DATE(‘2024-08-02’, ‘YYYY-MM-DD’)
and log_time < TO_DATE(‘2024-08-03’, ‘YYYY-MM-DD’) + 1)
分析問題
這段SQL的執行過程實際上是:
- 四次獨立的表掃描:
-
兩次對dcc_sys_log表的完整掃描
-
兩次對dcc_ne_log表的完整掃描
- 四次過濾操作:
- 每個子查詢各自應用WHERE條件進行過濾
- 三次合并操作:
- 對四個結果集執行UNION操作,包含排序和去重
- 四次重復的公共條件判斷:
-
deal_log = ‘deal_log’
-
alert_type = ‘alert_type’
-
相同的時間范圍條件
這種寫法的效率問題主要體現在:相同表被多次無謂地掃描,公共條件被重復判斷,導致CPU和I/O資源的嚴重浪費。
精準優化:拆解和合并
經過分析,我們可以看到明顯的優化空間:
- 相同表的查詢合并:
-
第一部分和第四部分都查詢dcc_sys_log表
-
第二部分和第三部分都查詢dcc_ne_log表
- 差異點隔離:
-
dcc_sys_log表的差異僅在于cause條件
-
dcc_ne_log表的差異在于result/result_code/svcctx_id條件組合
將這些相似查詢通過OR條件合并后的SQL:
– 第一部分:合并對dcc_sys_log的兩次查詢
select peer_id 對端標識,
null 源域名,
null 目標域名,
alert_type 告警類型,
log_time 告警時間,
cause 告警內容,
deal_log 處理狀態,
deal_staff 處理人,
deal_time 處理時間,
remark 備注
from dcc_sys_log
where action = ‘disconnect’
and (cause like ‘對端被關閉%’ or cause like ‘接收消息異常%’)
and deal_log = ‘deal_log’
and alert_type = ‘alert_type’
and log_time >= TO_DATE(‘2024-08-02’, ‘YYYY-MM-DD’)
and log_time < TO_DATE(‘2024-08-03’, ‘YYYY-MM-DD’) + 1
union
– 第二部分:合并對dcc_ne_log的兩次查詢
select peer_id 對端標識,
origin_host 源域名,
dest_host 目標域名,
alert_type 告警類型,
log_time 告警時間,
cause 告警內容,
deal_log 處理狀態,
deal_staff 處理人,
deal_time 處理時間,
remark 備注
from dcc_ne_log
where ((result = 0 and cause like ‘parser失敗%’) – 第一個差異條件組合
or (result_code = ‘DIAMETER_UNABLE_TO_DELIVER’ and svcctx_id like ‘SR-Timeout%’)) – 第二個差異條件組合
and deal_log = ‘deal_log’
and alert_type = ‘alert_type’
and log_time >= TO_DATE(‘2024-08-02’, ‘YYYY-MM-DD’)
and log_time < TO_DATE(‘2024-08-03’, ‘YYYY-MM-DD’) + 1
實際應用中的注意事項
- 括號使用:在復雜OR條件組合中,正確使用括號確保邏輯正確
where ((result = 0 and cause like ‘parser失敗%’)
or (result_code = ‘DIAMETER_UNABLE_TO_DELIVER’ and svcctx_id like ‘SR-Timeout%’))
and deal_log = ‘deal_log’
2. OR vs AND優先級:OR條件比AND優先級低,必要時使用括號明確優先級
-
處理NULL值:當OR條件兩側涉及NULL值判斷時,需特別注意邏輯正確性
-
驗證結果一致性:務必確認優化前后結果完全一致
-
索引使用情況:通過執行計劃檢查OR條件是否影響了索引使用
優化效果分析
這個優化帶來了幾個明顯的好處:
-
表掃描次數減少:原來需要掃描dcc_sys_log表2次,dcc_ne_log表2次,現在各只需掃描1次
-
I/O減少:減少了50%的表掃描,相應減少了磁盤I/O操作
-
代碼簡化:代碼行數從34行減少到24行,更加簡潔易讀
-
排序操作減少:UNION操作需要對結果進行排序去重,合并后的查詢能減少排序工作量
小結
本例通過OR條件替代UNION操作,減少表掃描次數。這種簡單卻有效的優化方法,在實際工作中經常被忽視。通過識別SQL中的重復訪問模式,將相似條件的查詢合并,我們可以在不改變業務邏輯的前提下,顯著提升SQL執行效率。
在實際優化工作中,建議首先嘗試通過EXPLAIN PLAN分析查詢性能,然后再決定是使用OR條件還是保留UNION結構,以獲得最佳性能。