在一些業務場景中,會使用NOT EXISTS語句确保返回數據不存在于特定集合,部分同事會發現NOT EXISTS有些場景性能較差,甚至有些網上謠言說”NOT EXISTS不走索引”,哪對于NOT EXISTS語句,我們如何優化呢?
以今天優化的SQL為例,優化前SQL為:
SELECT count(1)
FROM t_monitor m
WHERE NOT exists
(SELECT 1
FROM t_alarm_realtime AS a
WHERE a.resource_id=m.resource_id
AND a.resource_type=m.resource_type
AND a.monitor_name=m.monitor_name)
我們使用LEFT JOIN方式進行優化,優化後SQL為:
SELECT count(1)
FROM t_monitor m
LEFT JOIN t_alarm_realtime AS a
ON a.resource_id=m.resource_id
AND a.resource_type=m.resource_type
AND a.monitor_name=m.monitor_name
WHERE a.resource_id is NULL
優化效果:
優化前執行時間29秒以上,優化後1.2秒,優化提升25倍
那NOT EXISTS真的不走索引麼?
查看兩種SQL的執行計劃:
使用NOT EXIST方式的執行計劃
使用LEFT JOIN方式的執行計劃:
從執行計劃來看,兩個表都使用了索引,區别在于NOT EXISTS使用DEPENDENT SUBQUERY方式,而LEFT JOIN使用普通表關聯的方式。
通過MySQL提供的Profiling方式來查看兩種方式的執行過程
使用NOT EXIST方式的執行過程
使用LEFT JOIN方式的執行過程:
從執行過程來看,LEFT JOIN方式的主要消耗在Sending data一項上(1.2s),而NOT EXISTS方式主要消耗在executeing和Sending data兩項上,受限于Profiling隻存放100行記錄緣故,從Profiling中隻能看到47個” executeing和Sending data”的組合項(每個組合項約50us),通過執行計劃看出,外表t_monitor的數據量為578436行,忽略統計信息不準情況下,使用NOT EXISTS方式應該會産生578436個” executeing和Sending data”的組合項,總計消耗時間=50μs*578436=28921800us=28.92s。
從上面執行過程可以推斷出,使用NOT EXISTS方式的執行性能嚴重依賴于NOT EXISTS子查詢的執行次數即外層查詢結果集的數據量。
除上述問題外,在優化過程中發現本應該存儲相同數據的resource_id列在兩個表中定義不同,一表為VARCHAR而另外一表為BIGINT,外部結果集的字段類型和NOT EXIST字表中字段類型不同導緻NOT EXISTS子查詢中無法使用索引,使得子查詢性能較差,最終影響整個查詢的執行性能。京東商城也曾出現過大量類似案例,一些表使用VARCHAR來存放訂單号,而另一些表使用BIGINT來存放,在兩表進行管理時性能極差,希望研發同事引以為戒。
關注我更多幹貨奉上!,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!