1. 程式人生 > >使用子查詢可提升 COUNT DISTINCT 速度 50 倍

使用子查詢可提升 COUNT DISTINCT 速度 50 倍

原因 desc 精準 http user 計數 而且 -1 nbsp

Count distinct是SQL分析時的禍根

首先:如果你有一個大的且能夠容忍不精確的數據集,那像HyperLogLog這樣的概率計數器應該是你最好的選擇。但對於需要快速、精準答案的查詢,一些簡單的子查詢可以節省你很多時間。

讓我們以我們一直使用的一個簡單查詢開始:哪個圖表的用戶訪問量最大?

select
   dashboards.name,
   count(distinct time_on_site_logs.user_id)
from time_on_site_logs 
join dashboards on time_on_site_logs.dashboard_id = dashboards.id
group by name order by count desc

首先,我們假設user_id和dashboard_id上已經設置了索引,且有比圖表和用戶數多得多的日誌條目。


一千萬行數據時,查詢需要48秒。要知道原因讓我們看一下SQL解析:

技術分享


它慢是因為數據庫遍歷了所有日誌以及所有的圖表,然後join它們,再將它們排序,這些都在真正的group和分組和聚合工作之前。


先聚合,然後Join

group-聚合後的任何工作代價都要低,因為數據量會更小。group-聚合時我們不需使用dashboards.name,我們也可以先在數據庫上做聚集,在join之前:

select
  dashboards.name,
  log_counts.ct
from dashboards join ( select dashboard_id, count(distinct user_id) as ct from time_on_site_logs group by dashboard_id) as log_counts on log_counts.dashboard_id = dashboards.id order by log_counts.ct desc

現在查詢運行了20秒,提升了2.4倍。再次通過解析來看一下原因:

技術分享

正如設計的,group-聚合在join之前。而且,額外的我們可以利用time_on_site_logs表裏的索引。


首先,縮小數據集

我們可以做的更好。通過在整個日誌表上group-聚合,我們處理了數據庫中很多不必要的數據。Count distinct為每個group生成一個哈希——在本次環境中為每個dashboard_id——來跟蹤哪些bucket中的哪些值已經檢查過。

我們可以預先計算差異,而不是處理全部數據,這樣只需要一個哈希集合。然後我們在此基礎上做一個簡單的聚集即可。

select
  dashboards.name,
  log_counts.ct
 from dashboards
 join (
  select distinct_logs.dashboard_id,
  count(1) as ct
  from (
    select distinct dashboard_id, user_id
    from time_on_site_logs
  ) as distinct_logs
  group by distinct_logs.dashboard_id
) as log_counts
 on log_counts.dashboard_id = dashboards.id
order by log_counts.ct desc 

我們采取內部的count-distinct-group,然後將數據拆成兩部分分成兩塊。第一塊計算distinct (dashboard_id, user_id) 。第二塊在它們基礎上運行一個簡單group-count。跟上面一樣,最後再join。

技術分享

呵呵,大發現:這樣只需要0.7秒!這比上面的查詢快28倍,比原來的快了68倍。

通常,數據大小和類型很重要。上面的例子受益於基數中沒多少換算。distinct (user_id, dashboard_id)相對於數據總量來說數量也很少。不同的對數越多,用來group和計數的唯一數據就越多——代價便會越來越大。

下一次遇到長時間運行的count distinct時,嘗試一些子查詢來減負吧。

使用子查詢可提升 COUNT DISTINCT 速度 50 倍