《PostgreSQL服务器编程》一一2.6 过程化语言

本节书摘来自华章计算机《PostgreSQL服务器编程》一书中的第2章,第2.6节,作者:(美)Hannu Krosing, Jim Mlodgenski, Kirk Roybal 著
,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.6 过程化语言

SQL Server允许你使用任何产生CLR的语言来创建DLL。这些DLL必须在启动的时候被加载到服务器里面。为了在运行时间创建一个程序,并且使它立即可用,唯一的选择就是内建的SQL方言——Transact SQL(TSQL)。
MySQL有一个叫做插件的功能。其中一种合法的插件类型是过程化语言。几种语言完成加工后,借由插件系统可以和MySQL一起工作。这些语言包括了最流行的几种语言,比如PHP、Python等。这些函数不能被用在存储过程与触发器中,但是它们可以被普通SQL语言唤醒。接下来,你就会被内建的SQL没完没了地纠缠。
PostgreSQL完全支持额外的过程化语言,这种语言可以被用来在数据库中创建任何合法的实体,而这些实体可以使用PL/pgSQL来创建。这些语言可以从一个正在运行的PostgreSQL版本中进行添加(或删除),而且任何的使用这种语言的函数定义也可以在PostgreSQL运行的时候被创建或者抛弃。这些语言对PostgreSQL内部函数和所有数据实体具备所有的访问权限,这样调用者是被允许的。
对于PostgreSQL,事实上有许多类似的插件语言扩展程序可供使用。我自己已经使用过的包括:PHP、Python、bash和PL/pgSQL。这意味着PostgreSQL的标准语言也需要使用其他语言所使用的相同扩展系统,来进行安装和管理。
这个让我们意识到,相比于最初所料想的,实际上有更多的开发者可以使用PostgreSQL。软件开发者不必去学习一个新的开发语言来实现存储过程。他们可以选择合适的语言来扩展PostgreSQL,并且继续按照之前的工作风格和流程进行代码
编写。
经验总结:在PostgreSQL开发社区里没有二等公民。任何人几乎可以使用任何语言进行编码。
第三方工具:对于不同的数据库平台,我们经常会比较平台上可以使用的第三方应用的数量。我不确定第三方工具的总数和你实际所需要的第三方应用数量是否同样
重要。
最后,以下是一张产品列表,我经常会将这些产品和PostgreSQL一起使用:
Pentaho Data Integration (kettle):一个优秀的抽取、转换、加载(Extract Transform and Load,ETL)工具
Pentaho Report Server:一个强大的报告引擎
PgAdmin3:一个极好的数据库管理工具
php5-postgesql:一个供PHP进行本地访问PostgreSQL的包
Qcubed:一个支持PostgreSQL的PHP开发框架
Yii:一个很好的PHP开发框架
Talend:一个有用的ETL工具,但是并不是我所喜欢的
BIRT:一个很好的Java报告工具,这个工具带有简单的报告创建环境
psycopg2:Python针对PostgreSQL的套件
以上几乎是一张完美的产品列表,这些工具已经让PostgreSQL开发变成了一件轻而易举的事情。我们可以用这样一张支持PostgreSQL的应用程序列表来充实本书内容。也非常感谢他们的合法授权,PostgreSQL可以被内嵌到很多商业应用中,但是你可能从来没有真正认识它。
经验总结:你不用过多考虑到底现有多少工具可以支持PostgreSQL这个产品。所有重要的工具都是可用的。
2.6.1 平台兼容性
SQL Server是微软的产品。这样说来,它曾经是,而且将来也会是一款微软平台的工具。通过ODBC,我们可以进行一定的限定级别的访问,但是对于跨平台的开发来说,它不是一个严谨的选择。目前来看,MySQL和PostgreSQL支持当前可用的每一个操作系统。这种能力(或者说限制的缺失)对于长期的稳定性来说是一个非常有力的因素。如果某种特定的操作系统不再可用,或者不再支持开源软件,那么把数据库服务器迁移到另外一个平台将是一件非常麻烦的事情。
2.6.2 应用程序设计
“已有的事,后必再有。已行的事,后必再行。日光之下并无新事。”
——传道书 1:8-10 KJV
“旧的事情都已经过去,看呀,所有新的事情都已经发生。”
——2哥林多后书5:16-18 KJV
在软件开发过程中,我们经常会遇到这样的情况,当过时的技术再次兴起时,这些开发者就如同信奉宗教一样拥抱这种观点。我们曾经在瘦服务端与瘦客户端之间摆动,在扁平存储与分级存储之间选择,也经历了从桌面应用到网络应用的转变,但在本章中,我们最适宜讨论的话题是客户端与服务端程序设计。
程序设计实现之间之所以会出现这种摇摆,与客户端或服务端所能提供的功能无任何关系,反而很大程度上是开发者的经验会产生更大的影响,且这种影响可以导向任何一种选择,这种选择取决于开发者首先碰见的是何种程序设计实现方式。
我鼓励服务端开发者与客户端开发者都先撇下他们所使用的工具,之后再阅读本章剩余部分。
在接下来有限的时间内,我们将讨论“服务器程序设计”的绝大多数新功能。如果那时候你仍然没有被说服,那我们会看一下,在你没有抛弃应用为中心的观点的情况下,你如何利用这些功能所带来的诸多好处。
1.数据库被认为是不利的
看待服务器程序设计的最简单、最省事的方法是把数据库看做一个数据桶。你只需要使用最基础的SQL语句,比如INSERT、SELECT、UPDATE和DELETE,就可以每次操作一个单一的数据行,而且你还可以轻松地为多数据库创建应用程序库。
这个方法有一些明显的缺陷。以每次一行的方式在数据库服务器之间移动数据,这种方式的效率极低,并且你也会发现这种方法在网络架构的应用程序中完全不可行。
这个观点经常和“数据抽象层”联系在一起,这是一种客户端库文件,它允许开发者花费较少的力气将数据库从应用程序下面分离出来。这个抽象层在开源开发社区中是非常有用的,它可以被使用在多种数据库上,并且不需要财务上的扶持就可以获得最佳的性能。
在27年的职业生涯中,在没有抛弃应用程序的情况下,我从来没有改变过任何一个应用所使用的数据库。敏捷软件开发的原则之一是YAGNI(你并不需要它)。这就是其中的一个例子。
经验总结:数据抽象对项目来说是有价值的,尤其是对于那些在安装的时候需要选择数据库平台的项目。对于任何其他人,只需要说no。
2.封装
另一个偏向于客户端开发体系的技巧是尝试将数据库中具体的调用分离到一个程序库中。这个设计的目标通常是让应用程序对所有业务逻辑进行控制。在这种情况下,应用程序仍然扮演着国王的角色,而数据库仅仅是受国王控制的一个必要的*。
这个数据库架构的观点揭露了应用程序开发者的短处,就比如他们忽略了一个装满了工具的工具箱而仅仅选择了那把锤子。应用程序中的所有东西到时候都被绘制得像一枚枚钉子,然后开发者可以使用锤子敲打它们。
经验总结:千万不要仅仅因为对数据库不熟悉,就放弃数据库所能带来的强大力量。使用过程化语言,检查一下扩展应用的工具包。那里有一些很棒的产品。
3.PostgreSQL可以提供什么
到目前为止,我们已经提到了过程化语言、函数、触发器、定制的数据类型和运算符。这些东西可以在数据库里面通过CREATE命令直接创建,或者使用扩展应用被添加为库
文件。
现在我将向你展示一些事情,这些事情是你在PostgreSQL的服务器上进行程序设计的时候所需要记住的。
4.数据位置
如果可以的话,尽量将数据保存在服务器上。请相信我,数据在服务器上会更加顺畅,当修改数据的时候性能会更好。如果所有的事情都在应用层完成的话,首先数据需要从数据库端返回给程序,然后进行修改操作,最后把数据发送回数据库去执行这个事务。如果你正在开发一个网络架构的应用程序,你最不该考虑的就是上述方法。
让我们来看一小段程序,来看看如何使用两种方法实现对一个记录的更新:
《PostgreSQL服务器编程》一一2.6 过程化语言

