再次声明: 并不是所有场景都需要(或者适合)用rust来写的,绝大部分操作数据库的功能和计算,用SQL就已经足够了!本系列中,所有的案例,仅用于说明pgrx的能力,而并非是说这样做比用SQL更合适。反之:对于操作数据库本身的部分,大部分能用SQL来实现的东西,都比做一个扩展开发要更加合适。——如果哪位大神写Rust走火入魔,说啥数据库功能都要用Rust来扩展实现的,不报我的名字,打成半死就行,报我的名字,请打成八成死。![](http://image109.360doc.com/DownloadImg/2023/04/2514/264949860_1_20230425020904944_wm.jpeg)
单值输入与输出函数这里的单值,是针对序列这种多值类型而言的。 SQL做为第四代语言,与第三代语言最大的区别,就是尽量舍弃了所谓的计算机思维,即for和if。例如我们在高级语言里面对一批数据要进行查询或者筛选,必须是按照计算机的思维方式来进行:例如老板说,我们准备找出大于35岁的同学,以向社会进行输送,那么程序员要实现这个想法,就得这样思考和实现问题:对整个数据集进行一个个的迭代,然后一个个的比较,如果满足条件,就进行社会输出……for row in dataset: if row['age'] > 35: row["结果"] = "输送社会" else: row["结果"] = "在用几年" 但是SQL里面,要实现这个功能,则不会有for和if这类语句:select * from dataset where age > 35 正如高级语言要执行,先要被编译器编译成汇编,然后再编译成机器语言再进行执行意义,SQL要执行,实际上也要别数据库引擎编译成汇编指令和机器语言,那么依然可以是可以解读为for和if的。#[pg_extern] fn age_add(age:i32) -> i32 { age +1 }
![](http://image109.360doc.com/DownloadImg/2023/04/2514/264949860_2_202304250209056_wm.png) 那么在实际使用中,比如要作用于数据库表格上的话,是什么样子呢?![](http://image109.360doc.com/DownloadImg/2023/04/2514/264949860_3_20230425020905100_wm.png) 如果我们把自定义的函数作用在这张表上的时候,就是这样的:![](http://image109.360doc.com/DownloadImg/2023/04/2514/264949860_4_20230425020905195_wm.png) 可以看见,相当于对于表emps进行逐行的迭代计算,然后得到了结果。当然,这种功能肯定没必要用扩展这种牛刀杀鸡的做法,直接用SQL就可以了:![](http://image109.360doc.com/DownloadImg/2023/04/2514/264949860_5_20230425020905334_wm.png)
![](http://image109.360doc.com/DownloadImg/2023/04/2514/264949860_6_20230425020905459_wm.png) 这是一个员工信息表有五个字段,分别id,名称、加入公司的时间、生日和工资,一共是20万条记录:我用Python的faker库生成的,当然没来得及去关注入职时间和生日之间的相关关系,里面肯定有没满18岁就参加工作的问题……就不要在意这些细节了。![](http://image109.360doc.com/DownloadImg/2023/04/2514/264949860_7_20230425020905631.png) 基数为2,也就是两个月工资,如果在公司超过10年,每年加1.5个点,如果不满10年,则每年1个点,不满一年的按基准算,最后用系数乘以工资,得到最后的奖金。这个场景在业务编码里面经常见到,虽然很简单,但是包含了多条件判断、时间计算和数学计算等多种计算模型,当然……用SQL本身就很容实现,如下所示: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;
结果如下: ![](http://image109.360doc.com/DownloadImg/2023/04/2514/264949860_8_20230425020905803_wm.png) 那么我们在扩展函数里面写怎么做呢?(再次强调,仅为说明能力,绝对不是建议大家这种功能小功能也动用扩展函数来牛刀杀鸡)#[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 }
![](http://image109.360doc.com/DownloadImg/2023/04/2514/264949860_9_202304250209066_wm.png)
性能对比我们来具体对比一下,使用SQL原生方式和与扩展函数两种方式,在PG上面的执行效率,我们采用EXPLAIN ANALYZE的方式来测试效率: 为了复用建立出来的20万条记录的表格,所以我们需要修改一下方法:把输入参数从i32改成i64——rust是一种强类型的语言,所以数据库中的integer和bigint是无法通用。当然,我们也可以用泛型来做,不过既然本教程针对的是初学者,这里我就不用了,以免增加学习负担。#[pg_extern] fn age_add(age:i64) -> i64 { age +1 } 结果如下: ![](http://image109.360doc.com/DownloadImg/2023/04/2514/264949860_10_20230425020906225_wm.png) 我们发现,对于20万条数据,用SQL执行加1操作,仅用了23ms,而采用扩展函数,则需要用37ms。接下去,我们分别测试更新和插入,用两种方法,生成一张新的表格,然后在做一次更新,分别来看看性能:CREATE AS SELECT的性能: ![](http://pubimage.360doc.com/wz/default.gif) ![](http://pubimage.360doc.com/wz/default.gif) 然后看看UPDATE的性能: ![](http://pubimage.360doc.com/wz/default.gif) ![](http://pubimage.360doc.com/wz/default.gif)
查询 ![](http://pubimage.360doc.com/wz/default.gif) 创建:
![](http://pubimage.360doc.com/wz/default.gif) (原生SQL的子查询模式更新,实在太慢了,20万条没有执行成功,所以我把数量缩减到了1000条,也有可能是我SQL语句没写对……因为以前没有搞过子查询模式的更新这种东西,如果哪位大神写过,可以联系我……) ![](http://pubimage.360doc.com/wz/default.gif) 而让我感到震惊的是,使用扩展函数编写的更新,对于20万条数据,整体执行的时间如下:![](http://pubimage.360doc.com/wz/default.gif)
对比如下: ![](http://pubimage.360doc.com/wz/default.gif)
结论简单计算和查询,SQL语言比扩展模式性能更好。 复杂计算和查询,随着数据量的变大,Rust扩展模式越发显示出优势,可能是因为在SQL里面也需要调用底层语言编写的函数,而导致转换间的性能损失吧。 如果有复杂计算且更新的需求,扩展函数的性能比原生SQL要好太多太多……可能并非是性能,而是运行机制的问题,此结论因为虾神SQL能力不行,所以不可靠。 不管是原生SQL模式还是扩展函数模式,一定都比DBC(Database Connectivity)模式要强很多很多……
待续未完。
|