UPDATE GLOBAL_NAME为空之后的恢复

Posted by 老熊 on 9月 6th, 2010

在以前的一篇文章中,我提到千万不能将Oracle数据库的global_name更新为空。这不,事儿来了。我的一个同事,提到了一个解决办法,不过那个办法实际上是一种不完全恢复的办法,如果没有备份,就行不通。如果没有备份,可以使用BBED来修改块来解决这个问题,不过使用bbed仍然比较麻烦。

下面是我一时心血来潮进行的一次测试。测试环境,10.2.0.4 for Linux AS 5.5。注意,不要在生产库上模仿。

首先UPDATE GLOBAL_NAME为空,COMMIT后以abort方式关闭数据库,以abort方式只是为了增加点难度。之后再启动数据库。

SQL> update global_name set global_name='';

1 row updated.

SQL> commit;

Commit complete.

SQL> shutdown abort
ORACLE instance shut down.
SQL> startup
ORACLE instance started.

Total System Global Area  209715200 bytes
Fixed Size                  1266632 bytes
Variable Size              75500600 bytes
Database Buffers          130023424 bytes
Redo Buffers                2924544 bytes
Database mounted.
ORA-01092: ORACLE instance terminated. Disconnection forced

启动失败,不出意料出现ORA-600 [18062]错误:

Mon Sep  6 15:43:31 2010
Errors in file /oracle/app/oracle/admin/xty/udump/xty_ora_3149.trc:
ORA-00600: internal error code, arguments: [18062], [], [], [], [], [], [], []
QMNC started with pid=16, OS id=3151
Mon Sep  6 15:43:33 2010
Errors in file /oracle/app/oracle/admin/xty/udump/xty_ora_3149.trc:
ORA-00600: internal error code, arguments: [18062], [], [], [], [], [], [], []
Mon Sep  6 15:43:33 2010
Errors in file /oracle/app/oracle/admin/xty/udump/xty_ora_3149.trc:
ORA-00600: internal error code, arguments: [18062], [], [], [], [], [], [], []

下面来解决这个问题。

第1步,重启数据库到MOUNT状态:

[oracle@xty ~]$ sqlplus / as sysdba

SQL*Plus: Release 10.2.0.4.0 - Production on Mon Sep 6 15:43:47 2010

Copyright (c) 1982, 2007, Oracle.  All Rights Reserved.

Connected to an idle instance.

SQL> startup mount
ORACLE instance started.

Total System Global Area  209715200 bytes
Fixed Size                  1266632 bytes
Variable Size              75500600 bytes
Database Buffers          130023424 bytes
Redo Buffers                2924544 bytes
Database mounted.

第2步,在另一个窗口中,使用gdb

