将 PHP 应用程序从 MySQL 移到 DB2,第 3 部分: 转换 PHP 代码

从 IBM 内部网应用程序案例研究中汲取经验

了解为何将 PHP 应用程序迁移到 DB2®,如何计划迁移、执行和支持它,以及如何根据 IBM 内部网应用程序案例研究的经验来处理潜在风险。这个由 4 部分组成的系列文章分享了成功地将关键任务型 PHP 内部网应用程序从 MySQL 迁移到 DB2 的经验,这个案例被 IBM 全球 4,000 名用户使用,以支持 ibm.com 内容制作。第 3 部分介绍转换 PHP 代码的步骤。

Daniel Krook, 软件工程师, IBM

Daniel Krook 是大纽约地区的 IBM/Open Group 高级认证 IT 专家。他有十多年的 web 应用程序开发经验,目前他正在使用 Java EE、DB2、REST 和移动技术为 IBM 构建云基础架构。他拥有 PHP、Java EE、BlackBerry、DB2 和 Solaris 的认证,还为 IBM developerWorks 撰写了 PHP 相关的文章,合作撰写了 IBM Redbook “Developing PHP Applications for IBM Data Servers”。


developerWorks 投稿作者

Yan Li Mu, IT 架构师, IBM

Yan Li Mu 是在中国大连工作的 IT 架构师。他在 web 应用程序开发方面有超过八年的经验,特别是在 Java EE 技术、PHP 和数据库开发方面。



Mark Nusekabel, 高级 IT 架构师, IBM

Mark Nusekabel 是居住在佛罗里达地区 Tampa Bay 的 IBM/Open Group 高级认证 IT 架构师,他在信息技术方面有二十多年的经验,目前他正在使用 JavaScript、PHP、Java 和 DB2 构筑内部工具。他拥有电子商务解决方案、Java 和 XML 方面的认证。



2011 年 11 月 28 日

免费下载:IBM® DB2® Express-C 9.7.2 免费版 或者 DB2® 9.7 for Linux®, UNIX®, and Windows® 试用版
下载更多的 IBM 软件试用版,并加入 IBM 软件下载与技术交流群组,参与在线交流。

系列简介

MySQL 是当前最常用的数据库服务器,可以和 PHP 编程语言一起用于构建动态 Web 应用程序。但是,DB2 是另一个 PHP 支持的流行数据库,比 MySQL 有明显优势,这使得它最终成为许多应用程序的理想之选。

该系列文章说明了为什么将 PHP 应用程序迁移到 DB2,如何准备迁移、执行和支持它,以及如何根据作者最近的迁移经验来处理潜在风险。本文提供了许多代码、配置样例以及帮助项目顺利进行的资源指南。

利用成功的真实转换中得来的示例和经验,您会了解到这是一个简单的项目,有很多这方面的文档记录,该项目能提供一些显而易见的好处。

该 4 部分的系列文章分享了成功地将生产级的关键任务型 PHP 内部网应用程序从 MySQL 迁移到 DB2 的经验,该案例被 IBM 全球 4,000 名用户使用,以支持 ibm.com 内容制作。

  • 第 1 部分介绍了迁移准备的步骤。
  • 第 2 部分介绍了迁移数据库的步骤。
  • 第 3 部分介绍了转换 PHP 代码的步骤。
  • 第 4 部分介绍了部署和支持应用程序的步骤。

本系列内容简介

本系列文章旨在让您了解将 PHP 应用程序从 MySQL 迁移到 DB2 通常需要什么,什么样的资源对您有帮助,以及在 2010 年初 IBM 项目团队如何完成任务。

如果您对 MySQL 到 DB2 的迁移做过调查,那么您可能已经从产品资料和性能基准测试中了解到了 DB2 所能提供的价值,或者可能已经从 DB2 文档或 IBM Redbooks® 中的比较(包括MySQL to DB2 Conversion Guide,参见 参考资料)中了解到它的一些功能。

您可能还知道 DB2 Express-C 是免费的、功能齐全的关系数据服务器,可在 Cloud 或 Amazon EC2 中使用 IBM Smart Business Development and Test 轻松安装和评估它。这些资料的链接可以在 参考资料 部分中找到。

本文为您提供了如何成功执行实际迁移的真实示例,该示例就是 2010 年 IBM 常用的 PHP 内部网应用程序,用于支持 ibm.com Web 网站各部分中发布的内容的日常管理。

在阅读完本系列之后,您将能够完成类似的迁移,了解需要执行的工作项目的时间和依赖关系,预测潜在风险,了解在何处寻找对各个步骤的支持。所有这些都会使您更坚定地选择 DB2,并在当前构建于 MySQL 上的 PHP 应用程序中最大程度地利用它。

未提及的内容

本系列文章旨在分享从 MySQL 到 DB2 的内部 IBM 迁移中得到的经验,为您提供执行类似迁移的可用资源信息。本文并不是一个全面的、适用于所有场景的迁移指南。

为了确定适合您的方法,请参阅 MySQL to DB2 Conversion Guide 或者联系 Software Migration Project Office (SMPO) 获取免费的迁移估算。参考资料 部分提供了相关链接。


代码迁移简介

本文介绍了案例研究中采取的五个主要步骤,将 PHP 应用程序代码从使用 MySQL 驱动程序和语法迁移到使用 DB2。如果认为需要的话,在进行转换之前,请参考 将 PHP 应用程序从 MySQL 移动到 DB2,第 1 部分:为您的迁移做好准备,了解整个迁移过程的步骤安排。

