电脑技术学习

全面解析IBM DB2数据库移植的常见问题

dn001
本文为作者针对实际项目数据库迁移的经验总结出的常见问题集,涉及到将数据库从 Oracle 或 SQL Server 迁移到 DB2 过程中的安装、配置、管理、开发等方面。

安装与配置

在 Linux 下重新安装 DB2 之后无法建立 DB2INST1 的实例,提示已经存在

解决办法:删除 /var/db2/v81 目录,再重新创建实例;

原因分析:在重新安装 DB2 之前,需要卸载原 DB2 安装环境,其中很重要的一步就是,使用 db2idrop 命令删除 DB2INST1 实例, 如果不删除,再次安装 DB2 之后,则无法建立相同名称的数据库实例。db2idrop 命令在 /opt/IBM/db2/V8.1/instance 目录下,以下为示例:

清单 1. db2idrop 命令示例

/opt/IBM/db2/V8.1/instance/db2idrop db2inst1

在Solaris 5.8 下安装 DB2 后却无法创建实例,提示 shmmax 需要调整

解决办法:编辑 /etc/system 文件,修改共享内存参数和消息队列值,重启机器后再建实例;

原因分析:DB2 在 UNIX 系统上需要使用 IPC 通信,所以内核参数共享内存和消息队列是关键指标,如果不能达到 DB2 要求数量,则 DB2 无法正常工作,其具体数字不易记忆,但可以查看 db2diag.log,里面记录了 DB2 所需要的具体数字,依照该数字更新内核参数即可。

DB2 Runtime Client 可否不安装开发工具包,只安装足够客户端工作的组件,以方便模拟客户实际应用环境进行测试?

解决办法:DB2 Runtime Client并不包含开发工具包。其功能就是只提供客户端工作的组件包。

一个裸设备无法同时分配给两个数据库,这样无法建立两个数据库服务器共享同一个裸设备

解决办法:把这两个数据库分别建在不同的实例上,将其中一个实例停下,第二个实例启动,分配裸设备给该活动实例上的数据库,分配完毕后将活动实例停下,启动第一个实例,重复先前分配裸设备的操作到第一个实例,即可实现当一个数据库停下时,该裸设备数据可以完全被另外一个数据库接管,从而实现共享。

原因分析:一个容器是无法同时分配给两个活动数据库的。必须针对两个实例来操作,分配给一个实例时另一个实例必须处于不活动状态。

在 Linux 上 DB2 安装完成后,不能绑定 License,报 DBI1430N 错误

解决办法:

1.更新系统时间到当前时间。使用 Linux 中的 DATE 命令;

2.使用 db2licm 命令绑定 License:

清单 2. 绑定 License 命令示例

./db2licm -a db2ese.lic

在Linux 上 DB2 实例创建不成功,返回错误码 DBI1281E

解决办法:正确设置主机名;

原因分析:DB2 创建实例时要取主机名,如果主机名设置不正确,则会报告无法初始化实例的错误,也就是 DBI1281E,可以首先用 uname 或者 hostname 查看是否有主机名,进一步可以用 ping 命令验证主机名是否正确,如果 ping 不通则不正确,还可以检查 /etc/hosts 查看 IP 和主机名的对应关系是否正确。

连接数据库

SuSE Linux C shell 环境下,无法连接数据库

解决办法:在cshell下,执行以下脚本设置环境,或将该命令加到 Linux 用户配置文件中:

清单3. 设置环境

source /sqllib/db2csrhc

原因分析:在 Linux 下,根据不同的 shell,需要执行不同的脚本来设置环境。

清单 4. 在不同的 shell 环境下设置

. /sqllib/db2profile (B shell 或 K shell)

source /sqllib/db2csrhc (C shell)

CLP 连接数据库服务器返回错误,提示 codepage 无法转换,按提示更改代码页后仍然无法连接

解决办法:在 CLP 中运行 db2 terminate 后再重新连接即可;

原因分析:在 CLP 中 codepage 更改不会即时生效,必须断掉当前连接再连才会生效。

Solaris 5.8 下用命令行方式无法连接数据库,提示遇到错误 SQL1084C

解决办法:编辑 /etc/system 调整内核参数,重启机器;

原因分析:查看 db2diag.log 可以发现消息队列错误,进一步发现该机器上的内核参数的消息队列值被改为 4096,DB2 需要 65535,更改这个参数重启机器和数据库,再连成功。

在Linux C Shell下创建新的 DB2 用户之后,用该用户无法连接数据库

解决办法:在该用户 home 目录下寻找 .cshrc 文件,如果没有则手工创建一个,然后在该文件中加入以下一行,然后重新登陆或者使用 source .cshrc 即可连接数据库。

清单5. shell 环境设置

source /sqllib/db2cshrc

原因分析:创建的用户所用的 shell 是 C shell,调用的是 .cshrc,该文件不存在,手工创建该文件,并在该文件中调用 sqllib/db2cshrc 后即可以正常连接数据库。

存储过程

在Stored Procedure 中如何判断一个系统文件是否存在

解决办法:使用 Java 开发一个 UDF, 将文件全名通过参数传递给这个 UDF,在 UDF 中判断系统文件是否存在然后返回结果代码;

原因分析:存储过程的主要目标是对数据库对象的操作,对文件系统操作需要借助于外部语言开发用户自定义函数,DB2 中提供了用各种外部语言开发函数的机制,所以推荐用 Java 开发 UDF 后,由该存储过程来调用。

DB2 SQL PL 的 HANDLER FOR SQLEXCEPTION, SQLWARNING, NOT FOUND 无法捕获 SQLCODE=-727 和 SQLCODE=-911 的异常

解决办法:将 DB2 补丁升级到 DB2V8 FixPack9;

原因分析:在 DB2 V8 FixPack7 上的确存在该问题,在 FixPack9 中已经解决。

备份与恢复

DB2中可否查询最近一次全备份执行的时间以及全备份文件存放的路径

解决办法:在 CLP 中可以使用 list history backup all for 命令查看,如需要在存储过程中使用,可使用表函数查询:select from table(snapshot_database('sample',-2)) as t

原因分析:snapshot_database() 是 DB2 中提供的表函数,可以返回当前数据库的一些信息,其中包括了数据库上次备份的时间信息。第一个参数是数据库名,第二个参数是数据库分区标志,-2 代表所有分区,-1 代表当前分区。

如何在数据库恢复的时候重定向表空间

解决办法:使用如下命令,详情请参见《DB2备份和恢复》简明手册:

清单6. 在数据库恢复的时候重定向表空间

RESTORE DATABASE SAMPLE FROM "C:TEMP" TAKEN AT 20050626155952 REDIRECT;

SET TABLESPACE CONTAINERS FOR 0 IGNORE ROLLFORWARD CONTAINER OPERATIONS

USING (PATH "C:DB2NODE0000SQL00005SQLT0000.0");

RESTORE DATABASE SAMPLE CONTINUE;

数据操作

如何对应 SQLServer 的 InsertBulk 命令?

解决办法:使用 load 命令。

如何给 VARGRAPHIC 类型字段设置缺省值?

解决办法:将数据库建为 UTF-8 格式的数据库。

原因分析:

表中有 long varchar 字段,在存储过程的游标中,以 select distinct varchar(该字段)……方式选择记录,但编译通不过

解决办法:把 distinct 去掉;

原因分析:如果有 long 型字段,加 distinct 关键字就失去意义而且十分影响性能,另外该列在原来的 Oracle 应用中也只是定义为 varchar(4000),建议在 DB2 中仍设为 varchar 型,同时将该表所在表空间的 pagesize 加大,由缺省的 4k 设为 16k 或 32k。

DB2存储过程中的异常处理如何写,与Oracle中的有何区别?

解决办法:DB2 中使用以下 SQL 语句声明和处理异常:

清单7. 声明和处理异常,方法一

DECLARE HANDLER FOR SQLEXCEPTION, SQLWARNING, NOT FOUND

BEGIN

END;

或者也可以针对某一sqlstate定义相应的错误处理句柄,示例如下:

清单8. 声明和处理异常,方法二

DECLARE condition_name CONDITION FOR SQLSTATE value;

DECLARE EXIT HANDLER FOR too_many_rows

BEGIN

...

END;

如何查询数据库中用户已定义的表?

解决办法:select * from syscat.tables;

原因分析:DB2 提供了一组视图用以用户查询数据库对象,这些视图统一存放在 SYSCAT 模式下,这些视图几乎包含了所以的数据库对象编目信息,包括表、视图、名字空间、存储过程等等。用户可以在《DB2 UDB SQL Reference Volume 1》SQL Reference 1 的 Appendix D 中找到这些视图的详细定义。

如何查询数据库中用户已定义的 sequence?

解决办法:使用 select * from syscat.sequences。

如何用SQL语句获得表空间的当前使用率?

解决办法:可以使用如下 SQL 语句查询,在用该语句获得了表空间已用页数,可用页数等信息后,即可计算出表空间当前使用率:

清单9. 获得表空间页数信息

select tablespace_name, page_size, usable_pages, used_pages, free_pages

from table( snapshot_tbs_cfg( 'sample', -1 ) ) as t

where t.tablespace_type = 0 and t.tablespace_name=

