笔者之前对postgresql有一些泛泛的了解。前几天从某篇文章看到一则信息说,mysql的使用率接近停滞,而postgresql 使用如日中天,笔者对此有些好奇,这次稍微实际深入了解了一些内容,
总的来看,postgresql的文档比mysql规整完善,更学术化,有一些自己的特色,比如内置支持空间信息管理等,有官方正规的管理工具。但在使用的过程中碰到的一些问题不吐不快,网上很难查到有用的资料,初次接触的开发人员很容易踩坑。
关于脚本语言
PostgreSql官方的文档里说,编写脚本语言可以用 C, sql, plpgsql, python等, 当选择sql时, 如果你使用了if控制语句,编译存储过程会报错:
SQL Error [42601]: 错误: 语法错误 在 "if" 或附近的
左看右看,这个简单的脚本,就一个普通的IF语句,怎么会有语法错误呢?原来,原因就在这里,脚本里存在控制语句 如 if then 等时,必须选择脚本的 language 为plpgsql, 并且函数体头尾要加上
begin
end
存储过程里的脚本,加入控制语句很常见的事情,这样突然冒出语法错误,让人丈二和尚摸不着头脑,e而在postgresql自带的管理客户端和dbeaver等工具里,创建存储过程/函数时,默认的脚本语言类型为
sql
而不是
plpgsql
,是不支持if else等控制结构的。
这让初次接触的开发人员会很懵圈。
存储过程不支持返回数据集
早先 postgresql 只有函数(Function),并不支持存储过程(Procedure),可能是设计者认为函数足够用了吧,后来在版本11加入了存储过程, 说是这样可以应付事务,但是并不支持存储过程返回数据集!
如果你在存储过程里select 了数据,然后直接在客户端SQL工具窗口里调用 call ,它会报一个错:
call
proc1(1);
SQL Error [42601]: 错误: 对于结果数据,查询没有目标
Hint: 如果您想要放弃SELECT语句的结果,请使用PERFORM.
意思是你没在脚本中声明数据集的承接对象。而实际上postgresql的存储过程既不能声明返回值,也无法接住内置的SELECT脚本输出的数据。
只有用函数(Function)才能返回数据集,并且数据集的使用非常繁琐,必须要声明类型。 是不是太刻板了?
CREATE OR REPLACE FUNCTION
get_customer()
RETURNS SETOF
customer
AS
$body$
SELECT
*
from
customer;
$body$
调用方法:
select
*
from
get_customer()
看起来中规中矩,然而上面的例子是刚好输出内容与某个表结构一样的情况,customer就是表名,而很多时候,查询都是多表联合查询,这时就比较麻烦了,有两种方式,
一种做法是事先定义type, 例如
create type
my_customer(customerid
int
,firstname
varchar
,... supportrepname
varchar
)
然后函数返回此
type
:上面的
returns
...改为
returns setof
my_customer
函数体内容:
declare
rec my_customer%
rowtype
begin
for
rec
in select
a.customerid,a.firstname,....b.lastname from customer a
left join
employee b
on
a.supportrepid=b.employeeid
return next
rec;
end loop;
return;
end;
第二种做法是不事先声明类型type, 返回record:
上面的
returns
...改为
returns setof record
函数体内容:
declare
v_rec
record
;
begin
for
v_rec
in select
...
loop
return next
v_rec;
end loop;
然后你在调用时候,需要定义一个type结构接住数据.
select * from
get_customer(...)
as
my_customer(customerid int....)
还是要定义个type表示一条数据,实际上还是换汤不换药,这给开发人员调试脚本增加了很多额外的工作量。而多表查询的情况是司空见惯的,上面的用法实在让人烦心,也有另外一种方法,返回一个游标
refcursor
CREATE OR REPLACE FUNCTION
function1 ()
RETURNS refcursor AS
$body$
DECLARE
result
refcursor
;
BEGIN
open result for select * from
table1,table2; --你可以任意选择你想要返回的表和字段
return
result;
END;
$body$
这样稍微好一些,但还有其它问题呢,我们知道,对于其它数据库,JDBC在使用存储过程时都可以统一用
CallableStatement
stmt = conn.prepareCall(
"{call proc1(?,?) }"
);
而postgresql只能用函数返回数据集,调用方式只能像这样了:
CallableStatement
stmt = conn.prepareCall(
"{ ? = call function1(?,?) }"
);
这与其它的数据库有着显著区别,其它的数据库,如Oracle,Mysql, MSSQL,SAP HAHA,DB2等等都支持过程返回数据,且在客户端工具的SQL工具窗口中就能直接看到结果,多个select语句就会在客户端的多个输出窗口tab页展示,
用JDBC调用可以直接按照顺序依次引用SP的数据集。 而到了postgresql这里,困难重重非常繁琐。
顺便说点题外话:从前在Hibernate流行时,很多人强调Hibernate的一个优点就是,使用它可以屏蔽数据库的差异,可以无缝切换数据库……对于这种说法,你可以一笑置之。
实际上,对于规模稍微大一点的公司和项目来说,切换数据库都是一件很严肃、超级大的动作,必须分配专门的资源集中处理,远远不是靠某一个ORM产品就能轻松实现数据库切换的。
ORM产品除了能对付基本的CRUD之外,其它稍微复杂的操作都比较吃力,必须借助数据库本身的功能, 这就是为什么Hibernate逐渐式微,大家慢慢都用MyBatis的原因,因为MyBatis使用原生的数据库脚本更方便。实际上,由于关系数据库与OO设计语言的结构性矛盾,笔者认为Mybatis也算是一个鸡肋,不用任何ORM才是最好的方案。 这样看来,数据库的选型就非常重要了。
过程/函数支持重载
在使用的过程中,postgresql与其它数据库重大区别之一是 postgresql过程/函数居然支持重载,也就是说,同一个函数名,参数不同,就成为了不同的函数!
postgresql支持函数重载,听起来高大上,然而笔者个人认为这样设计实际上弊大于利,
毕竟、不像JAVA/C++等本身就是面向对象的语言,SQL是结构化查询语言,提供重载功能,这既增加了数据库设计的复杂性,也让开发人员使用时更加繁琐,
一不留神使用时就会冒出各种各样的错误。
下面是我在通用客户端DB工具dbeaver上,展示的实现同一个功能的一些函数过程截图:
看到了没,由于支持重载postgresql 的函数/过程必须列出参数,如果万一有个同名函数,参数有很多个,一眼望去你根本不知道谁是谁了,
也许有人说,设计有这个功能,你不用不就行了吗?
理论上是这样,但是实际上你不得不被动的用。
如果你在调用某个过程函数,仅仅是参数有些不对,它会告诉你,找不到这个函数,而不是参数不对,是不是有些误导?
如果你用脚本删除一个函数/过程,删除的时候必须列明它的所有参数类型,是不是想想都头大?
在开发的过程中修改参数名称类型个数等是很常见的事情,但是在postgresql里如果你打开这个函数然后修改/调整某个过程或函数的名称类型等,然后保存,它会认为你是要
重载
,直接给你存成了另一个函数了,原来的就放那不动了!是不是觉得很无厘头?
如果你在修改一个函数,改动返回值类型,再保存,它会给你报一个错!说是不能修改函数的返回值!
那啥, 对于OO程序语言比如Java/C++等来说,重载确实是不允许两个重载方法返回不同值,开发人员觉得很正常,但是你这是结构化的SQL脚本啊,不同的功能通过函数名区别就行了,犯不着在参数上区别。SQL脚本支持重载给除了给自己挖坑外毫无益处,开发人员为这一点会碰到各种各样的无厘头问题,相当的无语。。。
虽然postgresql理论上做到了逻辑自洽,相比MySQL有一些自己的功能特色,但由于设计时考虑欠周,过分追求学院派风格,一些常用的功能用起来不是很方便,比较繁琐。
这些缺陷是开始时的设计问题,由于要考虑兼容性,postgresql后面的版本大概率也不会对这些再做优化处理了。
因此大的管理系统并不太适合选择postgresql用于生产环境。 否则后续开发维护扩展都会比较麻烦。
以上只是本人针对已了解到的信息发表的一些浅见,如果有不同见解,欢迎不吝指正。