使用 PostgreSQL
当我最初开始使用 Linux 进行基本的 Web 开发时,我看到我的三个主要工具——Perl、GNU Emacs 和 Apache——已经包含在内。但是,至少缺少一样东西,即关系数据库。当我开始使用 Linux 时,我已经使用数据库(主要是 Sybase)大约一年了,并且知道我需要一个好的数据库服务器才能创建复杂的网站。
我听说过 PostgreSQL,并在了解更多信息后,决定安装它。不幸的是,我的安装体验不太愉快,几个小时后我就放弃了。我放弃的另一个原因是,我找到了另一个数据库服务器 MySQL。MySQL 并不具备我想要的所有数据库功能,但它已经足够接近了;我使用它为客户实现了许多 Web 应用程序。
在过去的五年里,情况发生了很大的变化:MySQL 已根据 GNU 通用公共许可证重新发布,并计划包含对事务的基本支持,而 PostgreSQL 现在非常容易安装,并且包含大量特性和功能。两者在自由软件社区中都以强大的程序而闻名,可以帮助您完成工作。
我仍然将 MySQL 用于各种任务,并期望继续这样做,但是,我越来越发现 PostgreSQL 更适合我的需求。本月,我们将了解 PostgreSQL,从其基本功能开始,创建一个使用事务的小型基于 Web 的应用程序。在此过程中,我将尝试将其与 MySQL 进行比较,描述哪种产品可能更适合哪种情况。
截至撰写本文时,PostgreSQL 的最新版本是 7.0.2,于 2000 年春季发布。与所有开源软件一样,您可以从 Internet 下载 PostgreSQL 源代码并自行编译。我的办公室使用 Red Hat 发行版,我通常更喜欢使用 RPM 安装软件,以便于维护。我们从 PostgreSQL 网站下载了 RPM,安装了它们,并且几乎立即就可以运行了。
像所有现代数据库系统一样,PostgreSQL 包含一个服务器,可以处理来自多个客户端的连接。通常,网络中只有一台计算机被指定为数据库服务器,其余计算机配置为客户端。(服务器通常也被配置为客户端,以便于调试和系统配置。)服务器的 RPM 通常以名称“postgresql-server”开头,而客户端 RPM 的名称为“postgresql”,后跟版本号。
PostgreSQL 客户端以源代码形式和 RPM 形式存在,适用于人们在设计 Web 应用程序时使用的大多数编程语言,包括 Perl、Python、Java 和 PHP。如果您打算从头开始构建其中任何一个,您将需要从源代码或 RPM 安装 PostgreSQL 开发库。我从源代码编译了 Perl 及其模块,但在其他方面,我利用了 RPM 并安装了预编译的二进制文件。
像 MySQL 和许多其他关系数据库一样,PostgreSQL 中的表被分组到一个“数据库”中,就像文件被分组到一个目录中一样。PostgreSQL 在安全配置方面提供了很大的灵活性。数据库可以根据 IP 地址或用户名允许或拒绝访问,而单个表和其他对象允许基于用户名的各种访问级别。这些配置在 pg_hba.conf 文件中执行,该文件默认安装在我的系统上的 /var/lib/pgsql/data 中。
默认情况下,只有一个名为“postgres”的用户被授权创建新用户或创建新数据库。当您第一次开始使用 PostgreSQL 时,您可能必须使用 su 将您的身份更改为 root,然后再次 su 将您的身份更改为“postgres”。一旦您成为 postgres 用户,您就可以使用 createuser 程序创建一个或多个新用户。然后,您可以指示是否允许他们创建新数据库和/或新用户。
PostgreSQL 附带的 psql 数据库客户端是一个用于处理数据库的出色交互式工具。与 MySQL 客户端程序一样,它使用 GNU ReadLine 提供与 Emacs 兼容的键绑定,用于直接输入 SQL 查询。psql 还提供了大量的帮助命令,这些命令以反斜杠开头,例如 \h(显示任何 SQL 命令的帮助)、\d(列出可用对象以及有关这些对象的详细信息)和 \l(列出可用数据库)。我经常使用 psql 来仔细检查程序生成的查询是否正常工作;它易于使用且速度快。
PostgreSQL 实现了很大一部分标准 SQL,因此,如果您以前使用过关系数据库,则很容易学习。我们可以创建一个简单的表,如清单 1 所示。
serial 数据类型类似于 MySQL 的 AUTO_INCREMENT 标记。每次我们向表中 insert 新行时,它都会为我们提供一个唯一的数字。serial 列使用 PostgreSQL 数据类型,称为“序列”,PostgreSQL 的 nextval 和 currval 函数在该序列上运行。也可以直接使用序列(参见清单 2)。
PostgreSQL 区分单引号和双引号,因此请务必说 nextval(`people_id') 而不是 nextval("people_id")。
在 psql 内部,分号是 \g 的同义词,意思是“执行此查询”。在 psql 外部,它没有任何意义,甚至可能在您的数据库驱动程序中引起错误。
与 MySQL 不同,PostgreSQL 在表名和列名方面不区分大小写。但是,我更喜欢遵循 Joe Celko 的 SQL 大写规则:关键字全部大写,表名采用首字母大写,列名采用 all_lowercase_with_underscores。
与 MySQL 中一样,PostgreSQL 允许我们指定哪个列是主键。PostgreSQL 还允许使用 unique 列(以及列的组合),以及使用 create index 语句创建索引。
PostgreSQL 的数据类型与 MySQL 中的数据类型略有不同,但如果您使用过其他数据库,则相对容易理解。varchar 和 numeric 类型都受支持。MySQL 和 PostgreSQL 中数据类型之间最令人困惑的差异可能是 timestamp。timestamp 在 MySQL 下,时间戳列会自动设置为最新 insert 或 update 的值。在 PostgreSQL 中,时间戳列仅包含日期/时间值。
PostgreSQL 支持许多标准 SQL 函数和 MySQL 包含的许多扩展。当然,区别在于它们的实现方式。例如,MySQL 有一个 regexp 函数,类似于 like 函数。相比之下,PostgreSQL 使用 ~(波浪号)运算符实现了 regexp 功能,这可能是因为 Perl 的流行。
PostgreSQL 还允许使用 create type 函数创建新的数据类型。实际上,PostgreSQL 的一个著名之处在于它是面向对象数据库和关系数据库的混合体。我从未使用过此功能,但它似乎是一个有趣而强大的功能。
到目前为止,除了在一些基本语法上,PostgreSQL 似乎与 MySQL 没有太大区别。但是,在这里,两个数据库开始出现分歧。PostgreSQL 包括所谓的“参照完整性”检查,这意味着我可以定义一行中的某些值可以定义为非法。
数据库程序员学到的第一件事之一是 null 表示列不包含任何值——甚至不是 false、空字符串或零。我们可以通过将列声明为“not null”来禁止列包含“null”。这可能是最简单的完整性检查,数据库确保相关列永远不会包含非法值。在我们上面的 People 表中,name 列被定义为 not null,告诉数据库每个人都必须有一个名称。
但是,很多时候,这还不够。例如,People 表包含一个电子邮件列。现代电子邮件地址必须包含 @ 符号才能有效。使用参照完整性,我们可以确保添加到数据库的任何电子邮件地址都包含 @ 符号。为了做到这一点,我们使用 PostgreSQL 类似 Perl 的 ~ 运算符,该运算符将字符串与正则表达式匹配
email VARCHAR(50) NOT NULL CHECK (contact_email ~ '@')
如果没有 @,则输入的(或更新的)值将被视为非法并生成错误代码。让数据库标记这样的错误似乎令人沮丧,但这肯定比拥有包含不正确值的数据库要好。我经常使用此类检查来确保列不为空,并禁止空字符串或其他非法值。例如,可以使用参照完整性检查定义 People 表(参见清单 3)。
如果我尝试插入违反其中任何检查的行,PostgreSQL 将拒绝这样做(参见清单 4)。
因此,我的 address2 值似乎无效。实际上,我尝试在此处输入一个空字符串,这是不允许的。相反,我应该输入一个 null 值(请记住,null 和空字符串是不同的值)。果然,用 null 替换空字符串使查询成功。但是数据库拒绝允许我们用无效信息破坏它,并且没有插入新行。
参照完整性也意味着一个表可以可靠地使用“外键”指向另一个表。大多数表都有一个主键,即唯一标识每一行的列(或列集)。例如,美国社会保障管理局的数据库使用社会保障号码唯一标识每位美国公民。这个号码意味着您可以更改您的姓名、地址、电话号码、银行帐户和工作,但同一个 SSN 仍然会指代您。同样,主键允许我们继续指向表中的特定行,而无需依赖该行中的任何值。
例如,假设我们要创建一个包含三列的 Appointments 表(参见清单 5)。
清单 5 中的表允许我们指示何时与每个人会面,确保 notes 列为空或非空,并且一次只能进行一次预约。person_id 列应该包含 People 表中的 person_id。但是,是什么阻止我输入 person_id 为 5 或 50?我如何确定该值是有效的?
答案是我们可以将 person_id 设置为 People 的“外键”,这意味着 People.person_id 只能包含 People.person_id 中包含的值。我们可以使用 REFERENCES 关键字添加此约束(参见清单 6)。
PostgreSQL 用户对 MySQL 的主要抱怨之一是缺少事务。如果您只在开发工作中使用过 MySQL,您可能会想知道为什么需要事务以及它们如何适应数据库环境。
事务背后的基本思想是将多个查询分组到一个逻辑查询中。如果事务中的任何查询失败,数据库将“回滚”到事务开始之前的状态。
由于事务,您可以确保即使在事务执行过程中电源发生故障,从一个帐户到另一个帐户的资金转移也不会意外地让您拥有过多或过少的资金。在事务最终“提交”之前,数据库会假装所有这些都没有发生过。
MySQL 文档(以及作者和支持人员)有自己的事务哲学,包括提交和回滚,这与当前关系数据库中流行的“ACID”测试背道而驰。虽然我不同意他们的一些结论,但毫无疑问,MySQL 中缺少传统的事务使其成为一个灵活而快速的数据库,非常适合执行大量选择和少量插入和更新的网站。
PostgreSQL 非常接近地遵循标准模型,使得在不锁定表的情况下(如 MySQL 范例中那样)执行事务成为可能。要开始事务,请使用 being work 语句,要结束事务,请使用 commit 或 rollback 语句。Perl 的 DBI 或 Java 的 JDBC 的用户可以改为使用与数据库连接对象关联的 commit 和 rollback 方法。DBI 和 JDBC 默认在 AutoCommit 模式下运行,这意味着每个查询都隐式地放置在自己的事务中。要将多个查询放在单个事务中,程序必须关闭 AutoCommit 模式,执行事务,执行提交或回滚,然后(通常)重新打开 AutoCommit 模式。例如,假设我们有一个单独的 Salaries 表,指示每个员工的工资(参见清单 7)。
请注意上面的表如何通过对员工和日期的组合设置唯一限制来确保每个员工在给定日期只能获得一次加薪。
我们大概可以将此信息保存在 People 表中。但是,将工资信息放在单独的表中可以更容易地隐藏信息,使其免受窥探。这也意味着我们可以使用 SELECT 语句提取员工的整个工资历史记录,同时保持表的规范化,并且仅存储每个人的信息一次。
为单个员工设置两个表会引发一些问题。最重要的是,我们希望确保添加到 People 表的任何员工也将添加到 Salaries 表(拥有没有任何工资的员工将是相当尴尬的)。添加新员工应该是一个逻辑操作,但需要两个 insert 语句——一个插入 People,另一个插入 Salaries。如果数据库在第二个语句的中间崩溃了会发生什么?
清单 8 包含一个简单的命令行程序(可以很容易地将其转换为 CGI 程序),该程序创建一个新员工,首先向 People 表添加一个新行,然后向 Salaries 表添加相应的行。我们使用 PostgreSQL 的 currval 函数检索新插入员工的 person_id。然后,我们使用该值将行 INSERT 到 Salaries 表中。
我们通过关闭 AutoCommit 模式(将其设置为 0)来确保两个操作在单个事务中发生。一旦发生这种情况,我们就负责在完成时执行提交。如果没有调用 $dbh->commit,PostgreSQL 将假定我们想要回滚两个插入操作,假装它们从未发生过。如果程序在中间崩溃——如果任何 SQL 查询失败,就会发生这种情况,因为我们已激活 RaiseError——则不会发生任何更改。
为了捕获错误并向用户显示消息,我们可以使用 Perl 的 eval 函数的块版本,如清单 9 所示。这会运行 {} 内的代码,从中退出,并在出现任何问题时设置特殊变量 $@。这种使用 eval 捕获错误的技术为我们提供了一种基本形式的异常处理,并使我们能够仅打印出我们想要的错误。如果我们激活 PrintError 属性或“use diagnostics”编译指示,Perl 将打印出不仅仅是我们简单的消息,这会使用户感到困惑。
我们现在已经定义了三个表:People、Appointments 和 Salaries。如果我想创建一个表,列出所有与人安排的约会以及他们的工资历史记录会怎样?(这在年度工资会议之前很有用。)清单 11 中的表将执行这样的请求,按时间顺序排列约会。
与其每次都从头开始创建此查询,不如将其创建为“视图”。视图是动态生成的表,带有附加的名称,在很大程度上可以视为只读表。我们可以创建约会和工资信息的视图,如清单 10 所示。
请注意,通用 select 语句和用于创建视图的 select 语句之间几乎没有区别。实际上,我们所做的唯一更改是删除 order by 子句,因为 PostgreSQL 在版本 7.0.2 中尚未实现此功能。因此,我们可以按时间顺序列出所有约会(参见清单 11)。
视图比简单的 select 语句具有许多优点
它们迫使更多的处理发生在数据库服务器上。由于数据库服务器通常是高端机器,并且因为它们可以随时访问数据,所以它们最终会完成更多的工作。客户端花费更少的时间来创建动态生成的 SQL 语句。
视图有自己的权限结构。例如,公司的人事部门应该有权访问工资信息,但其他人则不应该。使用视图,可以隐藏信息并允许特定用户或 IP 地址访问基本表,但保持视图对公众开放。
也许最重要的是,视图使我们能够在比基本表更高的抽象级别上思考。视图可以对表中的值执行各种计算和操作,就像简单的 select 可以做的那样。如果您知道您需要将特定列中的所有值乘以 3,您可以创建一个新的视图,该视图会自动执行计算。您不再需要在 select 语句中或在检索值的程序中执行计算。
视图不能接受变量参数,这意味着您不能创建一个有时检索以“A”开头的用户名,有时检索以“B”开头的用户名的视图。要执行这样的操作,您需要创建一个过程。PostgreSQL 支持多种编程语言,可以在其中编写过程,包括 Pl/PGsql(类似于 Oracle 的 PL/SQL 语言)、PL-perl(用 Perl 编写的过程)和 Pl-tcl(用 Tcl 编写的过程)。
创建过程后,您可以创建“触发器”。触发器是在系统中发生某些事情时自动执行的过程。例如,您可以使用触发器来确保当用户从 People 表中删除时,Salaries 和 Appointments 表中引用该用户的行也被删除。如果没有这个,Salaries 或 Appointments 中的行可能会引用不再存在的 person_id。每当有人对表执行插入、更新或删除操作时,都可以激活触发器,并且可以在操作发生之前或之后运行。
最后,视图通常是只读对象,因为它们只是 select 查询的别名。但是,PostgreSQL 有一个复杂的规则系统,可以重写符合某些条件的查询。使用规则,您可以拦截针对视图的插入、更新或删除,并将其重写为对一个或多个表的一系列操作。因此,无法重写插入到 ApptAndSalaryView 中的内容,因为用户的电子邮件地址(来自 People)未出现。但是,更新肯定是有意义的,并且可以重定向到修改 People、Appointments 或 Salaries 表,必要时。
虽然 PostgreSQL 的普及程度正在迅速提高,但它确实存在问题。PostgreSQL 开发团队非常清楚这些问题,并且似乎正在迅速处理它们。
最紧迫的问题可能是每个元组或数据库行的 8KB 限制。这意味着没有行可以包含超过 8KB 的数据。这会影响数据库操作的许多部分,从仅为 BLOB(二进制大对象)提供适度的支持,到阻止开发人员创建甚至中等大小的表。
其他问题,例如视图与联合的组合,在最近的一个编程项目中困扰了我(和我的客户)。这,加上缺少外连接,意味着最近的一些项目中需要进行许多变通方法。可以在应用程序级别解决大多数或所有这些问题,但我正在焦急地等待添加这些功能。
使用 MySQL 在某种程度上宠坏了我,因为内置函数的数量很大,并且允许我创建数据库应用程序而无需创建自己的函数。PostgreSQL 的内置函数较少,但正如我们之前看到的,它确实允许我创建任何我可能喜欢的函数,使用各种编程语言。但是,这些语言的安装和使用文档记录不佳;虽然它们可能非常强大,但入门需要一段时间。
尽管最近 PostgreSQL 咨询小组发布了一些基准测试,但可以肯定地说,PostgreSQL 比 MySQL 慢。与此同时,我惊喜地发现速度差异没有我想象的那么大。当然,这种速度差异的存在是因为 PostgreSQL 包括事务和参照完整性,这两者都需要比 MySQL 的表级锁定更多的处理和记录。
PostgreSQL 的最后一个缺点是,没有那么多网络托管服务提供它。当使用专用服务器时,这可能并不重要,但我的一些客户只有预算租用虚拟服务器。在寻找 Web 服务器时,数据库功能应该是一个考虑因素,但在我的经验中,开发人员在项目中使用工具的发言权通常比他们希望的要少。尽管如此,如果您对更接近商业数据库(包括事务和完整性约束)的内容感兴趣,PostgreSQL 是您的最佳选择。
虽然我继续使用 MySQL,但我越来越对 PostgreSQL 印象深刻,并且已经开始将其用于许多咨询项目。我对它的速度和多功能性以及未来的发展方向印象深刻。6.x 版本和 7.x 版本之间的改进令人震惊,我期待看到更多!
如果您正在 Linux 下实施数据库应用程序(包括 Web/数据库应用程序),我鼓励您了解一下 PostgreSQL。即使您决定坚持使用 MySQL,了解其他数据库的工作方式以及数据库程序员社区中关于事务、存储过程和完整性约束的争论如此之大的原因也很有用。谁知道呢?也许您还会发现 PostgreSQL 比您想象的更适合您的应用程序。自由软件的美好之处之一是您可以选择最适合您需求的工具,而了解 PostgreSQL 是朝着这个方向迈出的重要一步。
Reuven M. Lerner 拥有一家小型咨询公司,专门从事 Web/数据库应用程序开发和管理。当您阅读本文时,他应该已经完成了由 Prentice-Hall 出版的 Core Perl。您可以通过 reuven@lerner.co.il 或 ATF 主页 http://www.lerner.co.il/atf/ 与他联系。