在 Oracle 中可以将游标结果集通过 BULK 方式存入宿主数组中,DB2中如何实现?

解决办法:DB2 中不支持 BULK 方式,其替代方式是定义相应的数组,然后以循环方式每次从游标结果集中 fetch 一条记录到数组元素中。

DB2中如何用 SQL 语句取出满足条件的结果集的前N条记录?

解决办法:使用 select * from where fetch first row only。

Oracle中有 trunacte 表的功能,速度非常快,它只是把表标志设为空,并不发生删除数据的 IO 操作,DB2 中如何做类似操作?

解决办法:可以先 drop 表,再重建该表

原因分析:Oracle 中 trunacte 表的速度之所以快,是因为它不记日志,只是直接把表的结构信息删除了,并不发生删除数据的 IO 操作。DB2 中 drop 表,也不会引起数据 IO 和日志记录,从而达到相同的效果。但前提是你预先有创建表的结构。否则不要随意 drop 表。

Oracle 有 truncate,DB2 中建议使用 drop,然后再 create 表,这样好还是 alter 表好?

解决办法:Oracle 的 truncate 是直接把表的数据清空,但不记日志,所以速度很快。DB2 中 drop 表也避免了大量记录记入日志的问题,再重建表就达到了快速清空表的目的。但 alter 是用于修改表结构的,和表的数据操作没有关系。

在一次性导入大量数据到表中时,会因为写日志而影响性能,如何提高该操作的性能,在导入数据后,需要做更新统计操作,如何做?

解决办法:一次性导入大量数据时为了提高性能,可以使用 alter table activate not logged initially 在该事务中关闭该表的日志选项,然后执行数据导入,在该事务结束后,该表日志开关自动打开。在导入大量数据后,为了提高查询性能,再运行 runstats on table 命令更新查询统计信息。

提示:必须在一个事务中执行关闭日志和数据导入才会不记日志,当前事务结束后日志开关会自动重新打开。

DB2 中如何定时执行一些任务,如表的清空等维护操作?

解决办法:在任务中心图形界面中将维护脚本导入,并设定定时调度的时间周期,任务中心会自动调度该任务。运行任务中心的前提是数据库服务器上已装有任务中心,并已编目工具目录数据库,如果没有工具目录数据库,请按以下步骤操作:

使用 create db taskdb 创建一个新的数据库,以用于工具目录数据库;

使用 db2admin start 启动数据库管理服务器,如果已经启动,进入第三步;

使用 db2 create tools catalog toolscat use existing db taskdb 编目工具目录数据库。

DB2 中如何实现为一个 DMS 表空间自动添加容器的脚本?

解决办法:可以先计算表空间当前可用大小,如小于某个设定值,则用 Alter tablespace add (...) 语句添加新的容器到表空间。计算表空间可用大小的方法可以参照问题“如何用sql语句获取表空间的当前使用率?”

用 UDF 实现了将 TimeStamp 类型的一个变量转换成 Date 型,但却无法将该 UDF 用于 Group By 子句

解决办法:可以使用 With 语句将需要 Group By 的字段先预先处理。如:with t1(col1) as (select func1(timecolumn) from t2) select col1 from t1 group by col1。With 语句中的 func1()为 UDF,实现了将 timestamp 转换为 date 的功能,其结果通过 with 语句被定义为一个 t1 中的虚列 col1,然后在 t1 中按 col1 分组。注意上面是一条语句,中间没有分隔符。

如何做类型转换才能将 VARGRAPHIC 类型的数据用在 LOCATE 函数中?

解决办法:先将 VARGRAPHIC 转换成 VARCHAR。然后再用于 locate 函数。

原因分析:LOCATE 函数只能接受字符串作为输入,所以必须将 VARGRAPHIC 先转换成 VARCHAR。同时在数据库编码格式上有要求,只有用 Unicode codepage 创建的数据库才能实现 VARGRAPHIC 到 VARCHAR 的转换。所以之前要用 UTF-8 CN_ZH 方式创建数据库。

如何使用 DB2 JDBC TYPE 4 方式连接数据库?

解决办法:将 JDBC Driver 配置为 com.ibm.db2.jcc.DB2Driver,在程序中数据库 URL 指定为 jdbc:db2://server:port/alias。Server 是指数据库服务器所在主机名或 IP;port 是数据库服务端口号,缺省是 50000;alias 是数据库别名。编译时需要 db2jcc.jar,记得把它指定在 classpath 中。

如何将一个 timestamp 类型转换为 yyyymmdd 格式?

解决办法:可以使用用户自定义函数(UDF)来实现。以下是一个广为流传的转换函数代码:

清单10. 将 timestamp 类型转换为 yyyymmdd 格式