第 1 步:开始进行代码迁移的第一步
  • 确保 PHP 配置已经更新,能支持 DB2
  • 更新单个 SQL 语句,能支持 DB2 语法
  • 如果需要的话,使用 DB2 用户定义的函数模拟本机 MySQL 函数
  • 如果需要,请将逻辑部分从 SQL 转到 PHP 中
第 2 步:开始进行代码迁移的第二步
  • 检查用来支持适当的隔离级别所需的改动
  • 将查询重新组织成逻辑工作单元,以获取更好的集成性并提稿性能
第 3 步:与相关利益者进行最初的业务用例测试
  • 请利益相关者执行那些在原有系统中已经熟悉的用例
  • 捕捉测试错误,让程序人员将其作为缺陷进行分析并修复
第 4 步:解决瓶颈问题,并确定功能基线
  • 功能验证后,根据用户的反馈提高系统性能
  • 重点关注 DB2 能自动修复哪些故障,因为 DB2 就是引入的最大的变更
  • 通过观察操作系统资源的使用情况来解决 PHP 性能瓶颈
第 5 步:评估代码迁移基线
  • 不断重复上述步骤后,就可以宣告已完成代码转换
  • 备份系统,在控制系统中标记为里程碑
  • 做好准备,以便进行下一项任务:应用程序部署

了解现有的 PTT PHP 代码库样例案例研究

提示

如果需要,再次查阅 参考资料,它会在您的迁移过程提供帮助。以下资源对于这一步尤其重要:

  • 免费的 IBM Redbook MySQL to DB2 Conversion Guide 中的第 8 章和第 10 章。
  • 免费的 IBM Redbook Developing PHP Applications for IBM Data Servers 中的第 4 章和第 6 章。
  • developerWorks 文章 “Recommended reading list:DB2 for Linux, UNIX, and Windows application development”
  • Daniel Krook 博客中关于另一个 ISV 迁移项目中的个人经验的内容

当然,PHP 编程语言和 DB2 驱动程序扩展文档同样具有指导意义。

还可以选择利用云实现迁移过程。您可以使用 Amazon EC2 Linux and DB2 AMI,或者注册 IBM SmartCloud(以前称为 Development and Test on the IBM Cloud)(请参阅 参考资料)。

对于本文中的样例,Project Tracking Tool (PTT) 的源代码中包含数百个 PHP 文件。代码库包含函数库、数据转换对象和管理类中的面向对象代码,还有用来呈现用户界面的各种 HTML 模板片段和辅助文件。

PTT 数据库可供各种函数使用,将信息发布到 ibm.com 上。全世界超过 4,000 个用户通过 PHP Web 前台访问和修改 PTT 数据库。在任意时刻,系统中活跃用户都有几百人。

代码部署在单个 Apache Web 服务器上,它会将 mod_php 作为共享模块加载。

本例中,现有的代码将会为新的 DB2 系统进行更新,这主要是通过修改嵌入的 SQL 来完成。还需要做一些细微的修改,以更新 PHP 配置来使用 DB2 驱动程序并调整数据库连接代码,从而使用新的连接字符串。本文还介绍并实现了一些对应用程序架构的改进,以更好地达到模型-视图-控制器 (MVC) 结构模型,从而提升应用程序质量、结构和可维护性。


安装转换软件

在样例代码转换之前,需要在 Windows 工作站上安装以下的组件,用于进行转换。

源数据库的 MySQL 副本
为了验证代码的每一项修改,复制原有系统很重要,可将原系统的功能用作参考,与原系统逐一对照,验证新系统的变更。您可以使用与 将 PHP 应用程序从 MySQL 移到 DB2,第 2 部分:迁移您的数据 一样的系统。
在本地或测试服务器上安装了数据服务器驱动程序的 DB2。
安装 DB2 会在工作站上创建新的目标数据库。一般来说,不需要与生产环境中使用的版本一样,但为了确保所有功能都兼容,我们建议使用相同版本。在本文的示例中,安装了 DB2 Enterprise Server Edition Version 9.7.2。请确保数据服务器驱动程序能够提供必要的 PHP 客户端库。您可以使用与 将 PHP 应用程序从 MySQL 移到 DB2,第 2 部分:迁移您的数据 相同的系统。
在 ibm_db2 扩展版或 PDO_IBM (PHP Data Objects) 中内置的 PHP 版本
下载最新版的 Zend Server,并选择 DB2 扩展版选项,它需要安装额外的软件包。
IDE,如 Zend Studio 或 Eclipse PHP Development Tools(PDT)
使用可读取 PHP 的 IDE,例如 Zend Studio 或 Eclipse PHP Development Tools,这样可以简化 PHP 开发。由于您需要维护 PHP 应用程序,因此最好您已经有了用得很顺手的工具。

一定要记录下下配置决策和经验教训,以便在部署时重复使用这些步骤。还可以考虑在重要目标达成的关键里程碑处将 Windows 操作系统的快照保存为虚拟镜像,使用该镜像作为配置备份以及后续代码改进对比的基线。

如果想要获取物理机器配置的镜像,可以使用免费的 VMware vCenter Converter。或者,可以考虑使用云来处理这些手动变更。您可以使用 Amazon EC2 Linux and DB2 AMIs,或者注册 IBM SmartCloud(以前称为 Development and Test on the IBM Cloud)。通过使用虚拟计算机,您一开始就不必花费精力购买服务器硬件并安装操作系统和 DB2,这样可以节省时间、加快迁移过程,并给予您更大的信心,来测试这些最能满足您需求的配置。参考资料 中提供了这些产品的链接。


第 1 步: 开始进行代码迁移的第一步

代码转换过程中重要的第一步就是配置新的 PHP 和 DB2 基础架构,并翻译应用程序使用 PHP 和 SQL 访问数据的方式。这一步包含以下子步骤:

