前言
再次聲明:
并不是所有場景都需要(或者適合)用rust來寫的,絕大部分操作數(shù)據(jù)庫的功能和計算,用SQL就已經(jīng)足夠了!
本系列中,所有的案例,僅用于說明pgrx的能力,而并非是說這樣做比用SQL更合適。反之:對于操作數(shù)據(jù)庫本身的部分,大部分能用SQL來實現(xiàn)的東西,都比做一個擴展開發(fā)要更加合適。
——如果哪位大神寫Rust走火入魔,說啥數(shù)據(jù)庫功能都要用Rust來擴展實現(xiàn)的,不報我的名字,你們打成半死就行,報我的名字,請打成八成死。
單值輸入與輸出函數(shù)
這里的單值,是針對序列這種多值類型而言的。
SQL做為第四代語言,與第三代語言最大的區(qū)別,就是盡量舍棄了所謂的計算機思維,即for和if。
例如我們在高級語言里面對一批數(shù)據(jù)要進行查詢或者篩選,必須是按照計算機的思維方式來進行:
例如老板說,我們準備找出大于35歲的同學,以向社會進行輸送,那么程序員要實現(xiàn)這個想法,就得這樣思考和實現(xiàn)問題: 對整個數(shù)據(jù)集進行一個個的迭代,然后一個個的比較,如果滿足條件,就進行社會輸出……
for row in dataset:
if row['age'] > 35:
row["結(jié)果"] = "輸送社會"
else:
row["結(jié)果"] = "在用幾年"
但是SQL里面,要實現(xiàn)這個功能,則不會有for和if這類語句:
select * from dataset where age > 35
有正如高級語言要執(zhí)行,先要被編譯器編譯成匯編,然后再編譯成機器語言再進行執(zhí)行意義,SQL要執(zhí)行,實際上也要別數(shù)據(jù)庫引擎編譯成匯編指令和機器語言,那么依然可以是可以解讀為for和if的。
例如,我們寫了這樣一個數(shù)據(jù)庫擴展:
#[pg_extern]
fn age_add(age:i32) -> i32 {
age +1
}
來看看效果,非常直觀:
那么在實際使用中,比如要作用于數(shù)據(jù)庫表格上的話,是什么樣子呢?
先有這樣一張表:
如果我們把自定義的函數(shù)作用在這張表上的時候,就是這樣的:
可以看見,相當于對于表emps進行逐行的迭代計算,然后得到了結(jié)果。
當然,這種功能肯定沒必要用擴展這種牛刀殺雞的做法,直接用SQL就可以了:
下面我們來做一個稍微復雜點的場景:
有如下這樣一張表:
這是一個員工信息表有五個字段,分別id,名稱、加入公司的時間、生日和工資,一共是20萬條記錄:我用Python的faker庫生成的,當然沒來得及去關注入職時間和生日之間的相關關系,里面肯定有沒滿18歲就參加工作的問題……就不要在意這些細節(jié)了。
現(xiàn)在我們要計算一下年終獎金的系數(shù),規(guī)則如下:
基數(shù)為2,也就是兩個月工資,如果在公司超過10年,每年加1.5個點,如果不滿10年,則每年1個點,不滿一年的按基準算,最后用系數(shù)乘以工資,得到最后的獎金。
這個場景在業(yè)務編碼里面經(jīng)常見到,雖然很簡單,但是包含了多條件判斷、時間計算和數(shù)學計算等多種計算模型,當然……用SQL本身就很容實現(xiàn),如下所示:
WITH a AS (
SELECT *,
CASE
WHEN date_part('year',age(now(),indate)) >= 10
THEN 2 + date_part('year',age(now(),indate))* 0.015
WHEN date_part('year',age(now(),indate)) <= 1
THEN 2
ELSE 2 + date_part('year',age(now(),indate))* 0.01
END as xs
FROM tab_emps )
SELECT * ,pay*xs FROM a
LIMIT 10;
結(jié)果如下:
那么我們在擴展函數(shù)里面寫怎么做呢? (再次強調(diào),僅為說明能力,絕對不是建議大家這種功能小功能也動用擴展函數(shù)來牛刀殺雞)
代碼如下:
#[pg_extern]
fn cal_bonus(indate:pgrx::Date,pay:i64) -> f32{
let now: DateTime<Local> = Local::now();
let now_epoch = now.timestamp()/60/60/24;
let x = (now_epoch as i32 - indate.to_unix_epoch_days()) / 365 ;
let mut xs:f32=0.0;
if x >=10{
xs = 2.0 + x as f32 * 0.015;
}
else if x <=1 {
xs = 2.0;
}
else{
xs = 2.0 + x as f32 * 0.01;
}
xs * pay as f32
}
結(jié)果如下:
可以看見二者的結(jié)果是完全一樣的。
性能對比
我們來具體對比一下,使用SQL原生方式和與擴展函數(shù)兩種方式,在PG上面的執(zhí)行效率,我們采用EXPLAIN ANALYZE的方式來測試效率:
- 對于加1方法的測試:
為了復用建立出來的20萬條記錄的表格,所以我們需要修改一下方法: 把輸入?yún)?shù)從i32改成i64——rust是一種強類型的語言,所以數(shù)據(jù)庫中的integer和bigint是無法通用。
當然,我們也可以用泛型來做,不過既然本教程針對的是初學者,這里我就不用了,以免增加學習負擔。
#[pg_extern] fn age_add(age:i64) -> i64 { age +1 }
結(jié)果如下:
我們發(fā)現(xiàn),對于20萬條數(shù)據(jù),用SQL執(zhí)行加1操作,僅用了23ms,而采用擴展函數(shù),則需要用37ms。
接下去,我們分別測試更新和插入,用兩種方法,生成一張新的表格,然后在做一次更新,分別來看看性能:
CREATE AS SELECT的性能:
依然是原生SQL性能更好
然后看看UPDATE的性能:
三個測試的結(jié)果如下:
然后我們再來測試一下稍微復雜點的系數(shù)與獎金計算:
查詢
創(chuàng)建:
更新 (原生SQL的子查詢模式更新,實在太慢了,20萬條沒有執(zhí)行成功,所以我把數(shù)量縮減到了1000條,也有可能是我SQL語句沒寫對……因為以前沒有搞過子查詢模式的更新這種東西,如果哪位大神寫過,可以聯(lián)系我……)
而讓我感到震驚的是,使用擴展函數(shù)編寫的更新,對于20萬條數(shù)據(jù),整體執(zhí)行的時間如下:
對比如下:
文章來源:http://www.zghlxwxcb.cn/news/detail-428778.html
結(jié)論
- 簡單計算和查詢,SQL語言比擴展模式性能更好。
- 復雜計算和查詢,隨著數(shù)據(jù)量的變大,Rust擴展模式越發(fā)顯示出優(yōu)勢,可能是因為在SQL里面也需要調(diào)用底層語言編寫的函數(shù),而導致轉(zhuǎn)換間的性能損失吧。
- 如果有復雜計算且更新的需求,擴展函數(shù)的性能比原生SQL要好太多太多……可能并非是性能,而是運行機制的問題,此結(jié)論因為蝦神SQL能力不行,所以不可靠。
- 不管是原生SQL模式還是擴展函數(shù)模式,一定都比DBC(Database Connectivity)模式要強很多很多……
待續(xù)未完。文章來源地址http://www.zghlxwxcb.cn/news/detail-428778.html
到了這里,關于[pgrx開發(fā)postgresql數(shù)據(jù)庫擴展]4.基本計算函數(shù)的編寫與性能對比的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!