create function ts_fmt(TS timestamp, fmt varchar(20))

returns varchar(50)

return

with tmp (dd,mm,yyyy,hh,mi,ss,nnnnnn) as

(

select

substr( digits (day(TS)),9),

substr( digits (month(TS)),9) ,

rtrim(char(year(TS))) ,

substr( digits (hour(TS)),9),

substr( digits (minute(TS)),9),

substr( digits (second(TS)),9),

rtrim(char(microsecond(TS)))

from sysibm.sysdummy1

)

select

case fmt

when 'yyyymmdd'

then yyyy || mm || dd

when 'mm/dd/yyyy'

then mm || '/' || dd || '/' || yyyy

when 'yyyy/dd/mm hh:mi:ss'

then yyyy || '/' || mm || '/' || dd || ' ' ||

hh || ':' || mi || ':' || ss

when 'nnnnnn'

then nnnnnn

else

'date format ' || coalesce(fmt,' ') ||

' not recognized.'

end

from tmp@

将上述代码保存在一个文本文件中,假设保存到 func.db2 中,使用以下命令创建函数:db2 –td@ -f func.db2。然后即可使用该 UDF 进行时间格式转换。该函数可以根据输入的 timestamp 转换为多种格式,包括 yyyymmdd,mm/dd/yyyy,yyyy/dd/mm hh:mi:ss,或者返回毫秒数(nnnnnn)。

在 SQL 存储过程中有('每月'||COALESCE(VAR, '')||'元')语句时,编译无法通过

解决办法:客户端 codepage 和脚本的编码格式不同,所以服务器端无法识别脚本命令,把客户端 codepage 设为 1386 即可。

Oracle 中有一个函数 DBMS_SQL.VARCHAR2_TABLE,可以传入一个以某个分隔符为分隔的字符串,该函数将该字符串以分隔符拆分,返回给调用者一个数组或是表,DB2 中如何实现类似功能?

解决办法:用全局临时表实现;

原因分析:Oracle 中的 varchar2_table 其实是集合的概念,在 DB2 中不支持集合。DB2 通常做法是声明全局临时表(DGTT),第一列存储记录顺序,第二列存储数据。以下是示例,在声明后即可向该表中插入和查询数据,效果和集合相同。

清单 11. 实现拆分

DECLARE GLOBAL TEMPORARY TABLE SESSION.temp_emp_list

(num integer, EmpName varchar(30))

WITH REPLACE

ON COMMIT PRESERVE ROWS NOT LOGGED;

DB2 无法对允许空值的列建唯一性索引

解决办法:将该列属性设为不允许为空;

原因分析:空值在数据库中被认为是不确定值,任何其他值与空值组合,结果仍然是空值,不具备唯一性,所以 DB2 不允许空值列作为唯一键的索引列。

同一张表的索引可以放在不同表空间中吗?

解决办法:在创建表的时候使用 INDEX in tablespace-name 选项,在该表上创建的索引将存放在指定的表空间上;

原因分析:表数据,表索引,以及表中的 long 型数据可以分开存放在不同的表空间。可以提高性能。

用UDF 调用存储过程总是编译通不过,报关键字冲突错误

解决办法:把该 UDF 改造成存储过程;

原因分析:DB2 中 UDF 主要用于逻辑运算和处理,存储过程则偏重于处理数据库对象,如表数据的增删查改,所以两者的用途不同决定了其使用方式也不同,UDF 使用的是内嵌的 SQL 语句,是 SQL 语言的子集,不能执行动态 SQL,如果 UDF 调用存储过程,则会导致 SQL 嵌套上的混乱,因为 UDF 通过调用存储过程实现了 SQL 全集,这是不允许的,所以报关键字冲突,把该 UDF 改为存储过程即可。

建议对于不涉及数据库表操作的业务逻辑可以用 UDF 实现,如逻辑算法,文件操作,系统调用等,而涉及到数据库表操作的功能则交给存储过程来实现。存储过程可以调用 UDF。

工具使用

如何在 CLP 中查看表结构

解决办法:使用 db2 describe table 命令。

用开发中心远程调试存储过程,可以构建,但构建并调试却不能成功

解决办法:在远程数据库上安装 DB2 开发包;

原因分析:在远程调试时将会调用远程数据库上的开发包,所以远程服务器一定要装有开发包才可远程调试。

如何将部署在 DB2 数据库上的存储过程导出为可执行脚本,以便在其它机器上部署

解决办法:在开发中心中可以导出存储过程为脚本,前提是之前创建存储过程的时候没有指定 hide body。

如何让 DB2 自动运行 runstat

解决办法:使用 db2 update db cfg using auto_runstats on 命令。