何剑敏 Oracle ACS华南区售后团队,首席技术工程师
曾供职于中国联通信息计费部、卓望数码,系统支撑部首席DBA,负责中国移动全网梦网业务和移动应用商城数据库维护。后供职于IBM,负责米其林项目和澳洲电信(Telstra)项目数据库管理。现供职于Oracle ACS华南区售后团队,首席技术工程师。多年从事第一线的数据库运维工作,有丰富项目经验、维护经验和调优经验,专注于数据库的整体运维。
编辑手记:字符集是数据库中很复杂的一个问题,通常设定后不做改变,但因生产的需要,也提供通过导入导出或命令修改的方式,但并不总是有效,涉及字符集的问题往往让DBA感到头疼,今天我们跟随作者一起来认识一个转变字符集的工具DMU,让字符集不再成为你DBA之旅的拦路虎。
和oracle字符集相关的参数是 nls_lang。NLS_LANG 的格式是:language_territory.client_charset,如AMERICAN_AMERICA.ZHS16GBK,那么第一位AMERICAN 表示语言,第二位 AMERICA 表示日期和数字格式,第三位 ZHS16GBK 表示字符集。影响数据库和客户端的其实是第三部分。
通常情况下,数据库字符集不轻易修改,如果要修改,一般可以简单采用下面两种方法可行:
1. 如果需要修改字符集,通常需要导出数据库数据,重建数据库,再导入数据库数据的方式来转换。 2. 通过 ALTER DATABASE CHARACTER SET|[INTERNAL_USE] <new_characterset> 语句修改字符集,但创建数据库后修改字符集是有限制的,只有新的字符集是当前字符集的超集时才能修改数据库字符集,例如 UTF8 是 US7ASCII 的超集,修改数据库字符集可使用 ALTER DATABASE CHARACTER SET UTF8。
另外,我们还可以用 csscan/csalter,和 DMU(The Database Migration Assistant for Unicode)来实现数据库的字符集转换。
csscan/csalter 适用在10g和11.1,在11.2之后就 desupport,12c之后,唯一的字符集转换工具就是 DMU。我们以 DMU 为例,进行一次数据集的转换。我们将原来的 zhs16gbk 的数据库字符集,转成al32utf8的字符集。
DMU的介绍可见The Database Migration Assistant for Unicode (DMU) Tool (Doc ID 1272374.1),且在这个文档中,也说明了可以在这里下载DMU。
关于 DMU 的 known issue,可以参考 Tips For and Known Issues With The Database Migration Assistant for Unicode (DMU) Tool version 2.1 (Doc ID 2018250.1)
初始的情况:
--再创建CS_TEST2表,字段类型比CS_TEST的大,是varchar2(20),而不是varchar2(4),然后灌入CS_TEST的数据:
--再创建 CS_TEST3 表,也是 varchar2(4),一会用 bulk cleansing 的方式清洗
--在 test2 下再建立一个 clob 字段的表,检查看看是否会影响 clob 字段
--再建一个 cs_test_normal,是 char 类型。
下面,我们就用DMU来进行字符集的转换。DMU目前只是提供了GUI界面,没有 CLI 界面,所以下面我们都用截图表示(每个截图可点击放大)。
注1,使用 DMU,必须在数据库中安装 XDB 组件,不然会报错:
注2,我们需要先运行 ?/rdbms/admin/prvtdumi.plb,不然会报错如下:
注3,建立存放 DMU repository 的表空间:
注4,由于修改字符集是逻辑的修改,可以在备份数据库的情况下,创建一个回滚点,以便在修改失败的情况下,迅速恢复。
好了,完成上述工作后,开始启动 dmuW64.exe:
点击新建数据库连接:
输入数据库连接信息:
点击 test connection,测试连接是否成功。如果成功,显示如下:
点击 connect 数据库,连上我们需要修改字符集的数据库。
可以看到原库的字符集,和要求建立repository。
我们转换成建议值,al32utf8。
选择之前建立的表空间。
repository建立完成。
出现 migration status 主菜单。
点击scan database。
进入 scan 设置向导。
设置 scan 的进程数和 buffer 大小。
注意勾上所有的 data dict 和应用 schema。
根据设置的进程数,计算表的切割份数。
点击finish开始扫描。
开始扫描了,可以看到扫描进度。
可以看到,如果扫描到有问题的 schema,左边会显示一个感叹号。
要查看 scan 报告,可以点击 database scan report。
可以点击 expand all 按钮,显示全部。
我们需要关注 with some issues。
expand all 之后,可以看到我的 cs_test 和 cs_test3 有问题。因为这2个表的a字段都是varchar2(4),里面包含两个汉字的行,zhs16gbk 是可以满足的,但是转成 al32utf8 之后,2个汉字会占据6个字符,varchar2(4) 就无法容纳了。在这里,我们对于两个表采用不同的修改方式:第一个表用手工清洗,第二个表我们用 bulk cleansing 放在批量的方式中处理。
我们开始对第一个表进行手工清洗。点击 cleansing editor。
点击 edit data。
可以看到转换前后的差别。
然后我们进行修改,点击 schedule 的 modify,这样就能在最后 convert 阶段修改,而不是当前就修改了。
我们把长度改成6,然后点击 apply。
可以看到现在都已经变成了绿色。
好了,手工清洗的,我只举了一个例子,由于如果很多表需要修改的话,手工清洗会很麻烦,我们可以采用批量清洗的方式。现在我们就点击 bulk cleansing,开始批量清洗。
进入批量清洗向导。
选择修改成 character length semantics,就原来是 varchar2(4),会修改成 varchar2(4 char)。即按照实际使用的字符计算。
同样也选择 schedule,即在 convert 阶段进行这些操作。
点击所有红圈和感叹号的对象。
可以看到我们之前的两个表都需要转换。
两个表都被列在了计划中。
清洗步骤完成后,再次扫描一次 database,确认所有的要素都会在 convert 时被修复。
再次进入扫描向导。
再次设置 scan 的进程数和 buffer
再次勾上所有。
再次点击 finish 开始扫描
扫描完成,可以看到左右的都是勾。
点击 convert database 开始转换。
请注意看 conversion step。列出来了 convert 的各个步骤,我们看到第一个步骤是修改一些参数和禁用一些 trigger。以防止在转换过程中触发内部操作。
第二个步骤是对字段进行内部更新。
第三个步骤暂时没有看到相关操作。
第四步骤,是进行了 internal_use 的转换。
第五步,是将刚刚 bulk cleansing 的字段进行修改,并且将在第一步中修改的参数复原。
注意,在右上角,我们可以修改一些 convert 时的参数。如点击点一个 conversion parameters:
我们可以选择并发度(就是第二步的 update 的语句的并发度),另外还有 convert 的进程数等等。
对于第二个 table convert plan,我们点开
也可以看到转换的方式。比如选择只需要 convert 的行进行转换。
选择完之后,回到原来的界面,可以看到第二步的语句中已经有变化,后面多了 where 的条件。
点击右边中间的 convert 按钮,开始正式转换。会提示你最后一次 scan 是什么时间。
提示你要对数据库进行 backup。
转换开始,可以看到右上角正在执行的步骤。
转换结束,要求你断开 DMU,重启数据库(因为还在 restricted 模式)。
转换过程容易踩到的坑是:更新字段 set column_a=SYS_OP_CSCONV(column_a,’AL32UTF8′) 的时候,只能采用 parallel dml 的方式,而不能采用分批提交的方式。这就容易造成一个大的事物,占用大量的 undo。而且万一更新失败,回滚 undo 也需要很长时间。我曾经尝试更新1亿多行记录的大表,发现回滚段占据了快10G,撑爆了磁盘空间。而在4个并发的情况下,花了半个多小时也没有完成。
转换后:
下面我们来看一下在新数据库中的字符是否显示正确。 注,如果看不到正确的数据,可能和以下因素有关。 (1)客户端操作系统不支持显示中文。 (2)Oracle客户端工具不支持显示中文。 (3)Oracle客户端有相关设置(比如NLS_LANG)不正确。 (4)存储在数据库中的数据已经是不正确的数据。
我的客户端是SecureCRT,操作系统是win7,操作系统支持中文。客户端也支持中文,客户端的nls_lang是继承操作系统,由于下面的设置是default:
所以就继承了OS的编码,操作系统的 page code 是936,即 gbk,所以要设置客户端的nls_lang 为 gbk。
如果不设置为 gbk,就犯了上面的第三条,就会报错。见下。
windows环境字符集: C:\Users\jijihe>chcp Active code page: 936
即GBK的字符集 而我的 SecureCRT 由于是设置 default 是继承操作系统的,所以也是 GBK。要正确显示数据库中的字符,需要也设置成 gbk SecureCRT登录数据库后:
如果设置成gbk,才能正确显示:
总体来说 nls_lang 的作用是告诉 oracle 数据库服务器,当前的客户端用的是哪个字符集,是否需要转码。
设置 nls_lang 要看客户端(或者说工具端)的设置。有些客户端自己包含字符集(如adobe 的一些产品,如 ebs 的产品,如 peoplesoft 的产品),有些客户端是继承操作系统的字符集,有些客户端是包含多个字符集,可以选择。要数据库服务器能正确转码,客户端登录数据库前,需要将 nls_lang 设置成客户端自己的字符集或者继承的字符集。
网上说要设置客户端的nls_lang和数据库端的字符集一样,并不是一种准确的说法。Oracle 在文档中也提到这种做法 NOT ALWAYS correct: