日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

sql查询时间大于某一时间_查询时间从24分钟到2秒钟:记一次神奇的SQL优化

發布時間:2025/4/16 数据库 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 sql查询时间大于某一时间_查询时间从24分钟到2秒钟:记一次神奇的SQL优化 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
作者 | VWO譯者 | 無明編輯 | VincentAI 前線導讀:去年十二月份,VWO 平臺支持團隊發布了一份缺陷報告。這份報告很有意思,其中有一個來自某家企業用戶的分析查詢,它的運行速度非常慢。本文作者是這個數據平臺的一員,所以立馬開始著手診斷這個問題。更多優質內容請關注微信公眾號“AI 前線”(ID:ai-front)背? ?景

首先,我覺得有必要介紹一下 VWO(https://vwo.com/)平臺。人們可以在這個平臺上運行各種與他們的網站有關的工作負載,比如 A/B 測試、跟蹤訪問用戶、轉換、漏斗分析、渲染熱點圖、重放訪問用戶步驟,等等。

這個平臺真正強大的地方在于它所提供的報告。如果沒有這個平臺,即使企業用戶收集了大量數據也是毫無用處的,因為他們無法從數據中獲取洞見。

有了這個平臺,用戶可以針對海量數據執行各種復雜的查詢,比如下面這個: ?

Show?all clicks?by?visitors?on?webpage?"abc.com"
FROM?<date?d1>?TO?<date?d2>
for?people who were either
using?Chrome?as?a browser?OR?
(were browsing?from?Europe?AND?were?using?iPhone devices)

請注意查詢中的布爾運算符,查詢接口為用戶提供了這些東西,他們可以隨意運行復雜的查詢來獲得想要的數據。

慢查詢

這個用戶執行的查詢從表面上看應該是很快的: ?

Show?me all?session?recordings?
for?users?who visited?any?webpage?
containing the?url?that matches the pattern?"/jobs"

這個網站的流量是非常巨大的,我們保存了數百萬個唯一的 URL。這個用戶想要查詢符合他們業務需求的 URL。

初步診斷

現在讓我們來看看在數據庫方面都發生了什么。下面是相應的 SQL 語句: ?

SELECT?
count(*)?
FROM?
acc_{account_id}.urls?as?recordings_urls,?
acc_{account_id}.recording_data?as?recording_data,?
acc_{account_id}.sessions?as?sessions?
WHERE?
recording_data.usp_id = sessions.usp_id?
AND?sessions.referrer_id = recordings_urls.id?
AND?( urls &&?array(select?id?from?acc_{account_id}.urls?where?url?ILIKE?'%enterprise_customer.com/jobs%')::text[] )?
AND?r_time > to_timestamp(1542585600)?
AND?r_time < to_timestamp(1545177599)?
AND?recording_data.duration >=5?
AND?recording_data.num_of_pages >?0?;

這是它的執行時間: ?

Planning?time: 1.480?ms
Execution?time: 1431924.650?ms

這個語句查詢的行數在 15 萬行左右。查詢計劃顯示了一些信息,但還不能看出瓶頸出在哪里。

我們再來進一步分析一下查詢語句。這個語句連接了三張表: ?

  • sessions:用于展示會話信息的表,例如 browser、user-agent、country,等等。

  • recording_data:記錄 url、頁面、時間段,等等。

  • urls:為了避免出現重復的 url,我們使用單獨的表對 url 進行了規范化。

  • 另外請注意,我們使用 account_id 對這三表進行了分區,所以不可能出現因為某些賬號記錄過多導致性能變慢的情況。

    尋找線索

    經過進一步排查,我們發現這個查詢有一些不一樣的地方。比如下面這行: ?

    urls && array(
    select?id?from?acc_{account_id}.urls?
    where?url?ILIKE?'%enterprise_customer.com/jobs%'
    )::text[]

    最開始我認為針對所有長 URL 執行“ILIKE”操作是導致速度變慢的元兇,但其實并不是! ?

    SELECT?id?FROM?urls?WHERE?url?ILIKE?'%enterprise_customer.com/jobs%';
    id
    --------
    ...
    (198661 rows)

    Time: 5231.765 ms

    模式匹配查詢本身只花了 5 秒鐘,所以要匹配數百萬個 URL 顯然并不是個問題。

    第二個可疑的地方是 JOIN 語句,或許是大量的連接操作導致速度變慢?一般來說,如果查詢速度變慢,我們首先會認為連接操作是罪魁禍首,但對于目前這個情況,我不認為是這樣的。 ?

    analytics_db=# SELECT
    count(*)
    FROM
    acc_{account_id}.urls?as?recordings_urls,
    acc_{account_id}.recording_data_0?as?recording_data,
    acc_{account_id}.sessions_0?as?sessions
    WHERE
    recording_data.usp_id = sessions.usp_id
    AND?sessions.referrer_id = recordings_urls.id
    AND?r_time > to_timestamp(1542585600)
    AND?r_time < to_timestamp(1545177599)
    AND?recording_data.duration >=5
    AND?recording_data.num_of_pages >?0?;
    count
    -------
    8086
    (1?row)

    Time:?147.851?ms

    看,JOIN 操作實際上是很快的。

    縮小可疑范圍

    我開始調整查詢語句,盡一切可能提升查詢性能。我和我的團隊想出了兩個方案。

    針對子查詢使用 EXISTS:我們想要進一步確認問題是不是出在 URL 子查詢上。一種方法是使用 EXISTS,它會在找到第一條匹配記錄時就返回,對性能提升很有幫助。 ?

    SELECT
    ? ?count(*)?
    FROM?
    ? ?acc_{account_id}.urls?as?recordings_urls,
    ? ?acc_{account_id}.recording_data?as?recording_data,
    ? ?acc_{account_id}.sessions?as?sessions
    WHERE
    ? ?recording_data.usp_id = sessions.usp_id
    AND?(?1?=?1?)
    AND?sessions.referrer_id = recordings_urls.id
    AND?(exists(select?id?from?acc_{account_id}.urls?where?url?ILIKE?'%enterprise_customer.com/jobs%'))
    AND?r_time > to_timestamp(1547585600)
    AND?r_time < to_timestamp(1549177599)
    AND?recording_data.duration >=5
    AND?recording_data.num_of_pages >?0?;
    ? ?count
    ? ?32519
    ? ?(1 row)
    Time: 1636.637 ms

    使用了 EXISTS 后,速度變快了很多。那么問題來了,為什么 JOIN 查詢和子查詢都很快,但放在一起就變得這么慢呢?

    將子查詢移到 CTE 中:如果子查詢本身很快,我們可以預先計算結果,然后再傳給主查詢。 ?

    WITH matching_urls AS (
    ????select?id::text?from?acc_{account_id}.urls?where?url?ILIKE?'%enterprise_customer.com/jobs%'
    )

    SELECT?
    ? ?count(*)?FROM?acc_{account_id}.urls?as?recordings_urls,?
    ? ?acc_{account_id}.recording_data?as?recording_data,?
    ? ?acc_{account_id}.sessions?as?sessions,
    ? ?matching_urls
    WHERE?
    ? ?recording_data.usp_id = sessions.usp_id?
    ? ?AND?(?1?=?1?)?
    ? ?AND?sessions.referrer_id = recordings_urls.id
    ? ?AND?(urls &&?array(SELECT?id?from?matching_urls)::text[])
    ? ?AND?r_time > to_timestamp(1542585600)?
    ? ?AND?r_time < to_timestamp(1545107599)
    ? ?AND?recording_data.duration >=5?
    ? ?AND?recording_data.num_of_pages >?0;

    但這樣仍然很慢。

    尋找元兇

    還有個地方之前一直被我忽略了,但因為沒有其他辦法了,所以我決定看看這個地方,那就是 && 運算符。既然 EXISTS 對性能提升起到了很大作用,那么剩下的就只有 && 可能會導致查詢變慢了。

    && 被用來找出兩個數組的公共元素。

    初始查詢中的 && 是這樣的: ?

    AND ( urls && array(select?id?from?acc_{account_id}.urls?where?url?ILIKE?'%enterprise_customer.com/jobs%')::text[] )

    我們對 URL 進行了模式匹配,然后與所有 URL 進行交集操作。這里的“urls“并不是指包含了所有 URL 的表,而是 recording_data 的”urls“列。

    因為現在對 && 有所懷疑,我使用 EXPLAIN ANALYZE 對查詢語句進行了分析。 ?

    Filter: ((urls && ($0)::text[])?AND?(r_time >?'2018-12-17 12:17:23+00'::timestamp with time zone)?AND?(r_time '2018-12-18 23:59:59+00'::timestamp with time zone)?AND?(duration >=?'5'::double precision)?AND?(num_of_pages >?0))
    Rows Removed by Filter:?52710

    因為有好多行 &&,說明它被執行了好幾次。

    我通過單獨執行這些過濾條件確認了是這個問題。

    SELECT?1
    FROM?
    ? acc_{account_id}.urls?as?recordings_urls,?
    ? acc_{account_id}.recording_data_30?as?recording_data_30,?
    ? acc_{account_id}.sessions_30?as?sessions_30?
    WHERE?
    ? urls &&?array(select?id?from?acc_{account_id}.urls?where?url?ILIKE?'%enterprise_customer.com/jobs%')::text[]

    這個查詢的 JOIN 很快,子查詢也很快,所以問題出在 && 上面。

    解決方案

    && 之所以很慢,是因為兩個集合都很大。如果我把 urls 替換成{"http://google.com/","http://wingify.com/"},這個操作就很快。

    我開始在谷歌上搜索如何在 Postgre 中不使用 && 進行交集操作,但并沒有找到答案。

    最后,我們決定這樣做:獲取所有匹配的 urls 行,像下面這樣: ?

    SELECT?urls.url
    FROM?
    ? acc_{account_id}.urls?as?urls,
    ? (SELECT?unnest(recording_data.urls)?AS?id)?AS?unrolled_urls
    WHERE
    ? urls.id = unrolled_urls.id?AND
    ? urls.url?ILIKE?'%jobs%'

    這里沒有使用 JOIN 語句,而是使用了一個子查詢,并展開 recording_data.urls 數組,這樣就可以直接在 where 語句中應用查詢條件。

    這里的 && 用來判斷一個給定的 recording 是否包含匹配的 URL。它會遍歷數組(或者說表中的行),在條件滿足時立即停止,這個看起來是不是跟 EXISTS 很像?

    因為我們可以在子查詢之外引用 recording_data.urls,在必要時可以使用 EXISTS 來包裝子查詢。

    把所有的東西放在一起,我們就得到了最終這個優化的查詢: ?

    SELECT?
    ? count(*)?
    FROM?
    ? acc_{account_id}.urls?as?recordings_urls,?
    ? acc_{account_id}.recording_data?as?recording_data,?
    ? acc_{account_id}.sessions?as?sessions?
    WHERE?
    ? recording_data.usp_id = sessions.usp_id?
    ? AND?(?1?=?1?)?
    ? AND?sessions.referrer_id = recordings_urls.id?
    ? AND?r_time > to_timestamp(1542585600)?
    ? AND?r_time < to_timestamp(1545177599)?
    ? AND?recording_data.duration >=5?
    ? AND?recording_data.num_of_pages >?0
    ? AND?EXISTS(
    ? ?SELECT?urls.url
    ? ?FROM?
    ? ? ?acc_{account_id}.urls?as?urls,
    ? ? ?(SELECT?unnest(urls)?AS?rec_url_id?FROM?acc_{account_id}.recording_data)?
    ? ?AS?unrolled_urls
    ? ?WHERE
    ? ? ?urls.id = unrolled_urls.rec_url_id?AND
    ? ? ?urls.url?ILIKE?'%enterprise_customer.com/jobs%'
    ? ?);

    這個查詢的執行時間為 1898.717 毫秒,是不是值得慶祝一下?

    等等,我們還要驗證一下結果是不是對的。我對 EXISTS 有所懷疑,因為它有可能會改變查詢邏輯,導致過早地退出。我們要確保不會在查詢中引入新的 bug。

    我們對慢查詢和快查詢結果進行了 count(*) 比較,不同數據集的查詢結果都是一致的。對于一些較小的數據集,我們還手動比對了具體數據,也沒有問題。

    學到的教訓

    在這次性能排查過程中,我們學到了這些東西: ?

  • 查詢計劃并不會告訴我們所有東西,但還是很有用的;

  • 越是明顯的疑點越不太可能是元兇;

  • 一個慢查詢可能包含多個單獨的瓶頸點;

  • 并非所有優化都是可簡化的;

  • 在可能的地方使用 EXISTS 來獲得大幅性能提升。

  • 結? ?論

    我們將一個查詢的運行時間從 24 分鐘降到了 2 秒鐘,一個不可思議的性能提升!我們花了 1 個半到 2 個小時的時間來優化和測試這個查詢。SQL 其實是一門非常神奇的語言,只要你放開心態去擁抱它。

    英文原文:

    https://parallelthoughts.xyz/2019/05/a-tale-of-query-optimization/

    你也「在看」嗎??

    總結

    以上是生活随笔為你收集整理的sql查询时间大于某一时间_查询时间从24分钟到2秒钟:记一次神奇的SQL优化的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。