这个教程分为两部分,第一部分讲述了我进行这种转换的动机,并一步步地解释了如何将已存在有MySQL的数据转换到Postgres中。第二部分将会解释如何根据新的数据库系统对PHP进行相应的调整。
转换的动机
我第一次了解Postgres是在PHPBuilder网站的一篇文章中。这篇文章将Postgres和MySQL进行了比较,当时我正在使用MySQL。但是,当我阅读了这篇文章后,我对Postgres着了迷 -- 但是当时我还没有想到对我的网站进行重新的设计。
我继续使用MySQL,因为我的主机提供商只能提供MySQL的支持,这是我所无法改变的。直到有一天,主机提供商的主机崩溃了。我立即换了一个主机提供商,与原来的那个相比,新的主机提供商有很多不同,他们在安全性和稳定性方面对我作出了更多的承诺。新公司试图说服我使用Postgres,因为Postgres要比MySQL来得更稳定,但是我当时没有接受这个建议,因为我的网站已经根据MySQL完成了全部的编码工作。他们只好专门为我的站点安装了MySQL。于是问题开始了。
我的第一个工作是将旧服务器上的MySQL的数据拷贝到新的主机上。首先,我将已有的数据dump到一个SQL文件中,然后在新的主机上导入这个SQL文件。在处理这个数千行的文件时,MySQL迅速地崩溃了。重启MySQL后,其中大概只有一半数据成功地导入了,而且MySQL只能间歇性地工作。最后,他们不得不删除了已经导入的信息让我再试一次。MySQL再次崩溃。这种情况重复了好几次,直到最终我决定将我的SQL文件分割成几块。我不得不又试了几次,最后终于将绝大多数的数据都成功地导入到新的MySQL服务器中。一切都好了,我总算松了一口气。
在下面的几个月中,MySQL几乎每两周都要崩溃一次,其中最惨痛的一次是在2001年6月底。这一次,存储在MySQL中的数据完全被毁坏了。我有一个SQL的备份文件,但是因为上次向MySQL中导入大量数据的痛苦的经历,这一次我再也不想通过这个备份恢复数据了。这时,公司再次建议我对我的网站进行转向,使用Postgres。由于MySQL的失败,最终我接受了这个建议。
将数据从MySQL转移到Postgres中
将数据从MySQL转移到Postgres是一个不大的挑战,因为Postgres比MySQL支持了更多的SQL的标准格式,在POstgres中直接使用SQL的dump结果是不可能的。但是,SQL语法相当相似,因此对于我来说,这并没有花费太多的时间。
对MySQL的Dump结果进行转换
首先,要求你的主机提供商为你的帐号建立一个数据库。和MySQL数据库一样,Postgres的数据库也由一系列包含实际数据的数据表组成。然后,使用mysqldump命令为你的MySQL数据库做一个dump文件。
mysqldump -u username -p databasename
> sqldump.txt
使用FTP将整个dump文件下载下来。现在在你的计算机上有了这个SQL文件,你可以将其转换成Postgres可以导入的文件。
首先,从dump文件中剪切所有的MySQL的CREATE
TABLE查询,并将其粘贴到一个单独的文本文件中。下一步是使用Postgres可以理解的语言重新对数据表进行定义。
Postgres建立表的SQL和MySQL非常类似,但不完全一样。下面是一个例子:
CREATE TABLE practicetable
{
someID SERIAL,
time TIMESTAMP
DEFAULT now(),
name VARCHAR(50),
address
VARCHAR(50),
city VARCHAR(50),
state VARCHAR(2),
country VARCHAR(3)
DEFAULT '
postlcode VARCHAR(15),
age smallint,
lattitude real,
longitude real,
somebool boolean,
message textitem
};
在一个Postgres的表定义中,字段名后面必须跟着字段类型。在上面的例子中我们给出了一些最普通的字段类型,你还可以在有关Postgres数据类型的文档中找到全部的字段类型的列表。对于不同的任务,Postgres在字段类型方面有多种选择,并可以存储各种类型的数据,从Internet地址到货币信息到几何对象的定义。这儿简要地介绍最常用的几种数据类型。
SERIAL类型的字段和MySQL中的自增唯一ID等价。当你在你的数据表中定义了一个SERIAL类型的列后,SERIAL的自增功能会被自动添加到数据库。当自增功能不能适应实际需求时,我们可以自定义唯一ID的逻辑。从MySQL向Postgres转输数据时,默认的功能已经足够了。
和字面上的意义一样VARCHAR类型是一个可变长度的文本字段。字段的长度由括号中的数值定义。例如,VARCHAR(5)定义了一个最多可包含5个字符的文本字段。
SMALLINT、INT和BIGINT用来定义整型字段。SMALLINT字段可存储数值范围为-32768到+32767(实际的范围可能会稍微受到你的计算机类型的影响,上面的范围适用于最普通的系统)。INT字段可存储数值范围为-2147483648到+2147483647。而BIGIN字段类型可存储任何更大的整数,它没有范围的限制。
REAL字符类型是一个包含十进制小数的实数。它可以精确到小数点后六位。DOUBLE PRECISION字段与此相类似,但是它可以精确到小数点后15位。
BOOLEAN字段是真或假、1或0。这和MySQL中相似。
TIMESTAMP字段和MySQL中的情况类型。每次记录更新时,timestamp被更新为当前的日期和时间。Postgres的时间字段还可以包含时区信息。有关Postgres时间数据的更复杂的应用,请参看PostgreSQL文档的日期和时间。
建立数据表
当你使用SQL文件在Postgres中建立数据表时,请检查在每一个CREATE TABLE查询的最后是不是都以分号结束 - 这对于Postgres是不可省略的。使用telnet这样的工具连接到你的Web主机,然后用下面的方法建立数据表。
首先,用一个文本编辑器打开你的表定义文件。然后登录到你的主机,并输入psql运行Postgres交互终端。默认的用户论证方式是使用你的telnet/FTP用户名作为你的Postgres帐号。这使得不需要你输入用户名和口令,Postgres就能自动鉴别你的身份。你的Web主机也许不是采用的这种方式,在这种情况下,你需要为psql程序带入参数:psql -d databasename -U username
-W。-d用来指定数据库,-U指定用户名,而-W要求psql提示你输入一个口令。
当你成功地运行了psql以后,将每个CREATE TABLE查询单独地粘贴到psql中并按回车键。如果在你的SQL语句中有错误,psql会给出相应提示。通过逐一地加入每一个表,你会得到每一个表的调试信息,这样做起来相当简单。
如果,在你输入了表的定义之后,你发现遗漏了一两个字段,有两种方法能解决这个问题。你可以使用ALTER TABLE命令,或者是使用DROP
TABLE删除这张表,然后重新生成。如果你使用第二种方法,你会看到一个警告以验证你是不是真的想要删除表。
要使用DROP TABLE命令,只需要输入DROP
TABLE practicetable;。这会删除我们刚才定义的表。但是当你对这个表重新进行定义时,你会发现一个错误。这是因为在删除一个表时并不相应地删除这个表中SERIAL类型字段的序列。这些遗留下来的序列会在你重建表时引起错误。要解决这个问题,你必须在删除表之前使用DROP SEQUENCE sequencename;删除相应的序列。而且有件很讨厌的事,那就是序列名并不就是SERIAL列的名字。当你定义一个SERIAL类型的字段时,Postgres会自动生成这样的序列名:tablename_colname_seq。在现在的这种情况下,DROP SEQUENCE 语句将会是这样的:DROP
SEQUENCE practicetable_someID_seq;。现在你就可以删除这张表并重新生成它了。
在添加完这些表之后,你可以输入z对这些表进行复查。而输入q将会退出psql。现在剩下来的就是准备输入到Postgres中的数据了。
处理Dump文件
因为MySQL保留了绝大多数的SQL语言的标准,从一个SQL的dump文件中导出实际数据并不是太困难的。然而,在我们使用Postgres对这个文件进行处理前,我们还是需要作一些编辑工作。
对于数据记录,在MySQL和Postgres之间的主要区别是对引号的处理。在Postgres中,字符串变量(包含文本的变量)必须由两个单引号引出。而在MySQL中,你还可以使用双引号,但是幸运的是,在mysqldump程序中程序中使用的是单引号,这刚好与Postgres一致。然而,MySQL和Postgres还有一个地方不同,那就是对字符串中出现的引号的处理。在MySQL中使用"",而在Postgres中使用"。使用你的文本编辑器并通过替换功能将其中所有的""替换为"。有趣的是,Postgres和MySQL都使用''来表示单引号,这使得我们免去了一个麻烦。
导入到Postgres中
当你整理好SQL dump文件后,将这个文件上载到你的Web主机中,就如同你当初建表那样登录到主机,转到SQL dump文件存放的目录。启动psql,不过这次你必须使用另一个命令行参数:psql -f sqldump.txt,这儿的sqldump.txt就改为你的SQL dump文件的文件名。这个命令会将全部的SQL文件导入到适当的Postgres数据表中。在此之前,你也许还需要其它的一些命令行参数以使得psql可以对你的身份进行验证。如果发生了错误,psql会告诉这是由什么引起的。找到文件中的这一部分,找到问题并手工解决它。我当初是没有遇到任何问题,我差不多准备结束工作了。但是,很快我注意到另一个问题。
在我开始使用我的新的Postgres驱动的站点时,我偶然地发现MySQL和Postgres之间另一个不兼容的地方。SERIAL类型的自增字段所使用的Postgres的序列,它从1开始,并在每次有一个SERIAL类型字段的记录插入时加一。然而,在我导入MySQL的dump文件时,这个dump文件中的SQL将这个值定义为整型主键。我当时的情况是,我有一个到唯一主键已经到了60,而序列仍然是1。于是我的每一个插入命令都没法成功,因为根据序列产生的不是唯一ID。我当时用了一个很笨的方法解决这个问题,那就是运行了60次INSERT语句以将序列调整为适当的值,但是后来有一个熟悉Postgres的朋友教给我一个好方法。下面就是他所讲的方法:
使用telnet这样的终端程序连接到你的主机。然后启动psql程序。首先,确定表中ID的最大值。这可以用SELECT fieldname FROM tablename
WHERE fieldname=MAX(fieldname);。然后使用DROP SEQUENCE table_colname_seq;删除有问题的序列,这儿table是表名,而colname是SERIAL字段的列名。然后使用CREATE SEQUENCE table_colname_seq
START 61;重建序列,当然这儿的61应根据你的实际情况进行修改。
安装一个图形界面的工具
当我成功地将数据导入到Postgres后,我还需要让我的不懂Unix的伙伴能够操作数据库中的数据。当初的MySQL我是使用的phpMyAdmin,这是一个很好用的工具,它能够在线的显示和编辑数据库。幸运的是,已经有了“Postgres版本”的phpMyAdmin,那就是phpPgAdmin。
phpPgAdmin的安装非常简单。首先,从phpPgAdmin网站下载最新的版本,然后将其放到你的Web主机上的你所能访问的地方。使用telnet这样的工具登陆到主机。到phpPgAdmin.tar.gz文件所在的目录,输入tar -xzvf phpPgAdmin.tar.gz对程序进行解压(这里只是举个例子,你的文件名可能不一样)。下面一件事就是将解压生成的新的子目录移到合适的地方,并阅读README文件。
最后,用你的Web主机上的文本编辑器打开config.inc.php。这个文件中包含了对phpPgAdmin的配置。将这个文件配置好你就可以通过浏览器使用PhpPgAdmin了,它会提示你输入用户名并登录,通过这个程序你管理你的数据库将变得非常简单。
在2001年六月,我将我的站点的后端数据库从MySQL改为PostgreSQL。这仅仅花了我一天的时间。从那时起,Postgres一直工作得很正常,这证明我作出了正确的选择。
在这篇文章的上半部分,我们讲解了如何将数据从MySQL转换到Postgres。我们还说明了我改用Postgres的理由。而在下半部分,我们将指导你完成另一半的转换工作并讲解如果将已编制好的用于MySQL的PHP代码改用于Postgres。
准备
在你做这项工作之前,你需要一些准备工作。你的编程技巧和网站的复杂性将对程序的转换影响很大。为了防止在工作中发生错误,在你的Web服务器上为你的站点作一份备份肯定是必要的。我当时的解决方法是设置了一个指定的子域来测试我正在修改的代码。因为站点会根据一个配置文件中的一些基本配置自动处理URL,所以这件工作很简单。你也许不能这样做,但是不管怎样,你都需要一个另外的空间来放置你正在改动的程序,这可以是一个子域、你的站点的一个子目录、另一个Web主机也可以是一个本地的开发机器。
注意:如果你在另一台机器修改你的程序,你必须确保这个Web服务器的配置和你正在使用的站点一样。如果你的Web主机只允许从本地连接Postgres,你也许将不得不将你的Postgres数据拷贝到开发机器上。要得到更多有关拷贝一个Postgres数据库的信息,可以参看http://www.postgresql.org/上的pg_dump和pg_restore。
做好了拷贝并且经过测试它可以正常工作之后,你可以着手对程序进行解剖了。
PHP手术:代码解剖
如何你之前没有一个中心的配置文件,那么首先建立它。这将减轻我们的很多工作量,而且也使得我们的改动可以即时对整个站点发生作用。这个配置文件也该是不允许其它人通过网站访问的,否则这将是一个安全隐患。PHP的默认包含目录是/usr/local/lib/php/。你需要在你的Web主机上建立这样一个目录并不允许通过网站访问。你还得确保这对于使用同一Web主机的其他人也无法读取这个目录。如果你的站点和我的一样,包含了一个标准的头文件,你可以将你的Postgres设置文件放在那儿,这个配置文件将会是这样的:!<?php
// /usr/local/lib/php/mysite/configfile.php
$hostname = "localhost";
$username = "username";
$database = "mydb";
$password = "mypasswd";
?>
<?php
//标准的HTML头
include("mysite/configfile.php");
?>
<html>
<head>
<title>Bill's Kazoos</title>
...
</head>
<body>
...
有了这样一个前面这样的中心配置文件,改变数据库系统将变得非常简单。现在可以着手改程序了。
连接和查询
PHP有关MySQL的函数和Postgres很相似,所以转换代码的工作并不是太复杂。事实上,你可以自己写一些函数来完成这种转换。在做这项工作之前,让我们来看看两者的不同:
要连接到MySQL数据库需要用到两个命令:
$connection_id=mysql_connect($hostname,
$username, $password);
mysql_select_db($database, $connection_id);
如果你使用一个持久的连接:
$connection_id=mysql_pconnect($hostname,
$username, $password);
mysql_select_db($database, $connection_id);
然而,PHP连接Postgres的函数只需要一个字符串参数,与MySQL函数不同,这是一个复合的字符串参数。Postgres函数也需要你指定使用的数据库。下面是一个示例:
$connection_id=pg_connect("host=$hostname
dbname=$database user=$username
password=$password");
一个持久的连接执行同样的工作,只是需要调用pg_pconnect()函数。
PHP的MySQL和Postgres的查询函数同样有点不同。MySQL的查询函数是$result_data = mysql_query("query
goes here",$connection_id);,而Postgres的查询函数是这样的:$result_data = pg_exec($connection_id,
"query goes here")。
正如你所看到的,PHP对MySQL和Postgres和连接和查询的支持区别并不大,但是函数参数的不同还是需要我们慢慢处理。要提高速度,你可以写一些函数使得Postgres可以使用和MySQL一样的函数来连接。如果你有了包含这样一种函数的中心库,你可以将这些函数也放在那里。你也可以将它们放置在我们前面所提到的配置文件中,因为它会自动地被每个页面包含。
//连接到数据库
function postg_connect($hostname, $username, $password, $database)
{
return pg_connect("host=$hostname dbname=$database
user=$username
password=$password");
}
//如果你仅仅使用一个数据库,你最好将这些变量放到你的配置文件中
function postg_autoconnect()
{
global $hostname,
$username $password $database;
return pg_connect("host=$hostname dbname=$database
user=$username
password=$password");
}
//查询函数
function postg_query($query, $connection_id)
{
return pg_exec($connection_id, $query);
}
不管你是否使用这种函数,代码转换的工作总是相当简单的。Postgres几乎可以支持所有的以前在MySQL下使用的SQL查询,但是你可能还是要整理一下你的查询。因为在不同的地方数据模型和代码会有一些不同,我在这里不想详细解释这个问题。然而,对SQL的转换并不困难。首先转换代码,然后看看有哪些查询无法在Postgres中正常执行。对MySQL语言指南和PostgreSQL用户向导中的相关问题进行比较,你也许不能在Postgres中找到所有与MySQL同等的功能,但是Postgres支持所有的通用的功能。
现在你已经把连接和查询的代码改好了,下面的问题可能要稍微复杂一点。PHP中MySQL和Postgres对结果集处理的不同可能需要你对代码作更多的变动。
让我们来看看对结果的处理
PHP的Postgres对结果的处理并不完全和MySQL一一对应;它们有一些微小的不同。这些微小的差别可能只需要对代码作微小的改动,但是也可能是一个挺复杂的问题。
首先,让我们看看MySQL和Postgres有哪些相似的地方。下面这个列表介绍了普通的MySQL结果处理函数和它们相对应的Postgres函数:
MySQL
mysql_num_rows($result) 返回结果集的行数,这仅对SELECT语句有效
mysql_affected_rows($result) 返回在一个INSERT、UPDATE或DELETE查询中受到影响的行数
mysql_fetch_object($result) 取得一行的数据并将其作为一个对象返回。字段名对应于类的属性名。(即$field1 = $var->field1;)这个函数保存了一个内部变量以保证每次调用时可以返回下一行。
mysql_fetch_row($result) 这个函数以一个数组的形式返回结果集的一行。这个值可以通过一个从0开始的数组值获得。(即$field1 =
$var[0];)。同样,这个函数保存了一个内部的计数器以保证每次调用时可以返回下一行。
mysql_fetch_array($result) 这个函数和另外两个fetch函数基本相同,只是它以一个联合数组的形式返回一个行($field1 = $var["field1"];)。
Postgres
pg_numrows($result) 与对应的mysql_num_rows($result)完全一样
pg_cmdtuples($result) 与对应的mysql_affected_rows($result)完全一样
pg_fetch_object($result, $row) 获得结果集中的指定行。必须使用$row参数,而且没有一个内部的计数器。除此之外,它与mysql_fetch_object($result)完全相同。
pg_fetch_row($result, $row) 以一个数组的形式返回结果集中的指定行。同样必须使用$row参数,而且没有一个内部的计数器。
pg_fetch_array($result, $row) 与对应的mysql_fetch_array($result)基本一样,只是需要指定行,并且缺少一个内部的计数器。
有关这些函数的更详细的信息,请参看PHP.Net上的PHP文档。
PHP对MySQL和Postgres支持的最本质的不同在于对结果集的阅读。MySQL自动决定获取哪一行,而Postgres必须指定要阅读哪一行。下面是一些例子,你也可能会遇到这些问题,对于它们有两个解决方案。
//第一个普通的例子:
$rslt=mysql_query("SELECT *
FROM blah", $connection_id);
while($value=mysql_fetch_array($rslt))
{
//完成数据处理工作
}
//对于Postgres,这样的代码无法执行,因为他们需要指定行号
//代码将作如下改动(如果你没有使用前面讨论的函数):
$rslt=pg_exec($connection_id,
"SELECT * from blah");
$limit=pg_numrows($rslt);
for($rownum=0;$rownum<$limit;$rownum++)
{
$value=pg_fetch_array($rslt, $rownum);
//完成处理工作
}
在上面的例子中,你可以注意到Postgres的代码要稍微长一点,这是因为你必须指定行号。然而,如果你使用了你编写的自己的计数函数,问题就变得很简单了。这儿是一个添加了这样一个函数的有用的文件。请注意在postg_query()中使用了三个全局变量。
// /usr/local/lib/php/mysite/configfile.php
$hostname = "localhost";
$username = "username";
$database = "mydb";
$password = "mypasswd";
//内部计数变量
$fetch_array_counter=0;
$fetch_object_counter=0;
$fetch_row_counter=0;
//处理连接到PostgreSQL数据库的函数
function postg_connect($hostname, $username, $password, $database)
{
return pg_connect(host=$hostname, dbname=$database
user=$username,
password=$password");
}
//不需要任何参数的连接
function postg_autoconnect()
{
global $hostname,
$username, $password, $database;
return pg_connect(host=$hostname, dbname=$database
user=$username,
password=$password");
}
//查询函数
function postg_query($query, $connection_id)
{
//将全局变量设置为0
global $fetch_array_counter, $fetch_row_counter,
$fetch_object_counter;
$fetch_array_counter=$fetch_row_counter=$fetch_object_counter=0;
return pg_exec($connection_id, $query);
}
//pg_fetch_array()置换
function postg_fetch_array($rslt)
{
global $fetch_array_counter;
$fetch_array_counter++;
//计数器加一
return pg_fetch_array($rslt, $fetch_array_counter);
}
//pg_fetch_row()置换
function postg_fetch_row($rslt)
{
global $fetch_row_counter;
$fetch_row_counter++;
//计数器加一
return pg_fetch_row($rslt, $fetch_row_counter);
}
//pg_fetch_object()置换
function postg_fetch_object($rslt)
{
global $fetch_object_counter;
$fetch_object_counter++;
//计数器加一
return pg_fetch_object($rslt, $fetch_object_counter);
}
?>
当然,如果你在同一个循环中同时对两个结果集进行操作,上面的函数将无法正常地工作,因为它们只使用了一个内部的计数器。如果因为某种原因,你需要同时阅读几个结果集,你将不得不使用传统的Postgres方法。
另一个你可能遇到的问题是在Postgres中没有与MySQL中mysql_insert_id()相应的函数,这个函数反映最后的INSERT查询的索引值。PHP文档往往会让读者误以为pg_getlastoid()会完成这项工作,但是实际情况并不是这样。缺少这一样一个函数并不一个无法逾越的障碍,你可以利用Postgres的SEQUENCE系统来实现这样的功能。
幸运的是,要获得最后的ID是相当容易的。你可以通过SQL获得Sequence信息,因此你可以用这个语句来实现mysql_insert_id()的功能:
function postg_insert_id($tablename,
$fieldname)
{
global connection_id;
$result=pg_exec($connection_id,
"SELECT last_value FROM ${tablename}_
${fieldname}_seq");
$seq_array=pg_fetch_row($result,
0);
return $seq_array[0];
}
因为Postgres使用了一个特别的命名系统来命名序列,我上面建立的这个函数需要指定表名和字段名。调用这个函数,会返回你的表中的任意SERIAL字段的最后一个序列值,即使在表中有不止一个这样的字段。
经过上面的这些处理后,你已经可以在你的MySQL站点上成功地运行PostgreSQL了。然而,这仅仅是第一步;如果你想了解更多,继续看下去,你会看到一些有用的PostgreSQL的资源。
更进一步的资源
从PostgreSQL非FAQ文档站点你可以看到最初的和最重要的PostgreSQL资源。这个有价值的资源可以向你提供大量的书籍、参考、技术参考甚至于具体的工作。它同样会涉及将后端数据库从MySQL 改为Postgres,此外对于使用PostgreSQL的其它问题它也可以给你帮助。
另一个有价值的PostgreSQL提供的资源是PostgreSQL交互文档。其中涉及到使用PostgreSQL的很多问题。
Xach Beane,因为其在The GIMP(一种图形处理软件)上的工作而著名,他也编写了关于将MySQL dump转换为Postgres dump的书写一个脚本。他的程序可以更为全面地处理这些问题。不过,对这些问题的处理并不是十分完美的,因此你得小心地使用它。
Dobrica Pavlinusic也编写了一个程序以处理从MySQL到Postgres的转换。同样得提醒你这个程序的处理仍然不是十分完美的,因此还是少不了象我们上面所讨论的手工的修改。
从这儿你可以找到一个非常完全的PostgreSQL&PHP指南。它会从安装开始介绍PostgreSQL的使用。这个指南非常值得初学者阅读。
Bruce Momjohan编写了一本关于PostgreSQL的名为PostgreSQL: Introduction and Concepts的书,已由Addison Wesley出版。你甚至还可以在线阅读!
最后,OpenDocs也出版了实用PostgreSQL。这本书在2001年十月份出版,你可以从the OpenDocs
Linuxports.com站点阅读。
将你的站点的后端数据库从MySQL改为PostgreSQL是一个明智的选择。转换工作肯定会耗费时间和精力,但是经过这些努力后,你的站点可以拥有一个更完美的数据库系统。就象我一样,你也会觉得这项工作是有意义的