更新 PHP 驱动程序和配置

确保现有的 PHP 配置已作修改,能支持 DB2 驱动程序,匹配服务器数据库编码,并且能通过详细的警告和错误通知来提供诊断信息。

为了更新 PHP 代码能支持 DB2,通常需要更改代码中每个使用 ibm_db2 扩展函数的地方,或者更新实现 PDO (PHP Data Objects) 接口的 PDO_IBM 连接字符串。如果在基于 MySQL 的系统中使用 PDO 作为数据库抽象库,那么代码变动是很小的。只要修改连接字符串就行。但即使所用得是程序式的 ibm_db2 扩展,也很简单。只要在代码中将以 mysql_ 或 mysqli_ in 开头的函数替换成 db2_ prefixed 前缀就可以了。请参见 IBM RedbookDeveloping PHP Applications for IBM Data Servers(请参阅 参考资料)中第 6 章第 6.3 节中的函数映射表和代码样例。

这两个方法都依靠 DB2 客户端作为连接 DB2 服务器的桥梁,因此需要保证已配置 DB2 客户端,能匹配远程服务器上的设置。尤其要注意的是,确保 DB2 客户端的代码页与 DB2 服务器一致;否则,字符集会出错。例如,运行 清单 1 中所示的命令,将客户端编码设置为 UTF-8,这就样就匹配了 将 PHP 应用程序从 MySQL 移到 DB2,第 2 部分:迁移您的数据 清单中的 CREATE DATABASE 命令。

清单 1. 将 DB2 客户端代码页设置为 UTF-8
db2set db2codepage=1208

为了确保在安装运行时客户端的每个工作站或服务器上的设置都正确,请运行 清单 2 中的命令。

清单 2. 查看 DB2 配置变量
db2set -all

最后,在开发过程中,如果能捕获所有错误消息和警告,并将它们显示在浏览器上,这会对您有所帮助。在 php.ini 文件中,更新 清单 3 中所示的参数。请记住,在移动到生产环境时,请将它们改回简单的设置。本系列文章的第 4 部分会提供一个样例,在部署到生产环境时会使用更合适的错误报告机制。

清单 3. 在 php.ini 中设置错误控制参数
display_errors = On
error_reporting = E_ALL & ~E_NOTICE

转换 SQL 语法

下一步,更新单个 SQL 语句以支持 DB2 语法,或者修改它们,用稍微不同的方式访问数据并获得相同的结果。对于大多数应用程序,这一步骤中主要是大量的代码更新和验证测试。请参考 RedbookMySQL to DB2 Conversion Guide 中第 8.1 节 Data Manipulation Language differences and similarities,查看常见语法转换列表。以下提供了更多的案例研究应用程序中的情形。

SELECT 子句通配符行为
SELECT 语句使用通配符(星号字符)从多个表中选择除了所有列之外特定的列,这在 MySQL 中是合法的,但在 DB2 中是不允许的。在这种情况下,请将表名称(或别名)限定符前缀添加到通配符,指定要插入的每个表的每一列,或者简单地用一个通配符列出每个表的所有列。清单 4 显示了如何修改这些查询。
清单 4. 对比 MySQL 和 DB2 中合法的列和表通配符
-- In MySQL: 
SELECT R.NAME, * 
FROM USER U, ROLE R 
WHERE U.ROLE_ID = R.ID;

-- In DB2: 
SELECT R.NAME, U.* 
FROM USER U, ROLE R 
WHERE U.ROLE_ID = R.ID;

-- Or
SELECT R.NAME, U.ID, U.NAME, U.ROLE_ID, U.DEPART_ID 
FROM USER U, ROLE R 
WHERE U.ROLE_ID = R.ID;
SELECT 子句使用 LIMIT 设定最大结果集的大小
LIMIT 是 MySQL 中独有的、非标准的关键字,可以使用它来指定从查询中返回的最大行数值。DB2 使用 FETCH FIRST n ROWS ONLY 语法提供同样的功能。清单 5 显示了如何重写 DB2 查询。
清单 5. 在 MySQL 和 DB2 中设置从查询返回的最大行数值
-- In MySQL: 
SELECT * FROM ROLE LIMIT 10;

-- In DB2: 
SELECT * FROM ROLE FETCH FIRST 10 ROWS ONLY;
GROUP BY 子句
MySQL 允许使用 GROUP BY,且无需指定聚合函数没有对其执行任何操作的每个列的名字。但在 DB2 中,这是非法的,因为结果集会包含歧义结果,这让许多用户都无法接受。清单 6 显示了如何在 DB2 查询中明确设置列。
清单 6. 对比 MySQL 和 DB2 中合法的 GROUP BY 聚合函数
-- In MySQL: 
SELECT R.ID, R.NAME, COUNT(U.ID) AS NUM 
FROM USER U, ROLE R 
WHERE U.ROLE_ID = R.ID 
GROUP BY R.ID;
				
-- In DB2: 
SELECT R.ID, MIN(R.NAME), COUNT(U.ID) AS NUM 
FROM USER U, ROLE R 
WHERE U.ROLE_ID = R.ID 
GROUP BY R.ID;

-- Or
SELECT R.ID, R.NAME, COUNT(U.ID) AS NUM 
FROM USER U, ROLE R 
WHERE U.ROLE_ID = R.ID 
GROUP BY R.ID, R.NAME;

清单 6 中,您可以更新 GROUP BY,因为 R.ID 和 R.NAME 在表中都是唯一的键。清单 7 显示了另一种 GROUP BY 查询,它不如 清单 6 中的查询容易处理。