[oracle@xty ~]$ ps -ef | grep LOCAL
oracle    3186  3156  0 15:43 ?        00:00:00 oraclexty (DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
oracle    3188  2978  0 15:44 pts/3    00:00:00 grep LOCAL
[oracle@xty ~]$ gdb $ORACLE_HOME/bin/oracle 3186
GNU gdb (GDB) Red Hat Enterprise Linux (7.0.1-23.el5)
...(省略)...
Reading symbols from /oracle/app/oracle/product/10.2.0/bin/oracle...(no debugging symbols found)...done.
Attaching to program: /oracle/app/oracle/product/10.2.0/bin/oracle, process 3186
Reading symbols from /etc/libcwait.so...(no debugging symbols found)...done.
Loaded symbols for /etc/libcwait.so
Reading symbols from /oracle/app/oracle/product/10.2.0/lib/libskgxp10.so...(no debugging symbols found)...done.
Loaded symbols for /oracle/app/oracle/product/10.2.0/lib/libskgxp10.so
Reading symbols from /oracle/app/oracle/product/10.2.0/lib/libhasgen10.so...(no debugging symbols found)...done.
...(省略)...
Reading symbols from /oracle/app/oracle/product/10.2.0/lib/libnnz10.so...(no debugging symbols found)...done.
Loaded symbols for /oracle/app/oracle/product/10.2.0/lib/libnnz10.so
Reading symbols from /usr/lib/libaio.so.1...(no debugging symbols found)...done.
Loaded symbols for /usr/lib/libaio.so.1
Reading symbols from /lib/libdl.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/libdl.so.2
Reading symbols from /lib/libm.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libm.so.6
Reading symbols from /lib/libpthread.so.0...(no debugging symbols found)...done.
[Thread debugging using libthread_db enabled]
Loaded symbols for /lib/libpthread.so.0
Reading symbols from /lib/libnsl.so.1...(no debugging symbols found)...done.
Loaded symbols for /lib/libnsl.so.1
Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Reading symbols from /lib/libnss_files.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/libnss_files.so.2
0x0025d402 in __kernel_vsyscall ()
(gdb)  break kokiasg
Breakpoint 1 at 0xa3d404d
(gdb) continue
Continuing.

第3步,OPEN数据库:

SQL> alter database open;

约等一会儿,在alert日志里面可以看到:

Mon Sep  6 15:44:41 2010
SMON: enabling cache recovery
SMON: enabling tx recovery
Mon Sep  6 15:44:41 2010
Database Characterset is ZHS16GBK

在gdb的输出可以看到:

Breakpoint 1, 0x0a3d404d in kokiasg ()

第4步,在gdb那里中止OPEN:

(gdb) kill
Kill the program being debugged? (y or n) y
(gdb) quit

sqlplus会提示:

alter database open
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

这一次,Instance并没有terminated。只是Server process被KILL了。

第5步,还原GLOBAL_NAME:

[oracle@xty ~]$ sqlplus / as sysdba

SQL*Plus: Release 10.2.0.4.0 - Production on Mon Sep 6 15:45:09 2010

Copyright (c) 1982, 2007, Oracle.  All Rights Reserved.

Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL> select status from v$instance;

STATUS
------------
OPEN

SQL> update global_name set global_name='XTY';

1 row updated.

SQL> commit
  2  ;
commit
*
ERROR at line 1:
ORA-01109: database not open

虽然可以执行UPDATE,但是不能COMMIT。再试试能不能做DDL:

SQL> create table t1 ( a int);

Table created.

成功了。那我们可以写一个DDL触发器,在触发器里面UPDATE GLOBAL_NAME:

SQL> create or replace trigger DDL_UPDATE_GN
  2  before ddl on database
  3  BEGIN
  4    if ora_dict_obj_name='T1' then
  5     update global_name set global_name='XTY';
  6    end if;
  7  END;
  8  /

Trigger created.

SQL> drop table t1;

Table dropped.

在另会一个会话中查看GLOBAL_NAME:

SQL> select * from global_name;

GLOBAL_NAME
--------------------------------------------------------------------------------
XTY

GLOBAL_NAME回来了。

这里通过DDL的隐式提交特性来UPDATE GLOBAL_NAME。其实还有更简单的办法:OCI主动断开连接时的自动提交。如果UPDATE之后,直接退出sqlplus,UPDATE GLOBAL_NAME的事务实际也提交了。看起来ORACLE这时只是不能执行显式的COMMIT语句。

第6步,重启数据库:

SQL> shutdown immediate
ORA-00604: error occurred at recursive SQL level 1
ORA-01109: database not open

SQL> shutdown abort
ORACLE instance shut down.
SQL> startup
ORACLE instance started.

Total System Global Area  209715200 bytes
Fixed Size                  1266632 bytes
Variable Size              75500600 bytes
Database Buffers          130023424 bytes
Redo Buffers                2924544 bytes
Database mounted.
Database opened.

SQL> drop trigger DDL_UPDATE_GN;

Trigger dropped.

看起来只能以abort方式关闭数据库:
成功了,数据库起来了。没有数据丢失,没有使用备份。不需要基于时间点的恢复,不需要BBED,不需要Resetlog。

再论字符集转换-Part IV

Posted by 老熊 on 8月 14th, 2009

此前关于字符集转换的文章,已经有三篇。写这新的一篇来源于最近有几次朋友问到的关于导入导出(exp/imp)的问题。这个问题是这样的:
使用imp导入数据后,发现数据是正确的,没有乱码,但是表和列上的注释(comments)、中文列名、Procedure/Package里面的中文全部变成了乱码。

网上很少有文章讨论到这一点,其实exp/imp与通常执行SQL引起的字符集转换有一些不同。这得从dmp文件的格式说起。
先看看下面的测试:

SQL> create table t1 ( a number,b varchar2(100));
SQL> insert into t1 values (123456,'aaaaaa');
SQL> insert into t1 values (67890,'中中中中');
SQL> commit;
SQL> comment on table t1 is '测试表';

现在将NLS_LANG设置为AMERICAN_AMERICA.ZHS16GBK,导出T1表,然后看看导出的dmp文件中的数据:

000008f0h: 22 54 31 22 0A 43 52 45 41 54 45 20 54 41 42 4C ; “T1″.CREATE TABL
00000900h: 45 20 22 54 31 22 20 28 22 41 22 20 4E 55 4D 42 ; E “T1″ (”A” NUMB
00000910h: 45 52 2C 20 22 42 22 20 56 41 52 43 48 41 52 32 ; ER, “B” VARCHAR2
00000920h: 28 31 30 30 29 29 20 20 50 43 54 46 52 45 45 20 ; (100)) PCTFREE
00000930h: 31 30 20 50 43 54 55 53 45 44 20 34 30 20 49 4E ; 10 PCTUSED 40 IN
00000940h: 49 54 52 41 4E 53 20 31 20 4D 41 58 54 52 41 4E ; ITRANS 1 MAXTRAN
00000950h: 53 20 32 35 35 20 53 54 4F 52 41 47 45 28 49 4E ; S 255 STORAGE(IN
00000960h: 49 54 49 41 4C 20 31 30 34 38 35 37 36 20 46 52 ; ITIAL 1048576 FR
00000970h: 45 45 4C 49 53 54 53 20 31 20 46 52 45 45 4C 49 ; EELISTS 1 FREELI
00000980h: 53 54 20 47 52 4F 55 50 53 20 31 29 20 54 41 42 ; ST GROUPS 1) TAB
00000990h: 4C 45 53 50 41 43 45 20 22 54 45 53 54 5F 38 4B ; LESPACE “TEST_8K
000009a0h: 22 20 4C 4F 47 47 49 4E 47 20 4E 4F 43 4F 4D 50 ; ” LOGGING NOCOMP
000009b0h: 52 45 53 53 0A 49 4E 53 45 52 54 20 49 4E 54 4F ; RESS.INSERT INTO
000009c0h: 20 22 54 31 22 20 28 22 41 22 2C 20 22 42 22 29 ; “T1″ (”A”, “B”)
000009d0h: 20 56 41 4C 55 45 53 20 28 3A 31 2C 20 3A 32 29 ; VALUES (:1, :2)
000009e0h: 0A 02 00 02 00 16 00 01 00 64 00 54 03 01 00 00 ; ………d.T….
000009f0h: 00 00 00 04 00 C3 0D 23 39 06 00 61 61 61 61 61 ; …..?#9..aaaaa
00000a00h: 61 00 00 04 00 C3 07 4F 5B 08 00 D6 D0 D6 D0 D6 ; a….?O[..中中?
00000a10h: D0 D6 D0
00 00 FF FF 0A 43 4F 4D 4D 45 4E 54 20 ; 兄?..COMMENT
00000a20h: 4F 4E 20 54 41 42 4C 45 20 22 54 31 22 20 49 53 ; ON TABLE “T1″ IS
00000a30h: 20 0A 08 00 27 B2 E2 CA D4 B1 ED 27 0A 45 58 49 ; …’测试表’.EXI
00000a40h: 54 0A 45 58 49 54 0A 00 00 00 00 00 00 00 00 00 ; T.EXIT……….

从上面的数据可以看到,有两部分数据,一部分是代码性质的数据,包括CREATE TABLE、COMMIT、INSERT等SQL语句;另一部分就是表T1的实际数据了,红色部分就是表的实际数据。

我们先看看表中的实际数据:

SQL> select a,dump(a,16) da,dump(b,16) db from t1;

         A DA                             DB
---------- ------------------------------ --------------------------------------
    123456 Typ=2 Len=4: c3,d,23,39        Typ=1 Len=6: 61,61,61,61,61,61
     67890 Typ=2 Len=4: c3,7,4f,5b        Typ=1 Len=8: d6,d0,d6,d0,d6,d0,d6,d0

对比一下就可以发现,dmp文件中T1表的数据,与数据库中原始数据是一模一样的,没有发生任何变化,也就是说,dmp文件中存储的表数据实际上与数据在数据库中存储的相同的二进制形式。

现在将NLS_LANG设置为AMERICAN_AMERICA.US7ASCII,导出T1表,然后看看导出的dmp文件中的数据:

000008f0h: 22 54 31 22 0A 43 52 45 41 54 45 20 54 41 42 4C ; “T1″.CREATE TABL
00000900h: 45 20 22 54 31 22 20 28 22 41 22 20 4E 55 4D 42 ; E “T1″ (”A” NUMB
00000910h: 45 52 2C 20 22 42 22 20 56 41 52 43 48 41 52 32 ; ER, “B” VARCHAR2
00000920h: 28 31 30 30 29 29 20 20 50 43 54 46 52 45 45 20 ; (100)) PCTFREE
00000930h: 31 30 20 50 43 54 55 53 45 44 20 34 30 20 49 4E ; 10 PCTUSED 40 IN
00000940h: 49 54 52 41 4E 53 20 31 20 4D 41 58 54 52 41 4E ; ITRANS 1 MAXTRAN
00000950h: 53 20 32 35 35 20 53 54 4F 52 41 47 45 28 49 4E ; S 255 STORAGE(IN
00000960h: 49 54 49 41 4C 20 31 30 34 38 35 37 36 20 46 52 ; ITIAL 1048576 FR
00000970h: 45 45 4C 49 53 54 53 20 31 20 46 52 45 45 4C 49 ; EELISTS 1 FREELI
00000980h: 53 54 20 47 52 4F 55 50 53 20 31 29 20 54 41 42 ; ST GROUPS 1) TAB
00000990h: 4C 45 53 50 41 43 45 20 22 54 45 53 54 5F 38 4B ; LESPACE “TEST_8K
000009a0h: 22 20 4C 4F 47 47 49 4E 47 20 4E 4F 43 4F 4D 50 ; ” LOGGING NOCOMP
000009b0h: 52 45 53 53 0A 49 4E 53 45 52 54 20 49 4E 54 4F ; RESS.INSERT INTO
000009c0h: 20 22 54 31 22 20 28 22 41 22 2C 20 22 42 22 29 ; “T1″ (”A”, “B”)
000009d0h: 20 56 41 4C 55 45 53 20 28 3A 31 2C 20 3A 32 29 ; VALUES (:1, :2)
000009e0h: 0A 02 00 02 00 16 00 01 00 64 00 54 03 01 00 00 ; ………d.T….
000009f0h: 00 00 00 04 00 C3 0D 23 39 06 00 61 61 61 61 61 ; …..?#9..aaaaa
00000a00h: 61 00 00 04 00 C3 07 4F 5B 08 00 D6 D0 D6 D0 D6 ; a….?O[..中中?
00000a10h: D0 D6 D0
00 00 FF FF 0A 43 4F 4D 4D 45 4E 54 20 ; 兄?..COMMENT
00000a20h: 4F 4E 20 54 41 42 4C 45 20 22 54 31 22 20 49 53 ; ON TABLE “T1″ IS
00000a30h: 20 0A 05 00 27 3F 3F 3F 27 0A 45 58 49 54 0A 45 ; …’???’.EXIT.E
00000a40h: 58 49 54 0A 00 00 00 00 00 00 00 00 00 00 00 00 ; XIT………….

这一次我们可以看到,红色部分的数据,也就是表的实际数据没有发生任何变化。
有变化的地方在哪里?稍微对比就可以发现,代码部分发生了变化。第一次导出时的COMMENT语句,明显可以看到“测试表”三个汉字,而第二次导出时,这三个汉字变成了三个问号。显然就是导出时GBK转换为US7ASCII码时,发生了乱码。

测试到这里,结论已经很明了了。exp导出时,表中的数据没有发生任何变化,以存储在数据库时的二进制一致的形式存储在了dmp文件中。然而,代码部分则于是纯文本形式的数据,在导出时要遵循字符集转换原则,发生转换。既然转换部分是代码形式的数据,那么下列代码中的数据都会发生转换:建表(CREATE TABLE),注释(COMMENT),创建视图(CREATE VIEW),其他程序代码(FUNCTION/PACKAGE/TRIGGER/PROCEDURE)如此等等。因此如果exp/imp时字符集不兼容,那么中文列名,注释、视图、程序代码中的中文都将会是乱码,而表中的实际数据则不会发生变化。这也是本文开头提到的问题的来源。

Oracle压缩表数据块格式解析 PartII

Posted by 老熊 on 5月 16th, 2009

接上文《Oracle压缩表数据块格式解析 PartI》,本文将分析压缩表块中实际的行数据部分。

首先我们还是先来看看行数据部分(tab 1)序号为0的行的数据:

tab 1, row 0, @0×1855
tl: 52 fb: --H-FL-- lb: 0×0  cc: 13
col  0: [ 3]  53 59 53
col  1: *NULL*
col  2: [ 5]  56 41 4c 49 44
col  3: [ 1]  4e
col  4: [ 1]  4e
col  5: [ 1]  4e
col  6: [ 2]  c1 62
col  7: [ 5]  54 41 42 4c 45
col  8: [ 7]  78 6d 03 0f 12 2a 38
col  9: [ 7]  78 6d 03 0f 12 2a 38
col 10: [19]  32 30 30 39 2d 30 33 2d 31 35 3a 31 37 3a 34 31 3a 35 35
col 11: [ 7]  41 43 43 45 53 53 24
col 12: [ 2]  c1 62
bindmp: 2c 00 0d 06 76 c9 78 79 79 79 ca c1 62 cd 54 41 42 4c 45 77 77 db 32 30 30 39 2d 30 33 2d 31 35 3a 31 37 3a 34 31 3a 35 35 cf 41 43 43 45 53 53 24 ca c1 62

bindmp那一行,与符号表中的一样,都是表示这一行数据在数据块中的原始数据,也就是压缩后的数据。我们需要把这个数据解压还原。

bindmp: 2c 00 0d 06 76 c9 78 79 79 79 ca c1 62 cd 54 41 42 4c 45 77 77 db 32 30 30 39
        2d 30 33 2d 31 35 3a 31 37 3a 34 31 3a 35 35 cf 41 43 43 45 53 53 24 ca c1 62

为了便于排版,这里把数据处理后显示为2行。

2c 行标志字节,与普通表数据块一样
00 锁标志,即ITL插槽位置
0d 这个字节表示数据被压缩后的列数,可以理解为后面的数据被分割成了几部分。
06 表示这一行中前面6列数据一定是被压缩的,具体含义在后面可以进一步解析。这一个字节,是网上很多讲压缩表格式时都没有正确地给出其意义的。
接下来的部分,与符号表类型,都是
    标志字节+数据
76 表示数据来自符号表中的序号为118(0×76)的行。
c9 依照解析符号表时的推论,应该表示接紧着后面的数据是实际的列数据,长度为1。但实际上在这里更为复杂。这里也是网上所有关于压缩表块格式分析的文章没有提到的。上面提到过”06 表示这一行中前面6列数据一定是被压缩的“,由于到这列为止,只压缩了1列,因此还没有到6列,那么这1列也是被压缩的,只不过压缩的数据没有在符号表中,而在这个字节紧接着的后面。

我们来看看上面提到的”C9”究竟代表什么?这里就不得不引入了压缩块头部中的fcls_9ir2:
Read the rest of this entry »

Oracle压缩表数据块格式解析 PartI

Posted by 老熊 on 5月 16th, 2009

前段时间花了点时间研究了一下Oracle压缩表的数据块格式,并给ODU增加了压缩表支持功能。鉴于目前在网络上基本上没有完整的关于Oracle压缩表数据块的格式分析,我觉得有必要把自己的研究得到的知识,发布出来与大家分享。

由于Oracle 9i与Oracle 10g在压缩块格式上没啥区别,我使用ODU也能够在11g上导出压缩表,所以本文就是Windows上的Oracle 9.2.0.8下的压缩表来进行块格式分析。下面我们用DBA_OBJECTS的数据建一个测试表:

SQL> create table t1 as select * from dba_objects;

表已创建。
SQL> select header_file,header_block from dba_segments where owner=user and segment_name=’T1′;

HEADER_FILE HEADER_BLOCK
----------- ------------
          8          137
SQL> alter system dump datafile 8 block 138;

系统已更改。

由于T1表所在的表空间是MSSM管理方式的表空间,一般来说紧接着段头后面的数据块就是存储表实际数据的第1个块。所以dump出了datafile 8的138块。

下面从块头往下一直进行解析。

Start dump data blocks tsn: 8 file#: 8 minblk 138 maxblk 138
buffer tsn: 8 rdba: 0×0200008a (8/138)
scn: 0×0000.003d0735 seq: 0×02 flg: 0×04 tail: 0×07350602
frmt: 0×02 chkval: 0xe80a type: 0×06=trans data
Block header dump: 0×0200008a
Object id on Block? Y
seg/obj: 0×1be4 csc: 0×00.3d0735 itc: 3 flg: - typ: 1 - DATA
fsl: 0 fnx: 0×0 ver: 0×01

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0×01   0xffff.000.00000000  0×00000000.0000.00  C---    0  scn 0×0000.003d0735
0×02   0×0000.000.00000000  0×00000000.0000.00  ----    0  fsc 0×0000.00000000
0×03   0×0000.000.00000000  0×00000000.0000.00  ----    0  fsc 0×0000.00000000

这一部分是Oracle块的头部,与其他非压缩表块的头部没什么不同。接下来是表类型(相对于索引类型的数据块)的数据头部:

data_block_dump,data header at 0×3393074
===============
tsiz: 0×1f88
hsiz: 0×280
pbl: 0×03393074
bdba: 0×0200008a
     76543210
flag=-0------  这里“O”标记表示是压缩的块
ntab=2     这里说明块里面有两个“表”的数据,实际上是“符号表”和“实际的表数据”,分别是”tab 0″和”tab 1″
nrow=291
frre=-1
fsbo=0×280
fseo=0×28c
avsp=0xc
tosp=0xc
  r0_9ir2=0×0        这里及下面的数行都是压缩表特有的数据
  mec_kdbh9ir2=0×3  这个字段后面有详细说明
  r1_9ir2=0×0
            76543210
  flag_9ir2=------OC
    fcls_9ir2[7]={ 0 32768 32768 32768 32768 32768 32768 }  这个字段后面有详细说明
    perm_9ir2[13]={ 0 11 1 12 6 7 8 9 10 2 3 4 5 }  这个字段后面有详细说明
0×32:pti[0] nrow=122 offs=0
0×36:pti[1] nrow=169 offs=122
0×3a:pri[0] offs=0×189f
0×3c:pri[1] offs=0×1890
0×3e:pri[2] offs=0×1889
0×40:pri[3] offs=0×18ae
0×42:pri[4] offs=0×18c4
0×44:pri[5] offs=0×18ce

行目录数据比较多,在此大部分省略。接下来,就是tab 0,也就是符号表的数据了:
Read the rest of this entry »

Oracle怎样标记坏块及一次数据恢复

Posted by 老熊 on 2月 24th, 2009

Oracle数据文件的坏块,可分为物理坏块和逻辑坏块。物理坏块(也可以称为介质坏块)指的是块格式本身是坏的,块内的数据没有任何意义。而逻辑坏块,指的是块内的数据在逻辑是存在问题。比如说索引块的索引值没有按从小到大排列。物理坏块一般是由于内存问题、OS问题、IO子系统问题和硬件引起,逻辑坏块一般是是由于Oracle Bug等原因引起。

Oracle数据文件的每个块,其块头为20字节。其定义如下:(来自于DSI401)

struct kcbh
{
    ub1 type_kcbh; /* block type */
    ub2 frmt_kcbh; 
    ub1 spare1_kcbh;
    ub1 spare2_kcbh;
    krdba rdba_kcbh; /* relative DBA */
    ub4 bas_kcbh; /* base of SCN */
    ub2 wrp_kcbh; /* wrap of SCN */
    ub1 seq_kcbh; /* sequence # of changes at the same scn */
    ub1 flg_kcbh; 
    ub2 chkval_kcbh;
};  
  

在块头中,seq_kcbh(占用1字节,块头偏移14)有着特殊的含义,如果该值为0xff,则表示该块被标记为corruption

下面我们做一个测试:

SQL> create table test.t1 as select * from dba_objects;

表已创建。

SQL> select header_file,header_block from dba_segments where segment_name=’T1′ and owner=’TEST’;

HEADER_FILE HEADER_BLOCK
----------- ------------
         10         1445

修改db_block_checksum参数值为TRUE,关闭数据库,我们用ultraedit修改10号文件的1447块的check sum(一个随便>0的数)及flag=0×04。然后再打开数据库。再执行下面的查询:

SQL> select count(*) from test.t1;
select count(*) from test.t1
*
ERROR 位于第 1 行:
ORA-01578: ORACLE 数据块损坏(文件号10,块号1447)
ORA-01110: 数据文件 10: ‘D:\ORACLE\ORADATA\XJ\TEST01.DBF’

由于非系统表空间在db_block_checksum参数设为FALSE时,会忽略checksum的检查。所以这里为了测试的方便设置为TRUE。
从上面的错误信息来看,块号1447这个块已经坏了,报的错误是经典的ORA-01578错误。
Read the rest of this entry »

怎样用调试工具Dump Oracle系统状态

Posted by 老熊 on 11月 21st, 2008

如果Oracle数据库hang住了,对Oracle做system dump,或做hang analyze,是研究和解决问题的有效办法,至少在提交SR时能够有更多的有用信息。如果能够连接数据库,并能够进行操作,那么用oradebug是简单快捷的办法。

但有的时候,数据库由于hang住,sqlplus不能连接时(在10g可以尝试用sqlplus -prelim连接数据库),可以使用操作系统上的调试工具来dump oracle系统状态。在记一次Oracle数据库无响应(hang住)故障的处理一文中,就曾使用dbx做systemstate dump,并发现问题所在,并最终解决了问题。下面是当时用dbx做dump的过程:

# dbx -a 446910
Waiting to attach to process 446910 …
Successfully attached to oracle.
Type ‘help’ for help.
reading symbolic information …
stopped in iosl.select at 0×9000000000c94d8 ($t2)
0×9000000000c94d8 (select+0xfffffffffff06318) e8410028 ld r2,0×28(r1)
(dbx) print ksudss(10)

Segmentation fault in slrac at 0×100083aa0 ($t2)
0×100083aa0 (slrac+0xe4) 88030000 lbz r0,0×0(r3)
(dbx) detach

从上面可以看到,使用dbx做dump的过程为:

  • 找到有异常的进程号,比如CPU非常高,HANG住的进程等。如果做系统范围的systemstate dump,可以是其他的进程。
  • dbx -a < 进程号>
  • print ksudss(10) --这里是直接调用ORACLE程序中的ksudss函数,dump level为10,就等同于在sqlplus 中用oradebug dump systemstate 10
  • detach
  • quit

在LINUX下可以使用gdb,下面是一个例子:

[oracle@xty ~]$ ps -ef | grep LOCAL
oracle 3765 3764 1 05:55 ? 00:00:00 oraclexty (DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
oracle 3767 3668 0 05:55 pts/2 00:00:00 grep LOCAL
[oracle@xty ~]$ gdb $ORACLE_HOME/bin/oracle 3765
GNU gdb Red Hat Linux (6.1post-1.20040607.62rh)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type “show copying” to see the conditions.
There is absolutely no warranty for GDB. Type “show warranty” for details.
This GDB was configured as “i386-redhat-linux-gnu”…(no debugging symbols found)…Using host libthread_db library “/lib/tls/libthread_db.so.1″.

Attaching to program: /u01/app/oracle/product/10.1.0/db_1/bin/oracle, process 3765
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libskgxp10.so…(no debugging symbols found)…done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libskgxp10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libhasgen10.so…done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libhasgen10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libskgxn2.so…done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libskgxn2.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libocr10.so…done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libocr10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libocrb10.so…done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libocrb10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libocrutl10.so…done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libocrutl10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libjox10.so…done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libjox10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libclsra10.so…done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libclsra10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libdbcfg10.so…done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libdbcfg10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libnnz10.so…done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libnnz10.so
Reading symbols from /usr/lib/libaio.so.1…done.
Loaded symbols for /usr/lib/libaio.so.1
Reading symbols from /lib/libdl.so.2…done.
Loaded symbols for /lib/libdl.so.2
Reading symbols from /lib/tls/libm.so.6…done.
Loaded symbols for /lib/tls/libm.so.6
Reading symbols from /lib/tls/libpthread.so.0…done.
[Thread debugging using libthread_db enabled]
[New Thread -1219938624 (LWP 3765)]
Loaded symbols for /lib/tls/libpthread.so.0
Reading symbols from /lib/libnsl.so.1…done.
Loaded symbols for /lib/libnsl.so.1
Reading symbols from /lib/tls/libc.so.6…done.
Loaded symbols for /lib/tls/libc.so.6
Reading symbols from /lib/ld-linux.so.2…done.
Loaded symbols for /lib/ld-linux.so.2
Reading symbols from /lib/libnss_files.so.2…done.
Loaded symbols for /lib/libnss_files.so.2
0×006967a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
(gdb) print ksudss(10)
[Switching to Thread -1219938624 (LWP 3765)]
$1 = 213658428
(gdb) detach
Detaching from program: /u01/app/oracle/product/10.1.0/db_1/bin/oracle, process 3765
(gdb) quit

然后我们可以找到有dump结果的trace文件:
Read the rest of this entry »

inside sqlplus prelim

Posted by 老熊 on 11月 15th, 2008

我们知道,在Oracle 10g中,如果数据库实例hang住了,应用及sqlplus都不能连接时,可以用sqlplus -prelim连接数据库。那么sqlplus连接时,加上-prelim这个参数有什么特别的地方呢?下面,让我们来研究一下:

测试环境:Linux AS4上的Oracle 10.2.0.1,客户端(sqlplus)版本为Windows 2003下的10.2.0.1。

首先在数据库上启用10046事件:

SQL> alter system set events ‘10046 trace name context forever’;

System altered.

先用普通的方式连接:

D:\>sqlplus /nolog

SQL*Plus: Release 10.2.0.1.0 - Production on 星期六 11月 15 15:36:02 2008

Copyright (c) 1982, 2005, Oracle. All rights reserved.

SQL> conn sys/manage@xty as sysdba
已连接。
SQL> exit
从 Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
With the Partitioning, OLAP and Data Mining options 断开

再看看加prelim参数时的情况:

D:\>sqlplus -prelim /nolog

SQL*Plus: Release 10.2.0.1.0 - Production on 星期六 11月 15 15:36:34 2008

Copyright (c) 1982, 2005, Oracle. All rights reserved.

SQL> conn sys/manage@xty as sysdba
初级连接已建立
SQL> exit
从 ORACLE 断开

从上面的信息可以看到,在使用prelim连接时,提示为“初级连接已建立”,退出sqlplus没有显示banner。
也可以通过下面的方式来用prelim方式连接数据库:

[oracle@xty ~]$ sqlplus /nolog

SQL*Plus: Release 10.2.0.3.0 - Production on Tue Dec 2 07:04:28 2008

Copyright (c) 1982, 2006, Oracle. All Rights Reserved.

SQL> set _prelim on
SQL> connect / as sysdba
Prelim connection established

再看看数据库的10046 trace:

从生成的trace文件中,可以发现在正常连接时,连接上数据库后,sqlplus自动执行了下面的SQL:

ALTER SESSION SET NLS_LANGUAGE= ‘SIMPLIFIED CHINESE’ NLS_TERRITORY= ‘CHINA’ NLS_CURRENCY= ‘¥’ NLS_ISO_CURRENCY= ‘CHINA’ NLS_NUMERIC_CHARACTERS= ‘.,’ NLS_CALENDAR= ‘GREGORIAN’ NLS_DATE_FORMAT= ‘DD-MON-RR’ NLS_DATE_LANGUAGE= ‘SIMPLIFIED CHINESE’ NLS_SORT= ‘BINARY’ TIME_ZONE= ‘+08:00′ NLS_COMP= ‘BINARY’ NLS_DUAL_CURRENCY= ‘¥’ NLS_TIME_FORMAT= ‘HH.MI.SSXFF AM’ NLS_TIMESTAMP_FORMAT= ‘DD-MON-RR HH.MI.SSXFF AM’ NLS_TIME_TZ_FORMAT= ‘HH.MI.SSXFF AM TZR’ NLS_TIMESTAMP_TZ_FORMAT= ‘DD-MON-RR HH.MI.SSXFF AM TZR’

select value$ from props$ where name = ‘GLOBAL_DB_NAME’

select SYS_CONTEXT(’USERENV’, ‘SERVER_HOST’), SYS_CONTEXT(’USERENV’, ‘DB_UNIQUE_NAME’), SYS_CONTEXT(’USERENV’, ‘INSTANCE_NAME’), SYS_CONTEXT(’USERENV’, ‘SERVICE_NAME’), INSTANCE_NUMBER, STARTUP_TIME, SYS_CONTEXT(’USERENV’, ‘DB_DOMAIN’) from v$instance where INSTANCE_NAME=SYS_CONTEXT(’USERENV’, ‘INSTANCE_NAME’)

select decode(failover_method, NULL, 0 , ‘BASIC’, 1, ‘PRECONNECT’, 2 , ‘PREPARSE’, 4 , 0), decode(failover_type, NULL, 1 , ‘NONE’, 1 , ‘SESSION’, 2, ‘SELECT’, 4, 1), failover_retries, failover_delay, flags from service$ where name = :1

而sqlplus使用prelim连接上数据库后,没有生成10046 trace文件,看起来没有执行SQL,也就是没有执行任何初始化动作和查询必要的信息。也许这也就是称之为“初级连接”的来历吧。

由于使用prelim方式连接,没有执行sql语句的,所以在数据库的某些hang住的情况下,能够连接上数据库。比如由于library cache latch 被长时间持有不能释放,不能解析SQL语句引起的hang。有的人会说,我的应用刚连上去还没做任何操作就hang住了。这只是表面现象,连接上数据库后,一般都会做一些初始化的操作,如设定环境之类的。

sqlplus -prelim能够在数据库hang住的情况下连接数据库,但只能说是连接,并不代表能够做很多操作。比如执行SQL查询。这种情况下,可能最有用的就是使用oradebug。

本文只是简单地“inside”,其实并不很深入,Oracle还会有其他的跟prelim有关的东西,是我们还没发现的。有兴趣的朋友,可以进一步研究。比如通过抓取tns包进行分析等等。

Oracle的文件号、相对文件号及其他(续)

Posted by 老熊 on 7月 20th, 2008

在前一篇文章(Oracle的文件号、相对文件号及其他)中,我们提到Oracle数据文件的相对文件号为1-1023。然而从10g开始,可以使用大文件(BIGFILE)的表空间,这种表空间只能使用1个数据文件。之所以称为大文件,是因为这种文件可以很“大”,最多可以有4G个块和128TB的大小。

大文件表空间的数据文件,其相对文件号都统一为1024,用下面的测试可以验证:

SQL*Plus: Release 10.2.0.1.0 - Production on Thu Apr 3 23:11:51 2008

Copyright (c) 1982, 2005, Oracle.  All rights reserved.

Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
With the Partitioning, OLAP and Data Mining options

SQL> select file_id,relative_fno,file_name,tablespace_name from dba_data_files;

   FILE_ID RELATIVE_FNO FILE_NAME                                TABLESPACE_NAME
---------- ------------ ---------------------------------------- --------------------
         1            1 /u02/oradata/xty/system01.dbf            SYSTEM
         2            2 /u02/oradata/xty/undotbs01.dbf           UNDOTBS1
         3            3 /u02/oradata/xty/sysaux01.dbf            SYSAUX
         4            4 /u02/oradata/xty/users01.dbf             USERS

SQL> create bigfile tablespace test1 datafile ‘/u02/oradata/xty/test01.dbf’ size 2m;

Tablespace created.

SQL> select file_id,relative_fno,file_name,tablespace_name from dba_data_files;

   FILE_ID RELATIVE_FNO FILE_NAME                                TABLESPACE_NAME
---------- ------------ ---------------------------------------- --------------------
         1            1 /u02/oradata/xty/system01.dbf            SYSTEM
         2            2 /u02/oradata/xty/undotbs01.dbf           UNDOTBS1
         3            3 /u02/oradata/xty/sysaux01.dbf            SYSAUX
         4            4 /u02/oradata/xty/users01.dbf             USERS
         5         1024 /u02/oradata/xty/test01.dbf              TEST1

SQL> create bigfile tablespace test2 datafile ‘/u02/oradata/xty/test02.dbf’ size 2m;

Tablespace created.

SQL> select file_id,relative_fno,file_name,tablespace_name from dba_data_files;

   FILE_ID RELATIVE_FNO FILE_NAME                                TABLESPACE_NAME
---------- ------------ ---------------------------------------- --------------------
         1            1 /u02/oradata/xty/system01.dbf            SYSTEM
         2            2 /u02/oradata/xty/undotbs01.dbf           UNDOTBS1
         3            3 /u02/oradata/xty/sysaux01.dbf            SYSAUX
         4            4 /u02/oradata/xty/users01.dbf             USERS
         5         1024 /u02/oradata/xty/test01.dbf              TEST1
         6         1024 /u02/oradata/xty/test02.dbf              TEST2

6 rows selected.

可以看到所有的“大”文件,其相对文件号均为1024。

每个大文件表空间只能有一个数据文件,尝试对大文件表空间增加数据文件,则会出现错误:

SQL> alter tablespace test1 add datafile ‘/u02/oradata/xty/test011.dbf’ size 2m;
alter tablespace test1 add datafile ‘/u02/oradata/xty/test011.dbf’ size 2m
*
ERROR at line 1:
ORA-32771: cannot add file to bigfile tablespace

Oracle的文件号、相对文件号及其他

Posted by 老熊 on 7月 20th, 2008

大家都知道从Oracle8开始,Oracle开始使用“相对文件号”,使原来一个数据库最多只能有1023个文件,扩展为一个表空间最多可以有1023个文件,每个库最多可以有65534个文件。

我们来作一个测试:

SQL> create tablespace test_mf datafile ‘F:\Works\oracle\product\10.2.0\oradata\
xj\many\m1.dbf’ size 100k reuse;

表空间已创建。

SQL> alter tablespace test_mf add datafile ‘F:\Works\oracle\product\10.2.0\orada
ta\xj\many\m2.dbf’ size 88k;

表空间已更改。

SQL> show parameter db_files

NAME                        TYPE        VALUE
--------------------------- ----------- ---------------
db_files                    integer     2000

SQL> begin
  2    for i in 193..1025 loop
  3       execute immediate’alter tablespace test_mf add datafile ”F:\Works\ora
cle\product\10.2.0\oradata\xj\many\m_’ || i ||”’ size 88k’;
  4   end loop;
  5  end;
  6  /
begin
*
第 1 行出现错误:
ORA-01686: 最大文件数 (1023) 对于表空间 TEST_MF 已达到
ORA-06512: 在 line 3

SQL> select count(*) from dba_data_files where tablespace_name=’TEST_MF’;

  COUNT(*)
----------
      1023

可以看到表空间TEST_MF的文件数为1023个,最多也只能为1023个。

SQL> select ts# from v$tablespace where name=’TEST_MF’;

       TS#
----------
         8

SQL> select file#,rfile#,name from v$datafile where ts#=8;

     FILE#     RFILE# NAME                                                                                             
---------- ---------- ------------------------------------------------------------                                     
         7          7 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M1.DBF                                            
         8          8 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M2.DBF                                            
         9          9 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M_1                                               
        10         10 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M_2                                               
        11         11 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M_3                                               
……………………………………………………………………….
      1019       1019 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M_1011                                            
      1020       1020 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M_1012                                            
      1021       1021 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M_1013                                            
      1022       1022 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M_1014                                            
      1023       1023 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M_1015                                            
      1024          1 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M_1016                                            
      1025          2 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M_1017                                            
      1026          3 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M_1018                                            
      1027          4 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M_1019                                            
      1028          5 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M_1020                                            
      1029          6 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\M_1021
                                            

从上面的数据可以看出,当绝对文件号小于等于1023,相对文件号与绝对文件号一样。相对文件号大于1023之后,又从1开始循环。

我们DUMP最后一个文件的文件头块看看:

Block Header:
block type=0×0b (file header)
block format=0xa2 (oracle 10)
block rdba=0×01800001 (file#=6, block#=1)
scn=0×0000.00000000, seq=1, tail=0×00000b01
block checksum value=0xe7f3=59379, flag=4
File Header:
Db Id=0xb004e979=2953111929, Db Name=XJ, Root Dba=0×0
Software vsn=0×0, Compatibility Vsn=0xa200100, File Size=0xb=11 Blocks
File Type=0×3 (data file), File Number=1029, Block Size=8192
Tablespace #8 - TEST_MF rel_fn:6

文件头里面有两部分内容,第一部分为块头,块头记录了该块的RDBA:block rdba=0×01800001 (file#=6, block#=1),因此块头记录的是相对文件号。第二部分为文件头,文件头里面有如下的记录:
    File Type=0×3 (data file), File Number=1029, Block Size=8192
   Tablespace #8 - TEST_MF rel_fn:6
因此文件头里同时记录了文件绝对号,表空间号和相对文件号。

下面我们再做另一个实验,看看段是怎么跟文件号关联的。

SQL> create tablespace test_lf datafile ‘F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\
XJ\MANY\TEST_LF.dbf’ size 1m;

表空间已创建。

SQL> select ts# from v$tablespace where name=’TEST_LF’;

       TS#
----------
         9

SQL> select file#,rfile#,name from v$datafile where ts#=9;

     FILE#     RFILE# NAME
---------- ---------- ------------------------------------------------------------
      1030          7 F:\WORKS\ORACLE\PRODUCT\10.2.0\ORADATA\XJ\MANY\TEST_LF.DBF

从上面的数据可以看出,一个表空间的数据文件,其相对文件号并不是从1开始的,而依然是从上一个用过的最后一个相对文件号继续。

SQL> select obj# from obj$ where owner#=0 and name=’T1′;

      OBJ#
----------
     47686

SQL> select obj#,dataobj#,ts#,file# from tab$ where obj#=47686;

      OBJ#   DATAOBJ#        TS#      FILE#
---------- ---------- ---------- ----------
     47686      47686          9          7

在数据字典里面记录了表的段头表空间号和相对文件号。

SQL> select header_file,header_block,relative_fno from dba_segments where segmen
t_name=’T1′ and wner=’SYS’;

HEADER_FILE HEADER_BLOCK RELATIVE_FNO
----------- ------------ ------------
       1030           11            7

在DBA_SEGMENTS视图里面,可以查到段头的相对文件号和绝对文件号。(这个视图最终是从file$、seg$等字典表里面取得数据)

再论字符集转换-Part III

Posted by 老熊 on 7月 20th, 2008

前文主要讲到的是执行DML的字符集转换,下面再讨论检索数据时的字符集转换,还是先看测试:

先将NLS_LANG设置为默认值ZHS16GBK

SQL> insert into t1 values (1,’中’,'中’);

已创建 1 行。

SQL> commit;

提交完成。

SQL> select * from t1;

        ID AA                   BB
---------- -------------------- ----------------------------------------
         1 中                   中

从抓取的网络包中找到返回的数据:

00000030                    01 3D 00 00 06 00 00 00 00 00       .=……..
00000040  10 17 3A 08 C0 CA 9B 07 F7 10 15 1A EA 23 F7 68 ..:……….#.h
00000050  DD 85 78 6C 01 1C 0D 22 36 52 00 00 00 03 00 00 ..xl…"6R……
00000060  00 39 02 00 00 81 16 00 00 00 00 00 00 00 00 00 .9…………..
00000070  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 …………….
00000080  02 02 00 00 00 02 49 44 00 00 00 00 00 00 00 00 ……ID……..
00000090  01 80 00 00 14 00 00 00 00 00 00 00 00 00 00 00 …………….
000000A0  00 00 00 00 00 00
54 0301 14 00 00 00 01 02 02 ……T………
000000B0  00 00 00 02 41 41 00 00 00 00 00 00 00 00 01 80 ….AA……….
000000C0  00 00 28 00 00 00 00 00 00 00 00 10 00 00 00 00 ..(………….
000000D0  00 00 00 00D0 0702 14 00 00 00 01 02 02 00 00 …………….
000000E0  00 02 42 42 00 00 00 00 00 00 00 00 07 00 00 00 ..BB…………
000000F0  07 78 6C 01 1C 0D 22 36 06 02 03 00 00 00 01 00 .xl…"6……..
00000100  00 00 00 00 00 00 00 00 00 00 07 02 C1 02 02D6…………….
00000110 D0024E 2D08 06 00 F2 DF 02 00 00 00 00 00 02 ..N-…………
00000120  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00000130  00 00 00 04 01 00 00 00 01 00 00 00 00 00 00 00 …………….
00000140  00 00 02 00 0E 00 03 00 00 00 00 00 07 28 00 00 ………….(..
00000150  04 00 00 16 00 00 00 01 00 00 00 00 00 00 2C 00 …………..,.
00000160  00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00000170  00 00 00                                        …            

上面展示的是返回的数据。红色分别为AA列和BB列的字符集ID:

SQL> select nls_charset_name(to_number(’0354′,’xxxx’)) from dual;

NLS_CHARSET_NAME(TO_NUMBER(’0354′,’XXXX’
----------------------------------------
ZHS16GBK

SQL> select nls_charset_name(to_number(’07D0′,’xxxx’)) from dual;

NLS_CHARSET_NAME(TO_NUMBER(’07D0′,’XXXX’
----------------------------------------
AL16UTF16

蓝色部分是列数据,D6 D0为ZHS16GBK编码的“中”,而4E 2D为AL16UTF16编码的“中”字,数据原样从数据库中返回。这两个不同的编码,最后显示的结果均为“中”字。由于数据库字符集ZHS16GBK与客户端相同,客户端没有对数据作转换,而国家字符集的“中”字,要转换为ZHS16GBK,再最终由客户端程序(SQLPLUS)显示出来。

下面把NLS_LANG设置为AMERICAN_AMERICA.US7ASCII,再进行同样的测试,发现,返回的网络包是一样,即服务器端返回的数据是一样的,并没有因为NLS_LANG的不同而不同,因此转换仍然是发生在客户端。在这次测试中,将服务器返回的数据,转换成US7ASCII编码,出现了乱码,显示为?号

再将NLS_LANG设置为AMERICAN_AMERICA.UTF8,看看返回的结果

SQL> select * from t1;

        ID AA                   BB
---------- -------------------- --------------------
         1 涓?                  涓

这次是出现了将“中”字转换成了其他汉字。为什么是转成了这个“涓”字,在此不在细述。

下面把NLS_LANG设置为AMERICAN_AMERICAN.UTF8,但增加了一个环境变量NLS_NCHAR=ZHS16GBK

SQL> select * from t1;

        ID AA                   BB
---------- -------------------- --------------------
         1 涓?                  中

在本次测试中,字符集为国家字符集AL16UTF16的列BB显示了正确的结果。这说明客户端OCI库在转换时,对国家字符集是根据NLS_NCHAR进行转换的,在这个测试中NLS_NCHAR为ZHS16GBK,将AL16UTF16编码正确地转换到了ZHS16GBK编码。

再作一个测试,将NLS_LANG设置为AMERICAN_AMERICA.ZHS16GBK,将NLS_NCHAR设置为AL16UTF16

SQL> select * from t1;

        ID AA                   BB
---------- -------------------- -----------
         1 中                   N-
由于NLS_NCHAR与国家字符集相同,因此对国家字集符的列没有作转换,直接返回。“中”字的AL16UTF16的编码为 4E 2D,在客户端操作系统中,正好是英文字符“N”和“-”的编码

结论:

在客户端向服务器端提交SQL语句时,客户端根据NLS_LANG和服务器数据库字符集,对SQL中的字符进行转换处理。如果NLS_LANG设置的字符集与服务器数据库字符集相同,不作转换,否则要转换成服务器端字符符。如果有国家字符集,客户端不作处理,由服务器端再将其转换为国家字符集。

在查询数据时,服务器端原服务器端的编码返回数据,由客户端根据返回的元数据中的字符集与NLS_LANG和NLS_NCHAR的设置进行比较。如果NLS_NCHAR没有设置,则其默认值为NLS_LANG中的字符集设置。如果数据中的字符集与客户端设置一致,不进行转换,否则要进行转换。国家字符集的转换根据NLS_NCHAR设置进行转换。

根据这个结论,再推断出EXPORT和IMPORT时的字符集转换行为:

在EXPORT时,EXP程序本身也是一个普通的客户端程序,因此在执行导出时也会按NLS_LANG和NLS_NCHAR的设置进行字符集转换。然后在DMP文件记录导出时客户端的字符集。

在IMPORT时,如果DMP文件记录的字符集与客户端字符集不一样,需要将其数据转换为客户端的字符集,然后在导入到库中时,由ORACLE的客户端OCI库按前述规则,根据NLS_LANG和服务器端字符集的比较,进行了转换。

关于EXPORT和IMPORT的行为,将在本系列文章的Part 4部分介绍。


Copyright © 2007 老熊的三分地-Oracle、UNIX、数据恢复. All rights reserved.