DB2分頁SQL優化(寶,我優化了分頁,每分鐘都想你的夜)

前言:

最近,項目中的一個 DB2分頁查詢很慢 ,組長將此分頁的優化分派給了我;而後一頓優化(亂操做)後,將DB2分頁查詢耗時降到了比較滿意的狀況,[ 開森 ];前端

而後立刻將結果報告了組長,組長查看個人演示後,發現分頁查詢確實快了不少,能夠達到讓人「接受的程度」,比優化以前的 頁面一直轉圈等待 至關能夠了呀;sql

注:優化後的演示環境與發現分頁查詢慢時的環境基本一致,包括庫中數據量、DB2的配置、服務器的配置等。服務器

首先經過查看執行計劃發現,SQL語句中的索引都利用上了,那麼暫時就不是 索引 的問題了,最後發現是 SQL語句存在問題 ,對SQL進行了優化,查詢就快了;微信

下面就簡單描述下DB2的分頁SQL是怎麼進行優化的,binggou 走起;網絡

本文主線:

  • 必備前提:select 查詢SQL的邏輯執行順序
  • 優化歷程:DB2分頁SQL優化過程
  • 知識擴展:分享一些致使分頁查詢慢的特別案例

必備前提:

在對查詢SQL語句進行優化時,須要知道其邏輯執行順序,這對進行SQL優化有很大幫助的;學習

SQL的 邏輯執行順序 ,指的是SQL語句按照必定的規則,一整條語句應該如何執行,每個關鍵字、子句部分在什麼時刻執行;fetch

一、一個簡單select查詢SQL的邏輯執行順序以下:

  1. 先執行 from table join table ,獲取要操做的表及關聯的表,對它們計算笛卡爾積,獲得一個虛擬表v1;
  2. 而後執行 on 條件 ,對虛擬表v1進行鏈接查詢篩選,獲得一個虛擬表v2;
  3. 而後執行 where 條件 ,對虛擬表v2中的數據進行篩選,獲得虛擬表v3;
  4. 而後在執行 group by 語句,將虛擬表v3中的數據進行分組,獲得虛擬表v4;
  5. 再執行 Having 條件過濾 ,對分組後的虛擬表v4進行條件篩選,獲得虛擬表v5;
  6. 而後對虛擬表v5的數據執行 select 投影列 ,只保留select中的展現的字段,獲得虛擬表v6;
  7. 而後再執行 order by 排序語句 ,對虛擬表v6中的數據進行排序,獲得虛擬表v7;
  8. 最後才執行 limit 等分頁語句(限制條數) ,獲得虛擬表v8 。

二、簡單分析下查詢慢的分頁SQL:

上面簡單描述了下查詢SQL的邏輯執行順序,下面就來分析下查詢慢的分頁語句的邏輯執行順序;優化

SELECT * FROM
 (
   SELECT B.*, ROWNUMBER() OVER() AS RN FROM
    (
        select
           ts.name,
           ts.age,
           tc.class_name,
           ts.describe,
           ts.birthday
        from t_student ts
        LEFT JOIN t_class tc on tc.class_id = ts.class_id
        where 
            ts.age = 23
            AND ts.birthday >= TO_DATE('2020-06-12','yyyy-MM-dd')
            AND ts.birthday <= TO_DATE('2020-07-15' , 'yyyy-MM-dd')
       order by ts.birthday desc
    ) AS B
)AS A WHERE A.RN BETWEEN 10 AND 20

t_student 表中存在二級索引:index(age, birthday) spa

t_class 表的主鍵:class_idcode

2.一、SQL邏輯執行順序分析:
  1. 首先將對 t_student ts 和 t_class tc 表計算笛卡爾積,若是ts表中的數據量是10,tc表中數據量是10,那麼笛卡爾積就是 10*10 = 100,獲得虛擬表v1
  2. 而後進行 on條件篩選,獲得虛擬表v2
  3. 而後進行 where條件篩選,獲得虛擬表v3
  4. 而後執行select 投影列 ,只保留select中的展現的字段,獲得虛擬表v4
  5. 而後根據birthday字段進行排序,獲得虛擬表v5
  6. 最後對虛擬表v5中的數據根據行號進行分頁,獲取當前頁須要的數據,獲得最終結果集
2.二、經過分析獲得的邏輯執行順序得知:
  • 第一步中計算笛卡爾積,若是兩個表中數據量都很大的話,那笛卡爾積就會很是大,對後面的 on條件篩選、where條件篩選都會慢不少,這裏能夠想辦法優化下
  • select投影列中,必定要按需返回字段,特別是前端頁面沒有用到的大字段,必定不要加到投影列中,像此SQL中 describe我的描述 字段頁面中就不須要展現
  • 最後的分頁語句是在篩選出所有的數據後才根據行號篩選出當前頁須要的數據,心想,能夠不能夠,不要篩選出所有數據,而是篩選到當前頁所須要的數據便可

優化歷程:

經過上面SQL的邏輯執行順序分析得知有三處地方能夠嘗試進行下優化,看看查詢是否是變快了;

一、減少笛卡爾積:

由於後面的where條件篩選中,都是對主表 t_student 表進行的篩選,因此能夠提早使用where條件對t_student表進行篩選,獲得虛擬表,

而後使用這個虛擬表和鏈接的表 t_class 計算笛卡爾積,此時笛卡爾積已經小不少了;

SQL語句以下:

SELECT * FROM
 (
   SELECT B.*, ROWNUMBER() OVER() AS RN FROM
    (
    select
       ts.name,
       ts.age,
       tc.class_name,
       ts.describe,
       ts.birthday
    from (select class_id, name, age, describe, birthday
          from t_student
         where
              age = 23
              AND birthday >= TO_DATE('2020-06-12','yyyy-MM-dd')
              AND birthday <= TO_DATE('2020-07-15' , 'yyyy-MM-dd')
       ) ts
  LEFT JOIN t_class tc on tc.class_id = ts.class_id
  order by ts.birthday desc
  ) AS B
)AS A WHERE A.RN BETWEEN 10 AND 20

二、不須要的大字段禁止返回:

在select投影列中,將不須要的大字段 describe 不要放入其中,由於若是放到投影列中的話,查詢時會形成其佔用較多的緩衝池空間,若是致使緩衝池空間滿了的話,就要進行磁盤IO了,磁盤IO很是耗時的;

還有就是若是響應中帶有大字段的內容的話,在進行網絡IO時,會形成傳輸速度變慢,頁面加載數據時也會變慢的;

SQL語句以下:

SELECT * FROM
 (
   SELECT B.*, ROWNUMBER() OVER() AS RN FROM
    (
    select
       ts.name,
       ts.age,
       tc.class_name,
       ts.birthday
    from (select class_id, name, age, birthday
          from t_student
         where
              age = 23
              AND birthday >= TO_DATE('2020-06-12','yyyy-MM-dd')
              AND birthday <= TO_DATE('2020-07-15' , 'yyyy-MM-dd')
       ) ts
  LEFT JOIN t_class tc on tc.class_id = ts.class_id
  order by ts.birthday desc
  ) AS B
)AS A WHERE A.RN BETWEEN 10 AND 20

三、分頁時限制數據的篩選:

分頁查詢都是根據 pageNo(頁號),pageSize(一頁的數量)進行的所需頁面數據篩選;

例如,當前頁面是第二頁的話,每頁展現數據10條,當前頁數據的篩選,就是 rownum between ((pageNo-1) pageSize) and (pageNo pageSize) ,那麼在分頁前的數據篩選時,可使用 fetch first (page * rows) rows only 限制篩選的數據量,別所有篩選出來了,而是最多隻篩選到當前頁面中最大的行號前便可;

因爲使用了 fetch first (page * rows) rows only ,減小了不少的篩選操做,速度會快不少,特別是點擊前幾頁時,速度都是很是快的,可能越日後翻頁,響應會相應慢些,可是分頁時,基本都是查看前幾頁的,後面的幾乎不多看,因此此時效果看起來是十分好的

SQL語句以下:

select
     ts.name,
     ts.age,
     tc.class_name,
     ts.birthday
  from (
      select aa.class_id, aa.name, aa.age, aa.birthday
       from(
           select class_id, name, age, birthday, ROWNUMBER() OVER () AS RN
            from t_student
            where
                age = 23
                AND birthday >= TO_DATE('2020-06-12','yyyy-MM-dd')
                AND birthday <= TO_DATE('2020-07-15' , 'yyyy-MM-dd')
            order by birthday desc
            fetch first  20 rows only
       ) aa where aa.RN BETWEEN 10 AND 20
     ) ts
LEFT JOIN t_class tc on tc.class_id = ts.class_id

至此DB2分頁SQL已經基本完成了優化,查詢速度提高了一大半了,可是此分頁SQL還有優化的餘地,能夠經過修改SQL語句和索引 index(age, birthday) 進一步提高查詢速度;

下面在知識擴展部分簡單描述下怎麼再次進行優化;

知識擴展:

一、分頁SQL的進一步優化:

先來看下面這段SQL語句:

