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

歡迎訪問 生活随笔!

生活随笔

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

数据库

做 SQL 性能优化真是让人干瞪眼

發布時間:2024/9/15 数据库 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 做 SQL 性能优化真是让人干瞪眼 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

很多大數據計算都是用 SQL 實現的,跑得慢時就要去優化 SQL,但常常碰到讓人干瞪眼的情況。

比如,存儲過程中有三條大概形如這樣的語句執行得很慢:

select a,b,sum(x) from T group by a,b where …; select c,d,max(y) from T group by c,d where …; select a,c,avg(y),min(z) from T group by a,c where …;

這里的 T 是個有數億行的巨大表,要分別按三種方式分組,分組的結果集都不大。

分組運算要遍歷數據表,這三句 SQL 就要把這個大表遍歷三次,對數億行數據遍歷一次的時間就不短,何況三遍。

這種分組運算中,相對于遍歷硬盤的時間,CPU 計算時間幾乎可以忽略。如果可以在一次遍歷中把多種分組匯總都計算出來,雖然 CPU 計算量并沒有變少,但能大幅減少硬盤讀取數據量,就能成倍提速了。

如果 SQL 支持類似這樣的語法:

from T -- 數據來自 T 表select a,b,sum(x) group by a,b where … -- 遍歷中的第一種分組select c,d,max(y) group by c,d where … -- 遍歷中的第二種分組select a,c,avg(y),min(z) group by a,c where …; -- 遍歷中的第三種分組

能一次返回多個結果集,那就可以大幅提高性能了。


可惜, SQL 沒有這種語法,寫不出這樣的語句,只能用個變通的辦法,就是用 group a,b,c,d 的寫法先算出更細致的分組結果集,但要先存成一個臨時表,才能進一步用 SQL 計算出目標結果。SQL 大致如下:

create table T_temp as select a,b,c,d,sum(case when … then x else 0 end) sumx,max(case when … then y else null end) maxy,sum(case when … then y else 0 end) sumy,count(case when … then 1 else null end) county,min(case when … then z else null end) minz group by a,b,c,d; select a,b,sum(sumx) from T_temp group by a,b where …; select c,d,max(maxy) from T_temp group by c,d where …; select a,c,sum(sumy)/sum(county),min(minz) from T_temp group by a,c where …;

這樣只要遍歷一次了,但要把不同的 WHERE 條件轉到前面的 case when 里,代碼復雜很多,也會加大計算量。而且,計算臨時表時分組字段的個數變得很多,結果集就有可能很大,最后還對這個臨時表做多次遍歷,計算性能也快不了。大結果集分組計算還要硬盤緩存,本身性能也很差。

還可以用存儲過程的數據庫游標把數據一條一條 fetch 出來計算,但這要全自己實現一遍 WHERE 和 GROUP 的動作了,寫起來太繁瑣不說,數據庫游標遍歷數據的性能只會更差!

只能干瞪眼!


TopN 運算同樣會遇到這種無奈。舉個例子,用 Oracle 的 SQL 寫 top5 大致是這樣的:

select * from (select x from T order by x desc) where rownum<=5

表 T 有 10 億條數據,從 SQL 語句來看,是將全部數據大排序后取出前 5 名,剩下的排序結果就沒用了!大排序成本很高,數據量很大內存裝不下,會出現多次硬盤數據倒換,計算性能會非常差!

避免大排序并不難,在內存中保持一個 5 條記錄的小集合,遍歷數據時,將已經計算過的數據前 5 名保存在這個小集合中,取到的新數據如果比當前的第 5 名大,則插入進去并丟掉現在的第 5 名,如果比當前的第 5 名要小,則不做動作。這樣做,只要對 10 億條數據遍歷一次即可,而且內存占用很小,運算性能會大幅提升。

這種算法本質上是把 TopN 也看作與求和、計數一樣的聚合運算了,只不過返回的是集合而不是單值。SQL 要是能寫成這樣:select top(x,5) from T 就能避免大排序了。

然而非常遺憾,SQL 沒有顯式的集合數據類型,聚合函數只能返回單值,寫不出這種語句!