清单 7. 需要针对 DB2 修改 MySQL 中有歧义的 GROUP BY
-- In MySQL: 
SELECT R.ID, R.NAME, COUNT(U.ID) AS NUM 
FROM USER U, ROLE R 
WHERE U.ROLE_ID = R.ID 
GROUP BY R.NAME;

本例中,R.ID 是表中唯一的键,而 R.NAME 不是。如果 ROLE 表中有重复的 R.NAME 值,则不能使用 MIN(R.ID) 替代 R.ID,并且无法将 R.ID 添加到 GROUP BY 子句中。如何转换这个 SQL 取决于您想要什么样的结果。清单 8 显示了适用于该情形的一些选项。

清单 8. DB2 中已翻译的 GROUP BY 查询
-- In DB2: 
-- Option 1, if you want to get same result as what you get in MySQL, the SQL is: 
SELECT (SELECT RL.ID FROM ROLE RL WHERE RL.NAME = R.NAME FETCH FIRST 1 ROW ONLY) AS ID,
R.NAME, COUNT(U.ID) AS NUM 
FROM USER U, ROLE R 
WHERE U.ROLE_ID = R.ID 
GROUP BY R.NAME;
				
-- Option 2, if role name is same, treat it as same role. In this case, 
-- the result is a little different from MySQL version in that there is no R.ID:
SELECT R.NAME, COUNT(U.ID) AS NUM 
FROM USER U, ROLE R 
WHERE U.ROLE_ID = R.ID 
GROUP BY R.NAME;
				
-- Option 3, if role ID is different, treat them as different role. 
-- In this case, the result is totally different than the MySQL version:
SELECT R.ID, R.NAME, COUNT(U.ID) AS NUM 
FROM USER U, ROLE R 
WHERE U.ROLE_ID = R.ID 
GROUP BY R.ID, R.NAME;
MySQL 中提供的是 REPLACE INTO,而 DB2 中提供的是 MERGE
MySQL 提供了 REPLACE INTO 子句。DB2 提供了 MERGE 子句来实现类似但不完全相同的用途。为了达到相同的效果,请创建一条新记录,如果它具有同样的键或唯一的值,则用它替代现有的记录。清单 9 显示了如何检查表中的唯一值,以及如何更新或插入值。
清单 9. 针对 DB2 转换 MySQL 的 REPLACE INTO 语法
-- In MySQL: 
REPLACE INTO ROLE (ID, NAME, DESCRIPTION)
SELECT ID, NAME, DESCRIPTION 
FROM ROLE_TMP;

-- In DB2: 
MERGE INTO ROLE R
USING (SELECT ID, NAME, DESCRIPTION FROM ROLE_TMP) RT
ON (R.ID = RT.ID)
WHEN MATCHED THEN
UPDATE SET (ID, NAME, DESCRIPTION) = (RT.ID, RT.NAME, RT.DESCRIPTION)
WHEN NOT MATCHED THEN
INSERT (ID, NAME, DESCRIPTION) VALUES (RT.ID, RT.NAME, RT.DESCRIPTION);
JOIN 子句
当对两个表执行外部连接并从第三个表检索数据时,要在 DB2 中设置外部连接中连接到 join 关键字的表。MySQL 允许以任何顺序列出它们。清单 10 显示了所需的变更。
清单 10. 对比 MySQL 和 DB2 JOIN 语法
-- Both of the following work in MySQL: 
SELECT * 
FROM USER U, ROLE R 
LEFT JOIN DEPARTMENT D 
ON U.DEPT_ID = D.ID;

-- Or
SELECT * 
FROM ROLE R, USER U 
LEFT JOIN DEPARTMENT D 
ON U.DEPT_ID = D.ID;

-- Only this one works in DB2: 
SELECT * FROM ROLE R, USER U 
LEFT JOIN DEPARTMENT D 
ON U.DEPT_ID = D.ID;
转义字符
在 MySQL 中,字符序列 \ 表示一个反斜杠转移单引号 ',但在 DB2 中,单引号必须要再加一个单引号来实现转义 ''(即两个单引号)。清单 11 显示了一个转义字符串序列,它使用了一个引号作为省略号。
清单 11. MySQL 使用反斜杠作为单个引号转义,而 DB2 再使用了一个引号
-- In MySQL: 
SELECT * FROM ROLE 
WHERE DESCRIPTION = 'It\'s a super admin role';

-- In DB2: 
SELECT * FROM ROLE 
WHERE DESCRIPTION = 'It''s a super admin role';
在 DB2 中无法检查范围
MySQL 在 SELECT 子句中会经检查数据类型的范围,但 DB2 无法检查范围。因此 清单 12 中查询 2月 1 日到 2 月 30 日的 SQL 语句在 DB2 中无效,但在 MySQL 是有效的。
清单 12. MySQL 不会针对数据类型检查 WHERE 子句中的值范围
-- MySQL allows you to specify February 30th as part of the range 
SELECT * FROM USER 
WHERE BIRTHDAY BETWEEN '1980-02-01' AND '1980-02-30';
插入到非默认值的 NOT NULL 列中
在 MySQL 中,如果要执行一个不提供 NOT NULL 列值的 INSERT 语句,此命令没有问题。MySQL 会自动提供一个默认值,即使您在创建表格时也没有为此列定义默认值。但在 DB2 中,如果未定义该值,则必须在 INSERT 语句中为 NOT NULL 列提供值。清单 13 中的 SQL 语句显示了不同的行为。
清单 13. MySQL 和 DB2 以不同方式处理 NULL 值的插入
-- Works in both MySQL and DB2 
CREATE TABLE TEST1 (ID INTEGER, NAME VARCHAR(20) NOT NULL); 

-- Works in MySQL, doesn't work in DB2
INSERT INTO TEST1 (ID) VALUES(1); 

-- Works in both MySQL and DB2 				
CREATE TABLE TEST2 (ID INTEGER, NAME VARCHAR(20) NOT NULL DEFAULT ''); 

-- Works in both MySQL and DB2
INSERT INTO TEST2 (ID) VALUES(1);

使用新的 DB2 兼容模式

您可以通过使用用户定义函数来模拟某些 MySQL 语法,还可以对 9.7.2 以后版本的 DB2 设置一些兼容模式。通过阅读了解有关 Antonio Cangiano 博客上启用 LIMIT 和 OFFSET 语法 的更多信息。

将 MySQL 函数转换为 DB2 中类似的函数
除了 MySQL 特定的 SQL,您还可以使用内置的 MySQL 函数。可以使用 SQL 标准函数或 DB2 中具有相同功能的函数来替换它。请参考附录 A:MySQL to DB2 Conversion Guide Redbook 中的映射 MySQL 内置函数和操作符(请参阅 参考资料)。

根据以上所述,如果找不到 DB2 中具有相同功能的函数,那么可以重写 SQL,以不同的方式访问数据。或者,您可以在 DB2 中模拟函数或者将逻辑移到 PHP 代码中,如以下章节所示。

创建用户定义的函数来模拟 MySQL 功能

如果更新 DB2 语法中的 SQL 语句也不能获得与 MySQL 查询相同的结果,那么您可以在 DB2 SQL 中以用户定义函数 (UDF) 的形式实现一个工作区,用它来模拟内置的MySQL 函数。

样例场景中有几处使用 MySQL 和 PHP 的地方,在通过更新应用程序来使用 DB2 时,需要使用不同方法来实现。尤其是,可以将一些功能从 MySQL 移动到 PHP 中,例如日期翻译。或者您可以模拟 DB2 中的函数(例如创建 DB2 中的用户定义函数),获得与 MySQL 中一些函数近似的功能,包括 UNIX_TIMESTAMP() 和 NOW()。MySQL to DB2 Conversion Guide Redbook 中的附录 B(请参阅 参考资料)解释了二者的不同之处,并且给出了解决变更的方法。Daniel Krook 的来自客户迁移经验的技巧也很有用(请参阅 参考资料)。

DB2 的 DML 与 MySQL 不同。本文讲述了如何将嵌入式 SQL 从 MySQL 转换到 DB2。尽管如此,不需要将所有语法从 MySQL 转换到 DB2,尤其是 MySQL 内置函数。MySQL to DB2 Conversion Guide Redbook 中的附录 A “映射 MySQL 内置函数和操作符”(请参阅 参考资料)列出了很多 MySQL 内置函数。通常情况下,可以将很多函数都转换为 DB2 UDF。您需要确定哪些常用内置函数需要转换。对于样例场景,需要使用 DB2 UDF 转换 MySQL 中常用的函数或寄存器。以下是需要转换的 MySQL 函数和寄存器。

  • CURRENT_DATE() / CURDATE()
  • DATE_FORMAT()
  • DATEDIFF()
  • FROM_UNIXTIME()
  • NOW()
  • PERIOD_DIFF()
  • TO_DAYS()
  • UNIX_TIMESTAMP()
  • WEEKDAY()
  • YEARWEEK()

幸运的是,这些函数中很多已经实现,而且能免费重用,如文章 “DB2 basics:Fun with dates and times” 中所示(请参阅 参考资料)。如果您使用的是 DB2 v9.7.2 或其更高版本,那么可以选择设置新的兼容向量 DB2_COMPATIBILITY_VECTOR=MYS。

DB2 生成用户定义函数很容易,因为可以用 SQL 编写函数,而不是像 MySQL 那样使用 C 语言。

清单 14 这是一个 UDF 示例,您可以用它来模拟 MySQL 的非标准化但经常被使用的 NOW() 函数。可以在应用程序样例中的多处地方使用模拟。

清单 14. 模拟内置的 MySQL NOW() 函数的 DB2 UDF 定义
CREATE FUNCTION NOW()
	RETURNS TIMESTAMP
	NO EXTERNAL ACTION

	BEGIN ATOMIC
		RETURN SELECT CURRENT TIMESTAMP
		FROM SYSIBM.DUAL;
	END

还有其他一些用户定义函数模拟常用的内置 MySQL 函数,在 MySQL to DB2 Conversion Guide Redbook 附录 B 中已经列出这些函数(请参阅 参考资料)。

如果需要,请将逻辑部分从 SQL 移动到 PHP 中

可能会出现无法将 MySQL 函数或语法转换成对应的 DB2 格式的情况。在这种情况下,请查看 PHP 是否支持此功能并且使用函数或扩展替代它。例如,MySQL 提供了一个调用 INET_ATON() 的 SQL 函数,它在 DB2 中就没有对应的函数。为 INET_ATON() 编写一个用户定义函数很容易出错,并且过程很繁琐。幸运的是,PHP 提供了 ip2long() 函数,它提供了同样的功能,而且不依赖于数据库向量。


第 2 步:开始进行代码迁移的第二步

PHP 代码已经更新,能够修改嵌入 SQL 语句,并按与 MySQL 类似的方式读取或更新数据。下一步是要重新调整查询,以实现非功能化目标,获得更好的数据集成和性能提升。这一步包含以下子步骤:

选择合适的并发方法

一旦将嵌入在基于 MySQL 的应用程序中的单个查询转换成 DB2 形式, 就需要考虑如何通过相应的改进来利用 DB2 并发性和数据集成特性。看看需要进行哪些修改,然后才能支持数据库并发连接之间适当的隔离级别。

DB2 中有四个隔离级别来控制并发性。隔离级别决定了事务如何对其他用户隐藏正在进行的数据变更。隔离级别有:

可重复读取隔离级别 (RR)
锁住所有行,直到事务结束。
读取稳定隔离级别 (RS)
锁住符合谓词条件的行,直到事务结束。
游标稳定隔离级别 (CS)
只锁住游标指向的行。这是默认操作。
未提交读取隔离级别 (UR)
不锁住任何行,除非数据发生变化。

从性能和数据集成角度考虑哪个隔离级别适合您的应用程序。当隔离级别从未提交读取 (UR) 移动到可重复读取 (RR) 过程中,并发性下降,数据集成性提高。RR 隔离级别保证了最大程度的数据集成,同时以高性能为代价。相反,UR 保证了最高的性能,但以一部分数据并发性为代价。图 1 演示了性能和数据集成之间的关系。

图 1. DB2 中的隔离级别
图片显示 RR、RS、CS 和 UR 锁一直下降。RR 数据集成规模最高。UR 性能规模最高

您可以对不同层次设置隔离级别,从单个查询直到连接和会话。如果有一个包含大量读取操作的工作负载,那么您可能想将隔离级别设置为未提交读取 (UR),从而不需要在每个语句中设置隔离级别。如果同时包含读取和写入操作,您可能会关注修改单个查询,而不是修改默认级别,即游标稳定性 (CS)。

您可能还会考虑您用来优化数据库访问的游标类型。ibm_db2 和 PDO_IBM 驱动程序都支持两种类型的游标:只能前进 (forward-only) 的游标和可滚动游标。默认情况下使用只能前进的游标。一般来说,默认设置适用于大多数情况,在这些情况下,您可以按照顺序遍历结果集。而在性能方面,只能前进的游标优于可滚动游标。但某些情况下,您需要游标既能向前又能向后,此时需要使用可滚动游标。在样例场景中,将游标设置为对每个必要的查询使用 清单 15 中的命令。

清单 15. 在 ibm_db2 函数和 PDO 驱动程序中定义 DB2 滚动类型
-- For ibm_db2
db2_exec($connection_resource, $sql, array('cursor' => DB2_SCROLLABLE));

-- For PDO_IBM
$DB_PDO->prepare($sql, array(PDO::ATTR_CURSOR, PDO::CURSOR_SCROLL));

请参阅 Developing PHP Applications for IBM Data Servers Redbook 的第 4.2 小节,其中使用了 PHP 和 DB2 数据库(请参阅 参考资料),您可以从该小节中查阅更多的信息。

整合查询以提升数据质量和性能

下一步要将查询组成逻辑单元,而不是使用单独的查询。在代码迁移的这一步,如果您知道 SQL 语句在接收数据,并且语句像在原有应用程序中一样修改数据,则需要考虑对数据访问查询和更新进行重新分组。这样做是为了匹配 MySQL 的性能级别,同时确保 DB2 的数据集成级别。

减少语句与数据库的连接
一般来说,与 DB2 相比,在 MySQL 中构建到数据库的连接更快一些。因此,如果减少每个 HTTP 请求的总连接数,将一个连接用于多个查询,则可以提高处理器、磁盘和网络性能。对于样例场景,查询已经合并,包括获取用户账户信息来执行更深入的前端连接(而不是使用若干后续查询),如 清单 16 中所示。但这是以预取过多数据为代价,在样例场景中,性能获得了提升,提取数据的数量也有所减少,而且程序开发人员有更好的方法来确定实际从给定页获取多少数据,这也节约了资源。
清单 16. 整合查询
-- Two individual queries that require two trips to the data server on the page
SELECT FIN_PROJECT_MANAGER AS PM, PROJNAME FROM PROJECT WHERE ID = $id;
SELECT EMAIL, FIRSTNAME, LASTNAME FROM USER WHERE ID = $pm;

-- Single consolidated query that retrieves the same information in one trip
SELECT U.EMAIL, U.FIRSTNAME, U.LASTNAME
FROM USER U, PROJECT P
WHERE U.ID = P.FIN_PROJECT_MANAGER 
AND PROJECT.ID = $id

为什么不迁移 Zend Framework?

样例场景的代码库的出现日期早于大多数 PHP 框架。从 2002 年编写 PPT 开始,PEAR 中可重用的组件和特殊用途库(如数据库虚拟层)就开始备受关注。完整的堆栈框架,如 CakePHP 和 Zend Framework 几年以后才出现。采用 Zend Framework 是理想的未来目标,但是要更新稳定代码来替换数据库,并完成一些基础工作,这些是代码迁移的首要任务。

向维护性更好的 MVC 架构迈进一大步
在使用模式-视图-控制器 (MVC) 架构模式构建的设计良好的三层 Web 应用程序中,控制器处理来自用户的 Web 请求,然后调用一个命令为获取结果做准备。结果对象会转换成视图(通常是通过一个简单的 HTML 模板表示),在页面上显示响应数据。原有的应用程序不使用 MVC 模式。该应用程序将查询直接嵌入模板中,有效地将三种责任组织到一个页面中。

通过采用将这些查询分组的 MVC 最佳实践,例如在一个模式调用中获取用户的所有用户账户信息和所有项目,视图页只需显示信息即可,无需逐步获取更多的信息。该方法将应用程序从数据集移动到业务对象集中,通过将数据访问逻辑从布局和页面设计中分离,帮助您更好地组织代码。

