SQL優化:慎用標量子查詢,改用left join提升查詢效率


一、項目實例問題

1、問題背景

  某個需求做了之后,注意到有個接口返回數據特別慢,特別是使用下面的 3 個字段排序時就直接卡死,肯定是 sql 性能寫法問題,所以決定研究一下查看究竟。

  其實需求挺簡單,有幾個字段排序,前端需要展示那些字段,然后之前的后端寫的 sql 如下,僅提取主要問題點,其實就是需要拿到 starCount、commentCount、totalReward 用來前端展示,而這三個字段呢,又需要從另外三個表里去分別計數,所以不考慮 sql 性能優化的話,就很容易想到了這種錯誤的寫法。

k.tags, v.views, (select coalesce(count(rid),0) from table1 where aa = 'kl' and rid = k.id) starCount, (select coalesce(count(id),0) from table2 where aa = 'kl' and rid = k.id::varchar) commentCount, (select coalesce(count(id),0) from table3 where aa = 'kl' and rid = k.id::varchar) totalReward from table4 k left join table5 v on k.id = v.kl_id

2、優化方案

  主要優化后的 sql 如下:使用 left join 替代標量子查詢

k.tags, v.views, coalesce (s.count,0) starCount, coalesce (m.count,0) commentCount, coalesce (p.count,0) totalReward from table4 k left join table5 v on k.id = v.kl_id left join (select rid,count(rid) from table1 where aa = 'kl' group by rid) s on k.id = s.rid left join (select rid,count(rid) from table2 where aa = 'kl' group by rid) m on m.rid = k.id::varchar 
left join (select rid,count(rid) from table3 where aa = 'kl' group by rid) p on p.rid = k.id::varchar 
order by totalReward desc

  優化前比如我有10萬篇文章,那就要執行10萬次(select coalesce(count(rid),0) from table1 where aa = 'kl' and rid = k.id) starCount。

  優化后,僅需一次兩表之間的匹配,即使是全表也是1次匹配,分組后也是1次匹配,數據量少是會提高效率但是頂多0.00幾的提高,關鍵是left join。提高了n倍之前order by直接執行失敗time out,優化之后是0.4s左右。

3、分析原因 - 為什么會想到錯誤的寫法

  以前我確實很少看到第一種那種標量子查詢的寫法,所以很納悶為什么會這樣寫。一般不都是用 left join 嗎?后來了解到可能情況不一樣:

(1)平常我們使用多表關聯都會想到 left join,因為我們會用到關聯表的多個字段或某個字段,需要將其查出來,所以很容易想到 left join。

(2)而這種情況只需要使用其他表的一個計數的值,沒有使用表里的任何字段,沒學過 sql 優化的,很難想到用 left join。

  而很多人使用標量子查詢而不自知執行效率差,往往是因為數據量比較小,並沒有發現不妥,一旦數據量大了之后,就會越來越慢。只有經過大數據量的考驗,才能寫出來優質的 sql。

  墨天輪平台有個標量子查詢的優化案例可以看下:Oracle 標量子查詢優化案例  —  https://www.modb.pro/db/41963

二、標量子查詢的問題

  標量子查詢、聚合標量子查詢、行轉列標量子查詢、帶top的標量子查詢如何轉成left join。

  之所以要轉換,主要是因為標量子查詢雖然寫法上比較直觀,容易理解,不用想就知道怎么寫,但是存在:代碼重復、多次訪問同一個表 問題,所以效率比較低。

1、標量子查詢的模板

  按標量子查詢方式,寫出來的sql,都類似下面的代碼:

select tb.col1, tb.col2, --下面的代碼是重復的,表和連接條件都類似,只是最后顯示的字段不同
       (select x1 from t where t.id = tb.id) as x1, (select x2 from t where t.id = tb.id) as x2, (select x3 from t where t.id = tb.id) as x3, (select x4 from t where t.id = tb.id) as x4, ... from tb

  可以看到,其中x1、x2、x3、x4等列,大部分代碼都是重復的。當然,代碼重復本身並沒有太大的問題,最多就是復制粘貼,拷貝多次,然后把字段名改改,就行了。

2、標量子查詢的執行過程

  上面的sql經過sql server的優化,生成執行計划,執行過程類似如下的過程:

(1)從tb表中取一條數據,用其中的id值,第1次和t表中的id值進行比較,如果相等,就返回t表的x1字段的值。

(2)從tb表中取一條數據,用其中的id值,第2次和t表中的id值進行比較,如果相等,就返回t表的x2字段的值。

(3)從tb表中取一條數據,用其中的id值,第3次和t表中的id值進行比較,如果相等,就返回t表的x3字段的值。

(4)從tb表中取一條數據,用其中的id值,第4次和t表中的id值進行比較,如果相等,就返回t表的x4字段的值。

(5)按照上述過程遍歷整個tb表的每一條數據。

  從上面的過程可以看出,一共訪問了t表4次,做了很多無用功。

  如果改成left join的方式,只需要訪問1次t表,少訪問3次,效率提高不少。

  所以,要盡量少用標量子查詢的寫法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM