第3章 Oracle的字符集(3.9-3.11)
Oracle全球支持(即Globalization Support)允许我们使用本地语言和格式来存储和检索数据。通过全球支持,Oracle可以支持多种语言及字符集,得以展示数据库的强大魅力。这篇介绍《循序渐进Oracle》第三章的3.9-3.11节,自定义字符集的使用、更改字符集的内部方式及字符集更改的案例。
由于不同语言及字符集的共同存储存在设置上具有一定的复杂性,字符集一度成为普遍困扰大家的一个主要问题。
本章就字符集一些常见问题进行讨论,并对字符集转化等本质内容进行探索。
3.9 自定义字符的使用
很多时候,需要存储的字符可能在数据库的字符集中并不存在,这时候我们就可以通过客户端的“TrueType 造字程序”来对空闲代码点进行自定义字符的创建。
在Windows上,单击开始->运行-> eudcedit 可以启动造字程序,下面我们用造字程序在字符集ChineseGBK下使用代码点AAA1创造一个新字进行测试:
图3-15 使用造字程序自造字
造字完成以后,保存该字符,在客户端字符集我们就完成了这个新字符的定义,接下来在菜单 编辑->输入法链接 将该字符链接到当前输入法中,以便可以通过输入法输入这个字。
下面我们可以将这个字存入数据库:
图3-16 将自造字存入数据库
那么接下来的查询这个字可以被正确显示:
图3-17 自造字及代码点
我们注意,这个字的编码为170 161,转换为16进制后正是:AAA1。
也就是说服务器端只管存入了客户端发过去的编码,至于这个编码代表了什么字符数据库服务器并不管,当反向查询时,Oracle数据库将编码再返回给客户端,客户端依据编码来对应客户端的字符展现,如果客户端存在对应,那么就可以正常显示我们创建的字符,如果不存在,或者客户端定义了不同的编码,那么显示的就将是不同的字符,这和不同字符集的相互转换的原理是相同的,对于本例,我们在另外一个客户端对AAA1定义了不同的字符,则查询的结果显示为:
图3-18 自造字在不同客户端可能的不同映射
这也就是为什么我们前面说,当进行字符集转换时需要通过csscan进行扫描,其中的一个原因就是因为同一个代码点在不同的字符集下可能代表不同的字符。
总结一下,字符集的实现实际上依赖于数据库端及客户端的字符集文件,只有服务器端字符集文件存在代码点到字符的映射,数据库服务器才知道存储的代码到底代表什么字符;而同样客户端也必须存在相应的代码点到字符的映射关系,这样数据库服务器上的字符传递到客户端之后才能被正确的解码成相应的字符。这就类似调制解调的过程,在传输过程中,自始至终传递的都是字符编码。
只有遵循通用的编码规范,我们的数据才能够通用的被不同的客户端、平台所共享和识别,如果是自定义的字符,那么在不存在定义关系的客户端就无法被识别或者不能被正确识别。
3.10 更改字符集的内部方式
前面我们曾经提到,通过修改props$的方式更改字符集在Oracle 7之后是一种极其危险的方式,应该尽量避免。我们又知道,通过ALTER DATABASE CHARACTER SET更改字符集虽然安全可靠,但是有严格的子集和超集的约束,实际上很少能够用到这种方法。
除了前面提到的几种方法外,Oracle还存在另外一种更改字符集的方式:通过隐含的内部命令强制修改字符集。
当使用模板的种子数据库创建数据库时,在创建脚本中可以看到这样的一系列命令:alter system enable restricted session;
alter database "eygle" open resetlogs;
alter database rename global_name to "eygle";
ALTER TABLESPACE TEMP ADD TEMPFILE 'C:\oracle\oradata\eygle\TEMP01.DBF' SIZE 20480K REUSE AUTOEXTEND ON NEXT 640K MAXSIZE UNLIMITED;
select tablespace_name from dba_tablespaces where tablespace_name='USERS';
select sid, program, serial#, username from v$session;
alter database character set INTERNAL_CONVERT ZHS16GBK;
alter database national character set INTERNAL_CONVERT AL16UTF16;
alter user sys identified by "&&sysPassword";
alter user system identified by "&&systemPassword";
alter system disable restricted session;
在这里面,可以看到这样两条重要的、Oracle非公开的命令:alter database character set INTERNAL_CONVERT ZHS16GBK;
alter database national character set INTERNAL_CONVERT AL16UTF16;
这个命令是当选择了使用模板方式创建了种子数据库以后,Oracle会根据选择的字符集设置,把当前种子数据库的字符集更改为期望字符集,这就是这条命令的作用。
如果检查警告日志文件,可以发现相关更改操作的痕迹:alter database character set INTERNAL_CONVERT ZHS16GBK
Updating character set in controlfile to ZHS16GBK
SYS.SNAP$ (REL_QUERY) - CLOB representation altered
SYS.METASTYLESHEET (STYLESHEET) - CLOB representation altered
SYS.EXTERNAL_TAB$ (PARAM_CLOB) - CLOB representation altered
XDB.XDB$RESOURCE (SYS_NC00027$) - CLOB representation altered
ODM.ODM_PMML_DTD (DTD) - CLOB representation altered
OE.WAREHOUSES (SYS_NC00003$) - CLOB representation altered
PM.ONLINE_MEDIA (SYS_NC00042$) - CLOB representation altered
PM.ONLINE_MEDIA (SYS_NC00062$) - CLOB representation altered
PM.ONLINE_MEDIA (PRODUCT_TEXT) - CLOB representation altered
PM.ONLINE_MEDIA (SYS_NC00080$) - CLOB representation altered
PM.PRINT_MEDIA (AD_SOURCETEXT) - CLOB representation altered
PM.PRINT_MEDIA (AD_FINALTEXT) - CLOB representation altered
Completed: alter database character set INTERNAL_CONVERT ZHS1
在使用这个命令时,Oracle会跳过所有子集及超集的检查,在任意字符集之间进行强制转换,所以,使用这个命令时你必须十分小心,并必须清楚这一操作会带来的风险。
之前讲过的内容仍然有效,可以使用csscan扫描整个数据库,如果在转换的字符集之间确认没有严重的数据损坏,或者可以使用有效的方式更改,就可以使用这种方式进行转换。
这个内部命令的用户引用格式如下:alter database character set INTERNAL_USE ZHS16CGB231280
来看一下具体的操作过程及Oracle的内部操作:SQL> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> STARTUP MOUNT;
Database mounted.
SQL> ALTER SYSTEM ENABLE RESTRICTED SESSION;
System altered.
SQL> ALTER SYSTEM SET JOB_QUEUE_PROCESSES=0;
System altered.
SQL> ALTER SYSTEM SET AQ_TM_PROCESSES=0;
System altered.
SQL> ALTER DATABASE OPEN;
Database altered.
SQL> alter session set events '10046 trace name context forever,level 12';
Session altered.
SQL> alter database character set INTERNAL_USE ZHS16CGB231280
Database altered.
这时警告日志文件中的记录了如下信息:Tue Oct 19 16:26:30 2004
Database Characterset is ZHS16GBK
replication_dependency_tracking turned off (no async multimaster replication found)
Completed: ALTER DATABASE OPEN
Tue Oct 19 16:27:07 2004
alter database character set INTERNAL_USE ZHS16CGB231280
Updating character set in controlfile to ZHS16CGB231280
Tue Oct 19 16:27:15 2004
Thread 1 advanced to log sequence 118
Current log# 2 seq# 118 mem# 0: /opt/oracle/oradata/primary/redo02.log
Tue Oct 19 16:27:15 2004
ARC0: Evaluating archive log 3 thread 1 sequence 117
ARC0: Beginning to archive log 3 thread 1 sequence 117
Creating archive destination LOG_ARCHIVE_DEST_1: '/opt/oracle/oradata/primary/archive/1_117.dbf'
ARC0: Completed archiving log 3 thread 1 sequence 117
Tue Oct 19 16:27:20 2004
Completed: alter database character set INTERNAL_USE ZHS16CGB231280
Shutting down instance: further logons disabled
Shutting down instance (immediate)
License high water mark = 1
Tue Oct 19 16:29:06 2004
ALTER DATABASE CLOSE NORMAL
...
格式化10046跟踪文件,得到以下信息(摘要):alter session set events '10046 trace name context forever,level 12'
alter database character set INTERNAL_USE ZHS16CGB231280
....
update col$ set charsetid = :1 where charsetform = :2
....
update argument$ set charsetid = :1 where charsetform = :2
....
update collection$ set charsetid = :1 where charsetform = :2
....
update attribute$ set charsetid = :1 where charsetform = :2
....
update parameter$ set charsetid = :1 where charsetform = :2
....
update result$ set charsetid = :1 where charsetform = :2
....
update partcol$ set spare1 = :1 where charsetform = :2
....
update subpartcol$ set spare1 = :1 where charsetform = :2
....
update props$ set value$ = :1 where name = :2
....
update "SYS"."KOTAD$" set SYS_NC_ROWINFO$ = :1 where SYS_NC_OID$ = :2
....
update seq$ set increment$=:2,minvalue=:3,maxvalue=:4,cycle#=:5,order$=:6,
cache=:7,highwater=:8,audit$=:9,flags=:10 where obj#=:1
....
update kopm$ set metadata = :1, length = :2 where name='DB_FDO'
....
ALTER DATABASE CLOSE NORMAL
可以看到这个过程和之前ALTER DATABASE CHARACTER SET操作的内部过程是完全相同的,也就是说INTERNAL_USE提供的帮助就是使Oracle数据库绕过了子集与超集的校验。
这一方法在某些方面是有用处的,比如测试;但应用于产品环境时就应该格外小心,除了你以外,没有人会为此带来的后果负责!
3.11 字符集更改的案例
前面强调过,通过更新props$表修改字符集的方式是不可取的,但是仍然有很多DBA在不断冒险,曾经有朋友在更新数据库字符集为ZHS16GBK时漏写了一个S,结果更新为ZH16GBK,导致数据库无法启动。
在这种情况下,根据我们前面3.3节的探讨,数据库无法启动实际上是因为无法加载一个不存在的字符集文件导致的,针对这种情况的一个临时性解决方案是通过Oracle的Locale Builder工具创建一个临时的字符集文件(字符集名称设置为错误拼写的字符集),使得启动时的字符集文件检验可以通过,然后数据库可以被打开。
曾经有网友经历过这样一个案例:
数据库为 9.2.0.7.0 ,OS : Solaris Operating System (SPARC 64-bit)
起因是这样的,我的一客户那里UPS出现故障导致系统宕机,然后起来,大约过了十来分钟,突然操作系统找不到磁盘又一次宕机,然后再起来,有用户报一个SQL用不上索引。
这个SQL是这样的:select * from ww.test20060504 dg where dg.user_number='7290'
第一个想法是给那个索引做分析,但还是不行,我们就对这个表做了一次分析,但执行计划没有什么改变 。我们尝试加提示(包括加 rule ),但也不行,用户反映是有一批这样类似的都用不到索引。
然后通过10053做trace,居然发现优化器根本没有考虑索引。开始怀疑这个数据库的数据字典可能有问题。我们只好用一个笨方法,将其中一个表导到测试库上去测试,在导出的过程中居然发现系统报错:EXP-00056: ORACLE error 6552 encountered
ORA-06552: PL/SQL: Compilation unit analysis terminated
ORA-06553: PLS-553: character set name is not recognized
居然系统报字符集的错!我们马上仔细检查了一个alter文件发现了一条信息,系统在第一次宕机起来后就自已将controlfile 中的字符集给更改了。最后我们将系统中的字符集改回来系统就恢复正常了。
警告日志中的信息是这样的:SMON: enabling tx recovery
Mon Jun 5 09:52:52 2006
Updating character set in controlfile to ZHS16CGB231280
replication_dependency_tracking turned off (no async multimaster replication found)
其实这个信息是手工更新过数据库字符集后,重新启动,Oracle比较数据库和控制文件信息,根据数据库字符集修改控制文件字符集导致的。在这样的案例中,由于字符集的错误修改,会导致数据库出现很多混乱,所以,当我们面对字符集的问题时,应该十分谨慎。
总结一下,无论什么时候,对于DBA来说,有一个很重要的原则就是:不要把你的数据库置于危险的境地!这就要求我们,在进行任何可能对数据库结构发生改变的操作之前,先做有效的备份,很多DBA没有备份的操作中得到了惨痛的教训。