更好地分配计算工作负载
由于样例场景中已经有一个单独的 Web 服务器和一个单独的数据库服务器,Web 服务器主要处理大量的 HTTP 流量。Web 服务器通过 PHP 模块执行服务器端逻辑。为了通过降低 Web 浏览器花费在 mod_php 中的 CPU 和内存损耗来提升 HTTP 服务器上的性能,需要将一些业务逻辑从数据服务器上分离出来。这会额外地降低网络流量(因为它减少了 Web 和数据服务器的流量),并且通过处理所访问的统一级别的数据来增加吞吐量。
通过对服务器上的查询进行分组来提升数据集成
对于事务集成,最终也是最重要的收益是将逻辑分组为服务器上的原子事务单元,这些事务单元使用了一些存储过程、触发器和用户定义函数。您会发现,在以下情况时,在应用程序(如 PTT)中创建任务更可靠一些:
  • 将所有数据作为整体转移到数据库中
  • 多个 INSERT 语句作为一个单元一起执行
  • 将一个成功或失败错误返回给用户
如果没有支持事务的数据库,样例场景中这些数据库修改中的一项或多项就会失败,从而使系统处于不可预知的状态,需要用户和管理员干预才能解决数据不一致问题,例如,创建了一半的任务,没有所有者和其他重要链接。清单 17 显示了两个相关更新作为一个原子单元出现的示例。
清单 17. 将更新分组到一个存储过程中
CREATE PROCEDURE BILLING_TYPE_UPDATE (IN p_date DATE)
BEGIN
    -- Update for project's billing type
    FOR row AS 
       SELECT * 
       FROM proj_billingtype_snapshot
       WHERE end_date IS NULL
       AND start_date = p_date
       
	    DO
	       -- Execute two updates in one transaction
	       t1: begin atomic   -- Transaction begins
	      
	       -- First update
	       UPDATE fin_attributes
	       SET proj_type = row.billing_type
	       WHERE project_id = row.proj_id;
	        
	       -- Second update
	       UPDATE fin_attributes_archive
	       SET proj_type = row.billing_type
	       WHERE project_id = row.proj_id
	       AND year = YEAR(NOW());
	          
	       end t1;            -- Transaction ends
    END FOR;
END

DB2 所有版本都支持事务,但大多数 MySQL 存储引擎,包括默认的 MyISAM 类型,都不支持事务。应用程序样例使用了默认的 MyISAM 表类型,而开发人员试图在代码层采用数据集成。这对 DB2 是一个很好的过渡,因为 DB2 将事务管理委托给了数据库,而这是 DB2 所擅长的。这样做还可以减少代码的长度和复杂性,以及应用程序中某个 PHP 页所需的各种移动部分。请参阅 RedbookPHP Applications for IBM Data Servers 中的第 5.2.8 小节 “事务和隔离级别”(请参阅 参考资料),以了解关于 DB2 中并发性的更多信息。


第 3 步. 与相关利益者进行最初的用例测试

到目前为止,我们已经有了一个可作为整体进行测试的功能系统,至少有一组能组成特定组件的相关函数。例如,创建一个与父项目有关联的新任务在 PPT 系统中是一个完全可以独立实现的用例。发送部分内容(如产品标题和描述到翻译服务则)是另一个用例,可以单独测试。这一步包含以下子步骤:

编译或创建用户接受的测试

在这一步中,会开始让感兴趣的利益相关者执行一系列曾在原有系统上使用的用例。要保证同样的测试工作在新系统上与预期一样。

这几年来,样例场景使用了相对简单的格式进行用户接受测试。因此,形成了测试档案,以测试新的功能并且避免现有代码的回归问题。测试组合已经扩展,能够让关键功能核心符合 PPT 的功能。这些测试通常封装了一组相关的步骤,以达到业务目标,而且他们重新绘制了用例图。与静态用例模型的不同之处在于测试一般存储在电子表格格式以及截图和表格中,其中的行已经更新,无论一组步骤或单个步骤成功或失败,测试者都能输入。表 1 显示一个样例用户测试案例格式,验证任务创建是否与预期一样。

表 1. 用户接受测试示例
测试预期结果通过?
1.创建一个新任务1.1 登录系统。显示欢迎页面。
1.2.打开任务页面。加载新任务。
1.3 填写并保存表单。显示成功消息和唯一的任务 ID 链接。
2.批准任务2.1 登录系统。显示欢迎页面。
2.2.定位到任务列表。加载任务列表。
2.3 点击任务旁的批准按钮。显示成功信息通知,发送 email。

请记住,让测试者投入适当时间来执行用户接受测试并且注意任何问题,这非常重要。如果跳过这些测试,就可能忽略错误。同样的原因,要记录谁在什么时候测试,还要记录所报告的结果,以加强责任。

单元测试,解决问题

这一步包含对用户接受测试者报告的问题的系统性处理。从测试者处收到用户接受测试后,所报告的错误会分配给开发人员供他们验证和修复。例如,表 1 中的测试 2.3 会作为一项新故障被分配。

当以迭代方式迁移系统时,开发人员应该将 DB2 中已迁移的函数与 MySQL 源系统进行对比,确保特定的工作单元运行结果与预期的一样。与用户接受测试一样,可以使用单元测试框架(如 PHPUnit)自动处理此过程,这种处理方式很有价值。由于没有很好地将示例代码库划分成 MVC 代码,这导致它自身很容易地进行编程式测试,因此没有使用自动化单元测试或持续集成系统。

捕获一个镜像并将它用作基线