不過好在全集的 TopN 比較簡單,雖然 SQL 寫成那樣,數據庫卻通常會在工程上做優化,采用上述方法而避免大排序。所以 Oracle 算那條 SQL 并不慢。

但是,如果 TopN 的情況復雜了,用到子查詢中或者和 JOIN 混到一起的時候,優化引擎通常就不管用了。比如要在分組后計算每組的 TopN,用 SQL 寫出來都有點困難。Oracle 的 SQL 寫出來是這樣:

select * from(select y,x,row_number() over (partition by y order by x desc) rn from T) where rn<=5

這時候,數據庫的優化引擎就暈了,不會再采用上面說的把 TopN 理解成聚合運算的辦法。只能去做排序了,結果運算速度陡降!

假如 SQL 的分組 TopN 能這樣寫:

select y,top(x,5) from T group by y

把 top 看成和 sum 一樣的聚合函數,這不僅更易讀,而且也很容易高速運算。

可惜,不行。

還是干瞪眼!


關聯計算也是很常見的情況。以訂單和多個表關聯后做過濾計算為例,SQL 大體是這個樣子:

select o.oid,o.orderdate,o.amountfrom orders oleft join city ci on o.cityid = ci.cityidleft join shipper sh on o.shid=sh.shidleft join employee e on o.eid=e.eidleft join supplier su on o.suid=su.suidwhere ci.state='New York'and e.title = 'manager'and ...

訂單表有幾千萬數據,城市、運貨商、雇員、供應商等表數據量都不大。過濾條件字段可能會來自于這些表,而且是前端傳參數到后臺的,會動態變化。

SQL 一般采用 HASH JOIN 算法實現這些關聯,要計算 HASH 值并做比較。每次只能解析一個 JOIN,有 N 個 JOIN 要執行 N 遍動作,每次關聯后都需要保持中間結果供下一輪使用,計算過程復雜,數據也會被遍歷多次,計算性能不好。


通常,這些關聯的代碼表都很小,可以先讀入內存。如果將訂單表中的各個關聯字段預先做序號化處理,比如將雇員編號字段值轉換為對應雇員表記錄的序號。那么計算時,就可以用雇員編號字段值(也就是雇員表序號),直接取內存中雇員表對應位置的記錄,性能比 HASH JOIN 快很多,而且只需將訂單表遍歷一次即可,速度提升會非常明顯!

也就是能把 SQL 寫成下面的樣子:

select o.oid,o.orderdate,o.amountfrom orders oleft join city c on o.cid = c.# -- 訂單表的城市編號通過序號 #關聯城市表left join shipper sh on o.shid=sh.# -- 訂單表運貨商號通過序號 #關聯運貨商表left join employee e on o.eid=e.# -- 訂單表的雇員編號通過序號 #關聯雇員表left join supplier su on o.suid=su.# -- 訂單表供應商號通過序號 #關聯供應商表where ci.state='New York'and e.title = 'manager'and ...

可惜的是,SQL 使用了無序集合概念,即使這些編號已經序號化了,數據庫也無法利用這個特點,不能在對應的關聯表這些無序集合上使用序號快速定位的機制,只能使用索引查找,而且數據庫并不知道編號被序號化了,仍然會去計算 HASH 值和比對,性能還是很差!

有好辦法也實施不了,只能再次干瞪眼!


還有高并發帳戶查詢,這個運算倒是很簡單:

select id,amt,tdate,… from Twhere id='10100'and tdate>= to_date('2021-01-10', 'yyyy-MM-dd')and tdate<to_date('2021-01-25', 'yyyy-MM-dd')and …

在 T 表的幾億條歷史數據中,快速找到某個帳戶的幾條到幾千條明細,SQL 寫出來并不復雜,難點是大并發時響應速度要達到秒級甚至更快。為了提高查詢響應速度,一般都會對 T 表的 id 字段建索引:

create index index_T_1 on T(id)

在數據庫中,用索引查找單個帳戶的速度很快,但并發很多時就會明顯變慢。原因還是上面提到的 SQL 無序理論基礎,總數據量很大,無法全讀入內存,而數據庫不能保證同一帳戶的數據在物理上是連續存放的。硬盤有最小讀取單位,在讀不連續數據時,會取出很多無關內容,查詢就會變慢。高并發訪問的每個查詢都慢一點,總體性能就會很差了。在非常重視體驗的當下,誰敢讓用戶等待十秒以上?!