这一小段代码将一行记录从数据库服务器拉出来并推送到客户端、进行数据评估,然后基于评估结果修改客户的账户信息。修改的结果最后会被发送回数据库进行处理。
在这个应用场景中,有几个错误的地方。首先,这个架构是可怕的。想象一下如果这个操作需要被上千甚至百万级的客户执行后果会是怎么样的呢?
第二个问题是事务的完整性。如果在查询与更新语句执行之间,一些其他的事务更新了这个用户的账户,出现这种情况该怎么办?这个客户是否仍然是价值客户?这取决于评估的业务逻辑。
尝试以下的示例:
《PostgreSQL服务器编程》一一2.6 过程化语言

这个示例变得更加简单了,它考虑了事务的完整性,并且可以应对相当大数量的客户操作。为什么我们在这里展示这么一个简单且明显的例子呢?因为许多开发框架都默认地按照错误的方式。可以预见的是,为了实现跨平台以及快捷地将形式集成到简单的设计模型里面,代码生成器会产生和这个例子相等的形式。
这个方法催生了一些可怕的事实。对于一个拥有少数并发事务的系统,你可能可以看到你期待的内容,但是随着并发量的增长,意外情况的也会频发。
第二个例子展示了一个更好的想法:对列进行操作,而不是行,将数据留在服务器上,并且让数据库为你完成事务操作。这就是数据库存在的理由。
2.6.3 更多基础
在开始服务器程序设计之前,这里旨在提供一些基础的背景信息。在接下来的几部分中,我们会研究你即将用到的通用的技术环境。我们会提到许多信息,不要着急,你不需要马上记住所有内容,抓住它们的大概意思即可。
1.事务
PostgreSQL里面默认的事务隔离级别叫做Read Committed。这意味着如果多个事务尝试修改相同的数据,它们必须等待其他事务完成,之后才可以对结果数据进行操作。它们在一个先进先出的队列中等待。数据的最终结果是大多数人所能预料到的,反映的是最后的一个时序性的更改。
PostgreSQL并没有提供任何会导致错误读取的方法。错读是在其他人的事务期间查看数据的能力,并且假设它已经被执行完毕,从而使用了它。由于多版本并发控制产生了作用,所以PostgreSQL并不支持这种能力。
这里有一些其他可用于事务隔离的方法,你可以在页面http://www.postgresql.org/docs/9.2/static/transaction-iso.html中进行深入阅读。
我们需要特别注意的是,当非事务性的代码块(BEGIN..END)被定义的时候,PostgreSQL会像一个私人事务一样对待每一个独立的语句,并且在语句完成的时候立即执行它们。这样的操作就可以让其他事务有机会插入到你的语句中。一些程序设计语言在你的语句块周围会提供一个事务代码块,当然也并不是所有的语言都会提供。请查看你的语言文档,求证一下你是否在一个事务会话中运行程序。
当我们使用这两种主要的客户端与PostgreSQL进行交互时,事务行为是不同的。psql命令行客户端并没有为你提供事务块。你需要自己决定什么时候启动/停止一个事务。而pgAdmin3查询窗口将你提交的所有语句封装到了一个事务块中。这就是它提供的一个“取消”选项。如果事务被中止了,一个“回滚”操作将被执行,然后数据将回到它的前一个状态。
一些操作是不被认定为事务的。比如即使事务失败了且已经被回滚,但是一个“序列”对象将会继续执行。“CREATE INDEX CONCURRENTLY”需要它自己的事务管理,并且不应该在事务块内部被调用。VACUUM和CLUSTER也是同样的原理。
2.通用的错误报告和错误处理
如果你想在你的执行期间把状态提供给用户,你应该对这些命令比较熟悉:RAISE、NOTICE和NOTIFY。从事务性的角度看,它们之间的区别是即使它们被打包在一个事务中,RAISE和NOTICE会立即发送信息,然而NOTIFY需要等事务被处置之后才会发送一条消息。因此如果事务失败或回滚了,NOTIFY则不会立即向你通知任何消息。
3.用户定义函数(UDF)
编写用户定义函数是PostgreSQL的强大功能之一。函数可以使用许多不同的程序设计语言来编写,也可以使用这个语言所提供的任何控制结构,并且即使是采用“不受信”的语言,函数也可以执行PostgreSQL中可用的任何操作。
函数可以提供一些甚至非SQL直接相关的功能。接下来我们引用的一些示例将会展示如何获取网络地址信息、查询当前系统、移动文件,以及任何你心中所期望的事情。
那么,我们该如何利用PostgreSQL的这个优点呢?我们从声明一个函数开始:
《PostgreSQL服务器编程》一一2.6 过程化语言

但是,如果我们想把三个整数加在一起,该如何操作呢?
《PostgreSQL服务器编程》一一2.6 过程化语言

我们在前面提到过一个概念叫做函数重载。这个功能允许我们声明一个同名函数,但是使用的是不同的参数,如此可能会产生不同的行为。这个区别的巧妙之处在于它仅仅改变了函数中一个参数的数据类型。PostgreSQL开发的函数取决于函数参数与期望的返回类型的匹配程度。
但是,假设我们的打算是把任意数量的数字加起来,那该如何完成呢? PostgreSQL也有方法来完成。
《PostgreSQL服务器编程》一一2.6 过程化语言

这个函数允许我们传入任意数量的整数,并且返回一个正确的结果。这些函数当然不会处理real或者numeric类型的数据。为了处理其他的数据类型,借助这些类型,我们仅仅需要再次声明这个函数,并且使用相应的参数来调用它。
为了获取更多关于变量参数的信息,你可以查看http://www.postgresql.org/docs/9.2/static/xfunc-sql.html#XFUNC-SQL-VARIADIC-FUNCTIONS
4.其他参数
目前,将数据传入函数与从函数输出有多种方法。我们也可以声明IN/OUT参数、返回表,返回记录集合,也可以使用游标进行输入与输出。
这里有一个特殊的数据类型叫做ANY。这种类型允许不限定参数类型,同时也允许任何基础数据类型被传递到函数,然后由函数决定如何处理这个数据。
5.更多控制
一旦你按照需求编写了你的函数,PostgreSQL便会在函数执行上给你提供额外的控制。你可以控制这个函数能访问什么数据,也可以控制PostgreSQL如何解释执行函数的开销。
这里有两个声明可以为你的函数提供安全环境。第一个是Security Invoker,这是默认的安全环境。在默认环境里,调用者的权限通过函数来限制。
另一个环境是Security Definer。在这个环境下,函数创建者的用户权限是在函数执行期间生效的。一般情况下,为了特殊目标,这种方法可临时被用于提高用户的权限。
同时,PostgreSQL也可以定义函数的开销。这个可以帮助查询规划器评估调用这个函数会产生多大的消耗。更高次序的开销会迫使查询规划器修改这个访问路径,以降低函数被调用的频率。PostgreSQL文档将这些数字显示为一个cpu_operator_cost因子。这里有一些误导。这些数字和CPU运行周期并没有直接关系。它们仅仅和同其他函数进行结果比较时是相关的,这更像是一些国家的货币与欧盟其他国家的货币相比。一些国家的欧元比其他的更为有优势。
为了估计自己所定义的函数的复杂性,让我们从你所使用的语言开始。对于C,默认值是1 * number of records returned。对于Python,默认值是1.5。对于脚本语言,如PHP,更合适的默认值可能是100。对于plsh,你可能要使用150或更多,这取决于所涉及的外部工具的数量。而对于PL / pgSQL,默认值是100,这样运作起来似乎效率挺不错的。

上一篇:ITOO---MVC3.0动态添加表格的行数并Controller中获取添加数据


下一篇:Python编程:使用cachy缓存数据