select class_id, name, age, birthday, ROWNUMBER() OVER () AS RN
   from t_student
   where
      age = 23
      AND birthday >= TO_DATE('2020-06-12','yyyy-MM-dd')
      AND birthday <= TO_DATE('2020-07-15' , 'yyyy-MM-dd')
      order by birthday desc
     fetch first  20 rows only

這段SQL語句執行時會使用到 二級索引 index(age, birthday) ,因爲在此索引上沒法查詢到所有的字段值,因此須要回表查詢 class_id, name 字段的值,因爲多了回表操做,會致使查詢變慢,因此此時要想辦法避免回表;

1.一、避免回表方法一:

只修改索引,將 二級索引 index(age, birthday) 改成 index(age, birthday,name,class_id) 便可,SQL語句不用變更;

可是索引字段變多後,對新增、更新、刪除SQL的操做影響比較大,會致使其執行耗時變長,由於須要對索引進行維護;因此若是這兩張表寫操做比較多的話, 不建議直接修改索引

1.二、避免回表方法二:

此方法其實也是須要回表的,可是此時回表次數會比以前回表的次數少的多得多,幾乎能夠忽略不計;

此方法不須要修改索引,而是對SQL語句進行修改, 首先進行 二級索引 index(age, birthday) 查詢時再也不須要返回 class_id, name, age, birthday 字段了,而是隻返回 t_student 表的 主鍵 stu_id ,此時就不須要回表了;

而後會使用到二級索引 index(age, birthday)中birthday默認排序,最後根據 fetch first 20 rows only 只返回前20條數據,此時返回了20條 主鍵 stu_id 值,而後能夠根據主鍵stu_id左外鏈接t_student,得到其它的字段值;

此時至關於只須要回表20次而已,不須要所有進行回表了;

SQL語句以下:

select stu2.class_id, stu2.name, stu2.age, stu2.birthday, ROWNUMBER() OVER () AS RN
  from (
    select stu_id
     from t_student
     where
        age = 23
        AND birthday >= TO_DATE('2020-06-12','yyyy-MM-dd')
        AND birthday <= TO_DATE('2020-07-15' , 'yyyy-MM-dd')
        order by birthday desc
       fetch first  20 rows only ) stu1
  left join t_student stu2 on stu1.stu_id = stu2.stu_id

最終優化後的分頁SQL以下:

select
     ts.name,
     ts.age,
     tc.class_name,
     ts.birthday
  from (
      select aa.class_id, aa.name, aa.age, aa.birthday
       from(
           select stu2.class_id, stu2.name, stu2.age, stu2.birthday, ROWNUMBER() OVER () AS RN
            from (
              select stu_id
               from t_student
               where
                  age = 23
                  AND birthday >= TO_DATE('2020-06-12','yyyy-MM-dd')
                  AND birthday <= TO_DATE('2020-07-15' , 'yyyy-MM-dd')
                  order by birthday desc
                 fetch first  20 rows only ) stu1
             left join t_student stu2 on stu1.stu_id = stu2.stu_id
       ) aa where aa.RN BETWEEN 10 AND 20
     ) ts
LEFT JOIN t_class tc on tc.class_id = ts.class_id

二、分頁查詢時的特殊狀況:

在進行分頁查詢時,通常是先查詢下符合條件的總數據量,這個數據量用於分頁,獲得分頁數的;

那麼咱們可能會遇到一些很是特別狀況,查詢總數據量的SQL以下:

select
   count(*)
from t_student ts
LEFT JOIN t_class tc on tc.class_id = ts.class_id
where
    ts.age = 23
    AND ts.birthday >= TO_DATE('2020-06-12','yyyy-MM-dd')
    AND ts.birthday <= TO_DATE('2020-07-15' , 'yyyy-MM-dd')

上面這個SQL語句其實能夠進行優化的,它其實不用關聯 t_class 表的, 具體緣由以下:

  • 一是由於where條件中都是對主表的 t_student 的篩選
  • 二是主表 t_student 與 外表 t_class 中數據關係是一對一

直接查詢主表中知足條件的數據便可,所以能夠將多餘的錶鏈接去掉,這會大大提高SQL的查詢速度;

八卦下,分析分析爲何有人會這麼寫呢?

猜測主要是爲了圖方便,直接複製的分頁SQL語句進行改的 [啼笑皆非] ;

❤ 點贊 + 評論 + 轉發 喲

若是本文對您有幫助的話,請揮動下您愛發財的小手點下贊呀,您的支持就是我不斷創做的動力,謝謝啦!

您能夠微信搜索 【木子雷】 公衆號,大量Java學習乾貨文章,您能夠來瞧一瞧喲!

相關文章
相關標籤/搜索