PEAR MDB 数据库抽象层
|
Write once - run anywhere 这是Java的一句行销口号,但是它同时也是PHP的关键特性之一。许多商业模型依赖于操作系统无关性来保证产品能够销售给广泛的客户群体。因而,为什么要把你自己绑在某种数据库厂商的身上呢?数据库抽象层使得你能够与数据库独立的开发你的应用程序。但是,通常情况下它们对性能的影响超过了你所希望的,要么他们并不足够抽象以消除所有和特定数据库相关的代码。 这篇文章将教给我什么? 这篇文章将对数据库抽象包 PEAR MDB 有一个很好的介绍。文章的焦点将是对 MDB 超越类似包所提供的更先进的特性,例如数据类型抽象和基于 XML 的 schema 管理。对 PHP 和 SQL 的基本理解是推荐的。 为什么另外再要一个数据库类? 通常, web 工程在客户已经确定了要使用那种 RDBMS (关系型数据库管理系统)之后被添加给已经存在的 IT 基础结构。即使那并不是因为不同的预算可能影响的你选择何种数据用于部署的情况。最终,你作为开发者可能简单的偏好于不把自己绑在某个厂商身上。自此,意味着给每个支持的数据保持版本或者牺牲更多性能但是获得多于必须的易用性:走入 PEAR MDB 吧。 MDB 是着眼于使得编写 RDBMS 无关的 PHP 程序成为简单的过程的数据库抽象层。大部分其他的 PHP 的所谓数据库抽象层紧紧给所有支持的数据库提供了一个公用 API 以及非常有限的抽象(大部分只是针对序列的)。MDB 另一方面能够用来抽象所有数据库发送和接收的数据。甚至数据库 schema 都能被定义为 RDBMS 无关的格式。但是它提供这些功能的同时仍然保持了很高的性能以及简单易用。这是通过深入观察两个流行的数据库抽象层,PEAR DB 和 Metabase, 之后并且对它们进行了融合后获得的。而且在融合过程中,趁着这个机会清理了它们融合后的 API 以及任何影响性能的设计。 MDB 是怎样出现的? 早在 2001 年的秋天,我就在寻找一种可能能够让我公司的程序框架与 RDBMS 独立的数据库抽象包。这个目标是把特定数据库相关的代码数量减少到零。我发现提供这样的功能的唯一的一个包是 Metabase。但是 Metabase有一些部分是因为为了和 PHP3 兼容的让人不舒服的 API。尽管如此,我们决定 Metabase 是我们唯一的选择。但是即使是在给 Metabase 增加了一个性能改进的补丁之后,我们仍然感到我们放弃了太多的性能。我们在 2001 年的 PHP 国际会议上碰到了 Metabase 的作者,并且我们谈论了让像 Metabase 这样的东西成为 PEAR 工程一部分的好处。后来不久,在 PEAR 邮件列表上就 PEAR DB 和 Metabase 融合的可能的好处又开始了一场讨论。在我们公司进行了许多讨论之后,我们决定承担这个任务。数个月的艰辛工作之后,我们现在有了 MDB 的第一个稳定的 release。 MDB 给你提供了什么? MDB 结合了 PEAR DB 和 Metabase 的大部分特性。实际上,PEAR DB 的特性中唯一不再存在的是作为结果集返回一个对象。我们放弃了这个特性是因为这个特性不常用而且对于性能的损失是非常明显的。许多开发上的时间用在了使得 API 尽可能的好用。最终,MDB 非常高地提供了这些功能至少和 PEAR DB 一样快而且比 Metabase 快很多。这些最重要地特性的列表: OO 风格的 API 那么它如何使用呢? MDB 提供了一些非常先进的抽象特性。记住这些特性只是供选择的是很重要的。但是在编写 RDBMS 无关的 PHP 程序时使用它们是非常重要的。一个展示使用 MDB 是多么简单的例子在文章的结尾的 "链接和文献" 部分。如前面所说,文章的焦点是介绍使得 MDB 与其他 PHP 数据库抽象层不同的那些特性。你可以在随本期文章一同包装的 CD 中找到所有这些例子脚本的代码。 但是,首先我们需要把 MDB 安装上去。使用 PEAR 安装程序这其实非常容易。我不能在这篇文章中完整的讲述 PEAR 安装程序但是我听说下一期将非常详细的讨论 PEAR 框架的里里外外。让安装程序运行于 Windows 的工作在进行当作但是支持仍然有一点古怪。对于 *nix 系统你需要 PHP 的 CGI 版本安装在了你的系统并且简单地运行下面地命令:
lynx -source go-pear.org|php
利用数据类型抽象 因为大部分数据库倾向于有一些个性或者怪癖,对于MDB来说把这些不同之处给开发者隐藏起来非常重要。MDB 通过定义自己的内部数据类型来达到这点:text,boolean,integer,decimal,float,date,time,time stamp,large objects(文件)。所有传递给数据库和从数据库获取的数据都能转换成 MDB 的内部格式或者从数据库的内部格式转化回来。本节相关的例子脚本能够再 datatype 目录中找到。让我们看看下面的查询:
// set time out to 30 minutes $timeout = time() 60*30; // SELECT query showing how the datatype conversion works $query = "SELECT createtime, user_id FROM sessions"; $query .= " WHERE session = ".$session; $query .= " AND lastaccess < ".$timeout;
$timeout = MDB_date::unix2Mdbstamp($timeout); // SELECT query showing how the datatype conversion works $query = "SELECT createtime, user_id FROM sessions"; $query .= " WHERE session = ".$mdb->getTextValue($session); $query .= " AND lastaccess < ".$mdb->getTimestampValue($timeout);
$result = $mdb->queryRow($query, $types);
因为相当多的 RDBMS 以相同的方法返回整数数据,没有必要转换整数数据。因而,为了获得稍许的性能改进你能够这么做:
$result = $mdb->queryRow($query, $types);
Listing 1 展示了一个使用预准备的查询的例子。如果你必须运行大量查询而唯一的差别是数据传递给数据库,但是查询的结构还是一样的,这些能够相当的方便。高级的数据库能够在内存中储存解析好的查询来加速性能。 Listing 1
array(1, "one", "un"), array(2, "two", "deux"), array(3, "three", "trois"), array(4, "four", "quatre") );
$p_query = $mdb->prepareQuery("INSERT INTO numbers VALUES (?,?,?)"); $param_types = array("integer", "text", "text");
foreach ($alldata as $row) { $mdb->execute($p_query, NULL, $row, $param_types); }
在支持的数据类型中还有 LOB (大对象),它使得我们能够在数据库中储存文件。二进制文件储存在 BLOB (二进制大对象)中而且普通文本文件储存在 CLOB (字符大对象)中。在 MDB 中你仅仅能够使用预准备的 INSERT 和 UPDATE 查询储存 LOB。使用 MDBA::setParamBlob() 或者 MDB::setParamClob() 你能够设置预准备查询的 LOB 域的值。两个函数都预期传递一个 LOB 对象,而它能够使用 MDB::createLob() 创建。
"Type" => "inputfile", "FileName" => "./myfile.gif" ); $blob = $mdb->createLob($binary_lob);
$character_lob = array( "Type" => "data", "Data" => "this would be a very long string container the CLOB data" ); $clob = $mdb->createLob($character_lob);
$mdb->setParamBlob($p_query, 1 , $blob, "b_data"); $mdb->setParamClob($p_query, 2 , $clob, "c_data");
$result = $mdb->executeQuery($p_query);
$binary_lob = array( "Type" => "outputfile", "Result" => $result, "Row" => 0, "Field" => "b_data", "Binary" => 1, "FileName" => "./myfile2.gif" ); $blob = $mdb->createLob($binary_lob);
如我们在这节所见,MDB 特性本身的原生数据类型集自动映射于数据库中的原生数据类型。这保证了无论我们发送和从数据库接收什么样的数据,它都能与使用的 RDBMS 无关的使用相同的格式。如我在本节开篇已经提到的,这明显需要数据库使用的数据类型是 MDB 预期的。这种需要被用于确保映射所耗费的代价很小。下一节将教给我们 MDB 如何辅助在数据库中使用正确的数据类型。
利用在上个段落中描述的特性,你能编写真正的数据库独立的程序。但是 MDB 尝试向前更加迈出一步:它允许你用 XML 定义你的 schema。一个管理器把这种 schema 转换为给每种 RDBMS 的必要的 SQL 语句。这意味着你能对所有支持的 RDBMS 使用相同的 schema。本节的例子能够在 xml_schema 目录中找到。 我们现在将从头编写一个 XML schema 文件。首先,我们必须定义一个 XML 文档。数据库定义是包含在一个 database 标签之中的。数据库的名字是使用 name 标签定义的。create 标签告诉管理器数据库是否需要在它不存在的时候被创建。如果你把你的 schema 文件分割成好几个文件你你首先提交给管理器的那个文件中把 create 设置为 1。
<database> <name>auth</name> <create>1</create> </database>
Listing 2
<name>users</name> <declaration> <field> <name>user_id</name> <type>integer</type> <notnull>1</notnull> <unsigned>1</unsigned> <default>0</default> </field> <field> <name>handle</name> <type>text</type> <length>20</length> <notnull>1</notnull> <default></default> </field> <field> <name>is_active</name> <type>boolean</type> <notnull>1</notnull> <default>N</default> </field> </declaration> </table>
下一件我们现在需要做的事情是通过在 user_id 域放置恰当的索引确保 user_id 是唯一的。索引定义就在声明标签之内(Listing 3)。 Listing 3:
<name>users</name> <declaration> <index> <unique>1</unique> <name>user_id_index</name> <field> <name>user_id</name> <sorting>ascending</sorting> </field> </index> </declaration> </table>
<name>users_user_id</name> <start>1</start> <on> <table>users</table> <field>user_id</field> </on> </sequence>
当然,你也能使用任何值初始化表。例如你可能想要用你总是想要包含在你的程序中的管理用户来初始化前面的表格。为了这么做,我们需要把一个 initialization 标签添加给 table 标签。Listing 4 定义了一在另外一用 insert 标签包括的行之后的行。 Listing 4
<name>users</name> <initialization> <insert> <field> <name>user_id</name> <value>1</value> </field> <field> <name>handle</name> <value>default</value> </field> <field> <name>is_active</name> <value>Y</value> </field> </insert> </initialization> </table>
$input_file = "auth.schema"; // we do not have to connect to a specify a specific database at this time $dsn = "mysql://$user:$pass@$host"; $manager->connect($dsn); $manager->updateDatabase($input_file, $input_file. ".before");
所有这些都非常令人惊奇但是它变得更好。许多情况下程序需要在某些地方作出改变。例如我们可能决定需要把表的名字从 users 变成 people。我们可能还需要增加一个域 pwd 来储存密码域(请检查 textbox 的保留字)。
我们没有称那个域为 password 的原因是那是 Interbase 中一个域名的保留字。因为我们需要 RDBMS 独立,MDB 管理器要么给出一个警告要么在 fail_on_invalid_names 选项被设置为真的时候(这是缺省值)失败。 在过去的时候,你可能现在正处于把你所有已经有的东西变成这种新的 schema 的痛苦之中。但是由于 MDB 这些工作能够自动完成。在 listing 5 中是我们对我们的表格定义进行的修改: Listing 5
<name>people</name> <was>users</was> <declaration> <field> <name>pwd</name> <type>text</type> <length>32</length> <notnull>1</notnull> <default></default> </field> </declaration> </table>
$manager->updateDatabase($input_file, $input_file.".before");
我现在要看看 XML schema 格式的最后一个特性。如果你想要编程性的使用管理器,这个特性尤其重要。假设你有好几个有相同验证程序运行在你的数据库服务器的客户。 每个客户有一个服务器运行在这个服务器有相同的 schema 只有微小的区别:数据库的名字。可能为每个客户单独保存 schema 文件是可行的因为更新周期可能不是一样的,这不是我们例子验证程序的情况。这儿所有的客户同时更新。XML schema 文件允许我们为此可以使用变量。
<database> <name><variable>name</variable></name> </database>
$variables = array("name" => $name) $manager->updateDatabase($input_file, $input_file.".before", $variables); }
大部分读者可能发现它们处于这样的境地他们已经有了大量运行于其他数据库抽象层的程序。由于 MDB 的出身,大部分 PEAR DB 的用户应当发现 MDB 感觉上非常类似,因为 MDB 的 API 是基于 PEAR DB 的。Metabase 用户应当发现他们所有偏爱的功能都在 MDB 中有对应的东西。XML schema 格式和 Metabase 中的是一摸一样的。一个完全的指导来引导你把已经写好的程序移植到 MDB 中超出了本文的范围,但是我将利用这个机会给一些提示。如果你有任何具体的问题,放心的发信来询问我。 为了把你的 PEAR DB 程序移植到 MDB,最好的起点是 PEAR wrapper。你能使用 PEAR wrapper 来运行你的程序。wrapper 当然增加了一些额外负担,因而你可能有些想要移植到原生的接口。那么第一步是列出所有你程序当前使用的 PEAR DB 函数。然后看看 wrapper 从中找出任何 API 上的区别。有两个你要注意的关键区别:结果集不再是对象而且所有的允许你传递结果集的数据类型的查询方法将导致参数顺序上的少许改变。第一个区别意味着不能再结果对象上调用获取函数。
$row = $result->fetchRow();
$row = $mdb->fetchRow($result);
如果你想要把你已经存在的程序从 Metabase 移植到 MDB 你将必须改动所有的函数调用。查看 Metabase wrapper 需要改动什么将变得非常明显。如果你知道正则表达式你可能能够完成大部分这样的替换工作。无论如何,你应当向前并且运行你原来喜爱的高级抽象特性但是现在用的是 MDB。你可能注意得到的是函数名变得更加简短了。如果你作一些性能测试,你也将看到可观的性能改善。
本文发表时 MDB 可能已经不再是原来的 1.0 release 了。在原来的 MySQL 和 PostGreSQL 驱动之后,MDB还将有一个 ODBC 驱动以及可能的更多的驱动。这是 MDB 开发过程中关注的关键区域之一。一旦 MDB 在驱动方面跟上了 PEAR DB,它很有可能成为 PEAR 框架中标准的数据库抽象层。 但是还有另外一个开发中的关键领域:MDB_frontend 工程。MDB_frontend将成为基于 MDB 和 MDB 管理器的 phpMyadmin。有了这个工具,你将能够浏览储存在 MDB 支持的 RDBMS 中的数据库。MDB_frontend 将同时显示原生和 MDB 数据类型。模拟的特性比如 MySQL 中的序列将被隐藏。用户将仅仅看到一个序列列表而不是一个储存序列指的表,而在 MySQL 中这就是序列是如何被模拟的。而且 MDB_frontend 将帮助移植已经存在的数据库来符合 MDB 预期使用的原生数据类型。它还将帮助创建和更新 XML schema 文件。一些初期的工作已经完成了但是很多工作需要在公开发布之前被添加。 驱动和 MDB_frontend 是当前开发的所有焦点,在 MDB 中还有许多用户可能需要的:像 bulk 获取 LOB 域的集成,其他人可能需要外部和主键支持。如一直以来的那样如果你参与测试和实现,开源的东西将加快很多。但是我也很感谢像特性需求合阳的反馈。
在数月的艰辛工作之后,MDB 正在当前的 PEAR DB 和 Metabase 用户中获得认可。我还希望当前还没有被其他数据库抽象层说服的用户意识到 MDB 给他们的好处。当然,还是有许多程序需要对 RDBMS 进行特殊剪裁,对于这种情况像 MDB 这样的工具仅仅是增加了不必要的额外负担和限制。总的来说,我非常高兴我们在我们的公司中作出领导 MDB 开发的决定。在起初,我对尝试同时取悦 PEAR DB 和 Metabase 的用户但是结果可能到处不讨好多少有些担心。另外一个关心的来源是 PHP 社区是否将帮助其开发。我非常高兴 PHP 社区来了并且帮助撰写驱动以及 MDB 的核心。因而我们认为这个项目是一个极大的成功。我们还一并相信 MDB 将得到更大的改进。而且我们对帮助 PHP 变得更好感到高兴。 关于作者
PEAR MDB homepage: http://pear.php.net/package-info.php?package=MDB |