容易想到的辦法是,把幾億數據預先按照帳戶排序,保證同一帳戶的數據連續存儲,查詢時從硬盤上讀出的數據塊幾乎都是目標值,性能就會得到大幅提升。

但是,采用 SQL 體系的關系數據庫并沒有這個意識,不會強制保證數據存儲的物理次序!這個問題不是 SQL 語法造成的,但也和 SQL 的理論基礎相關,在關系數據庫中還是沒法實現這些算法。


那咋辦?只能干瞪眼嗎?

不能再用 SQL 和關系數據庫了,要使用別的計算引擎。

開源的集算器 SPL 基于創新的理論基礎,支持更多的數據類型和運算,能夠描述上述場景中的新算法。用簡單便捷的 SPL 寫代碼,在短時間內能大幅提高計算性能!


上面這些問題用 SPL 寫出來的代碼樣例如下:

一次遍歷計算多種分組


AB
1A1=file("T.ctx").open().cursor(a,b,c,d,x,y,z)
2cursor A1=A2.select(…).groups(a,b;sum(x))
3
//定義遍歷中的第一種過濾、分組
4cursor=A4.select(…).groups(c,d;max(y))
5
//定義遍歷中的第二種過濾、分組
6cursor=A6.select(…).groupx(a,c;avg(y),min(z))
7
//定義遍歷中的第三種過濾、分組
8//定義結束,開始計算三種方式的過濾、分組

用聚合的方式計算 Top5

全集 Top5(多線程并行計算)


A
1=file("T.ctx").open()
2=A1.cursor@m(x).total(top(-5,x), ? top(5,x))
3// top(-5,x)計算出 x 最大的前 5 名,top(5,x) 是 x 最小的前 5 名。

分組 Top5(多線程并行計算)


A
1=file("T.ctx").open()
2=A1.cursor@m(x,y).groups(y;top(-5,x), ? top(5,x))

用序號做關聯的 SPL 代碼:

系統初始化


A
2>env(city,file("city.btx").import@b()),env(employee,file("employee.btx").import@b()),...
3//系統初始化時,幾個小表讀入內存

查詢


A
1=file("orders.ctx").open().cursor(cid,eid,…).switch(cid,city:#;eid,employee:#;…)
2=A1.select(cid.state='New ? York' && eid.title=="manager"…)
3//先序號關聯,再引用關聯表字段寫過濾條件

高并發帳戶查詢的 SPL 代碼:

數據預處理,有序存儲


AB
1=file("T-original.ctx").open().cursor(id,tdate,amt,…)
2=A1.sortx(id)=file("T.ctx")
3=B2.create@r(#id,tdate,amt,…).append@i(A2)
4=B2.open().index(index_id;id)
5//將原數據排序后,另存為新表,并為帳號建立索引

帳戶查詢


AB
1=T.icursor(;id==10100 ? && tdate>=date("2021-01-10") && tdate<date("2021-01-25") ? && …,index_id).fetch()
2//查詢代碼非常簡單

除了這些簡單例子,SPL 還能實現更多高性能算法,比如有序歸并實現訂單和明細之間的關聯、預關聯技術實現多維分析中的多層維表關聯、位存儲技術實現上千個標簽統計、布爾集合技術實現多個枚舉值過濾條件的查詢提速、時序分組技術實現復雜的漏斗分析等等。

正在為 SQL 性能優化頭疼的小伙伴們,來和我們一起探討吧:

《慢得受不了的查詢跑批》

識別二維碼打開該頁面

重磅!開源SPL交流群成立了

簡單好用的SPL開源啦!

為了給感興趣的小伙伴們提供一個相互交流的平臺,

特地開通了交流群(群完全免費,不廣告不賣課)

需要進群的朋友,可長按掃描下方二維碼

本文感興趣的朋友,請轉到閱讀原文去收藏 ^_^

總結

以上是生活随笔為你收集整理的做 SQL 性能优化真是让人干瞪眼的全部內容,希望文章能夠幫你解決所遇到的問題。

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