大量用例完成之后,倒数第二步是开始测试迭代,取得系统的当前状态作为备份和基线,以用作函数和性能验证参考。迭代变化再次满足稳定的里程碑,这对获取备份很重要。可以是传统文件格式或是 SQL 备份,或者可以是完整的操作系统镜像。用这些保存点防止过程中的错误,或者作为比较基线,也很重要。


第 4 步. 解决瓶颈问题并确定功能基线

一旦完成利益相关者指定的用户接受测试之后,就该提升系统性能了。这一步包含以下子步骤:

确认基线 UAT 测试

对于这个准备步骤,这里要重申的是,您应该对整个系统已确认的功能有充分了解,从而了解非功能性需求的任何变化,例如性能提升,不会对已经达成的功能需求造成负面影响,或造成任何代价。这一步骤在验证功能之后执行,这样做有两个理由。首先,确保发生任何非功能性的变化之前,已经存在已知的、良好的、签署过的功能基线。其次,它可以帮助您避免迁移过程中的过早优化陷阱。当您知道某个查询或方法对某一个案例会更快时,不应盲目地将该查询或方法套用到整个代码库中,尤其在有迹象表明存在性能问题时。

例如,如果后期出现验证回归问题,则需要保存用户验收测试历史记录,以确认是否对某些方法进行了检测并通过了测试,从而可以使用它们验证回归问题。

使用工具修复 DB2 中的性能问题

过早优化的危机

您可能听说过 Donald Knuth 1974 年发表的言论 “过早的优化是一切罪恶的根源。”该陷阱示例就曾发生在本系列文章中一个作者身上(我们并不是指责他!)当他知道单引号字符串执行得比双引号字符串更好(这是由于以内插值替换的变量在单引号中不会被替换),他决定将所有的 双引号用单引号替换。他认为应用程序不会遇到性能问题,他按照想法执行了查找和替换。不幸的是,它遗漏了一两个内插值字符串,引入了一个很小的 bug,结果引起了数据集成问题。尽管如此,我们原谅了他。

此时的测试应重点关注 DB2 能自动修复什么,因为 DB2 就是引入的最大的变化。当您改变代码、单元测试并执行 UAT 时,应该识别瓶颈并根据需要进行改进。要遵循的一条重要理念就是只解决单元测试和 UAT 测试中已知的问题,而不是对潜在的性能问题来源进行假设。完成此步骤之后,可以记录下已经做了哪些更改,以防止应用程序以后遇到类似问题。

根据一些最佳实践,在过早优化与进行所有修复之间有一些微妙的平衡。这是应用程序声明周期中一个迭代过程。关于过早优化的危险,请参阅侧边栏。

有一项技术很有用,即实现一个函数,就像在 PPT 应用程序中所做的那样,监控运行超过 60 秒的脚本并发送错误报告。本 系列文章 第 4 部分将介绍此类函数的一个样例。

根据报告中的可用信息,您就可以发现问题是由 PHP 执行慢还是查询慢引起的。对于 PHP 问题,Zend Studio 或 Eclipse PDT 中的调试器可帮助您定位问题。对于查询问题,您可以使用 IBM Data Studio 定位问题并给出修复意见。图 2 显示了某个查询的访问计划图样例。

图 2. 在 IBM Data Studio 中调优查询
流程图显示获取和扫描,然后连接并返回

定位并解决操作系统中的瓶颈

如果已经消除来自 DB2 和 PHP 的应用程序性能问题,那么接下来您可以利用 John Coggeshall 在 “Zend Enterprise PHP Patterns” 中介绍的经过检验而可靠最佳实践(请参阅 参考资料),以迭代方式定位其他瓶颈。简言之,您可以查看通常会成为应用程序瓶颈的三大方面,看看能否能够通过简单的硬件更新或调优分配给虚拟机的资源来解决一些性能问题:

  • CPU
  • 内存
  • 磁盘

Zend 书中介绍了很多工具,可以用它们来确定 Linux 操作系统的瓶颈,还有很多与 Windows 工具相关的技术,可在开发时、将更新后的应用程序部署到测试或分级服务器之后、部署应用程序之后使用它们。


第 5 步. 评估代码迁移基线

在经过 1-4 步完整的迭代之后(每个步骤中还包含一些子步骤),您应该已经在 Windows 工作站上实现了一个功能化数据库系统,并记录做了哪些更改,存在哪些问题。如果您使用的是虚拟机,那么应该获得了 Windows 系统的镜像作为快照,既可将其用作功能存储点,也可将其用作比较将来性能变化的基线。当然还有一个选择,就是在 IBM 或 Amazon Cloud 中使用虚拟镜像,并以相同方式使用它。您也许会遇到几种不同的代码更新,看看哪种最适合您的环境。

如果您对步骤 1-4 中使用的 Windows 工作站上的系统感到满意,您可以在版本控制系统中将已转换的代码作为发行版,并在部署前为最终步骤准备好基础架构,如本 系列文章 中的第 4 部分所示。


结束语

本系列文章旨在让您了解将 PHP 应用程序从 MySQL 迁移到 DB2 通常需要什么,什么样的资源对您有帮助,并了解 IBM 项目团队在 2010 年初是如何完成任务的。

本文是本系列的第 3 部分,您将:

  • 了解已转换的 PHP 代码。
  • 了解如何更新 DB2 应用程序。
  • 了解转换后如何测试和调优代码。

在第 4 部分,您将了解如何部署已迁移的应用程序并处理持续的支持。


致谢

作者非常感谢 Leons Petrazickis 和 Ambrish Bhargava 对本文的审阅和评论。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Information Management, Open source
ArticleID=777047
ArticleTitle=将 PHP 应用程序从 MySQL 移到 DB2,第 3 部分: 转换 PHP 代码
publish-date=11282011