Home > Knowledge > CLOB的物理存储结构及语言编码详解

CLOB的物理存储结构及语言编码详解

最近测试一个工具Database Migration for Unicode,功能是把非unicode编码存储数据的数据库转化为Unicode的数据库。在guide中提到一句,CLOB类型的列无需转换编码,这句话引起我的兴趣,为何别的字符类型char或者varchar2都需要编码转换,为什么CLOB不需要。在网上搜索,找到了DBSNAKE的这篇文章(http://dbsnake.com/2010/07/lob-characterset-endian.html)。里面提到了一篇Note [ID 257772.1],核心意思是,基本上从10g之后的CLOB的字符编码无论在何种CharacterSet下均为AL16UTF16。现在特来验证一下。

1. 在JA16EUC的数据库中创建测试用户及测试表(LOB存储为disable storage in row)

SELECT value FROM nls_database_parameters WHERE PARAMETER='NLS_CHARACTERSET';
 
VALUE
 
------------------------
 
JA16EUC
 
CREATE TABLE tst.tab1(col1 CLOB) tablespace DATA
lob (col1) store AS (tablespace DATA disable storage IN row nocache);
 
INSERT INTO tst.tab1 VALUES(’ビジネス運営’);
 
commit;

找出这一行存储的物理文件和block

SELECT dbms_rowid.rowid_block_number(rowid) bno,col1 FROM tst.tab1;
 
BNO COL1
 
---------- ------------------------------
 
135 ビジネス運営
 
SELECT FILE_NAME,FILE_ID FROM dba_data_files;
 
FILE_NAME                                             FILE_ID
 
-------------------------------------------------- ----------
 
/u01/app/oracle/oradata/euc2/system01.dbf                   1
 
/u01/app/oracle/oradata/euc2/sysaux01.dbf                   2
 
/u01/app/oracle/oradata/euc2/undotbs01.dbf                  3
 
/u01/app/oracle/oradata/euc2/users01.dbf                    4
 
/u01/app/oracle/oradata/euc2/DATA.dbf                       5
 
ALTER system dump datafile 5 block 135;

在udump里找到对应的trace文件,因为使用的是11gR2,所以路径略有不同

SELECT value FROM v$diag_info WHERE name='Default Trace File';
 
VALUE
 
----------------------------------------------------------------
 
/u01/app/oracle/diag/rdbms/euc2/euc2/trace/euc2_ora_6234.trc

在上述trace里找到

block_row_dump:

tab 0, row 0, @0x1f80

tl: 24 fb: –H-FL– lb: 0×1  cc: 1

col  0: [20]  00 54 00 01 02 08 80 00 00 02 00 00 00 01 00 00 00 2d c8 b5 –>这里后10个字节就是LobID

LOB

Locator:

Length:        84(20)

Version:        1

Byte Length:    2

LobID: 00.00.00.01.00.00.00.2d.c8.b5

Flags[ 0x02 0x08 0x80 0x00 ]:

Type: CLOB

Storage: BasicFile

Disable Storage in Row

Characterset Format: IMPLICIT  –>可以发现这里并没有写明CLOB字段所使用的编码

Partitioned Table: No

Options: VaringWidthReadWrite

这里就需要介绍一下LOB在enable还是disable storage in row这两种模式下的存储格式。

先看一下我们这个含有LOB的TAB1表有哪些相关的segment:

SELECT SEGMENT_NAME,SEGMENT_TYPE FROM dba_segments WHERE OWNER='TST';
 
SEGMENT_NAME                             SEGMENT_TYPE
 
---------------------------------------- ---------------
 
TAB1                                     TABLE
 
SYS_IL0000017724C00001$$                 LOBINDEX
 
SYS_LOB0000017724C00001$$                LOBSEGMENT

背景知识,LOB是Large Object(大对象)的简称,包括CLOB,NCLOB,BLOB,BFILE。这里我们讨论的只是CLOB。创建每一个LOB列除了表中的一个column外,另外同时还创建了两个物理结构:LOB index和LOB segment。

首先,需要了解的是在默认情况下LOB segment是和其所在的table存储在一起的,即in-line。而另外一种,LOB的访问是通过LOB locator来访问LOB index,LOB index再指向真正的物理存储数据的地方即LOB segment里的chunk。LOB locator就是指向LOB index的Key。

接下来先看一下Enable storage in row的方式下LOB的存储。这里分为两种情况,小于4000字节和大于4000字节。

LOB字段的locator加Inode加LOB的内容总长度小于4000字节时:

Col1 LOB Col
1 Locator1,Inode 1,LOB data 1
2 Locator2,Inode 2,LOB data 2

这里LOB字段的存储是按如下的格式:

2字节的LOB locator长度(除这两个长度字节外)
2字节的LOB locator structure版本
4字节的FLAG
2字节的字符集里字符的长度
10字节的LOB ID
16字节的inode

之后就是LOB data,所以这里in-line的情况下理论上最多能存储4000-2-2-4-2-10-16=3964字节。

其他情况:

Col1 LOB Col LOB Col
1 Locator1,Inode 1 ——–> LOB data 1
2 Locator2,Inode 2 ——–> LOB data 2

就是说即使Enable storage in row,LOB字段在LOB的控制信息(即Locator加Inode)长度加LOB data长度大于4000字节的时候,LOB data也是不存储在row内部的。在表里LOB字段的第36-84字节存储的是LOB data的前12个chunk的RDBA。如果12个不够的情况下,这里存放的不是chunk的RDBA,而是LOB index的RDBA。

另外,在Disable storage in row的方式下,数据都是这么存储的:

Col1 LOB Col LOB Index LOB Storage
1 Locator1 ——> Inode 1 ——> LOB data 1
2 Locator2 ——> Inode 2 ——> LOB data 2

LOB index是一个类似于unique index的结构。这里我们把LOB index的叶子块的信息dump出来。

先找出TAB1的OBJ#:

SELECT object_id FROM dba_objects WHERE object_name='TAB1';
 
OBJECT_ID
 
----------
 
17724
 
再找出TAB1里的LOB INDEX的ind#:
 
SELECT IND# from lob$ where OBJ#='17724';
 
IND#
 
----------
 
17726

把这个index的tree给dump出来:

ALTER session SET events 'immediate trace name treedump level 17726';
 
SELECT value FROM v$diag_info WHERE name='Default Trace File';
 
VALUE
 
-------------------------------------------------------------
 
/u01/app/oracle/diag/rdbms/euc2/euc2/trace/euc2_ora_11704.trc

从上面这个trace中找到:

—– begin tree dump

leaf: 0×1400093 20971667 (0: nrow: 1 rrow: 1)

—– end tree dump
解释:前两列分别是RDBA的十六进制和十进制地址,由此可以得出leaf block的number。Nrow 是行数,如果是branch block的话就是child nodes的个数。

SELECT DBMS_UTILITY.data_block_address_block(TO_NUMBER(LTRIM('1400093'),'xxxxxxxx'))
 BNO FROM dual;
 
BNO
 
----------
 
147
 
ALTER system dump datafile 5 block 147;

得到50个字节的LOB index

row#0[7982] flag: ——, lock: 2, len=50, data:(32):

00 20 03 00 00 00 00 00 00 0c 00 00 00 00 00 01 01 40 00 8f 00 00 00 00 00

00 00 00 00 00 00 00

col 0; len 10; (10):  00 00 00 01 00 00 00 2d c8 b5

col 1; len 4; (4):  00 00 00 00

LOB index的结构是:

长度为50个byte,具体组成如下:

行头flag(1个byte)

lock byte(1个byte)

lob index key ( 32个byte,要么由16个byte的lob inode+4个RDBA组成,要么由8个RDBA组成。Disable storage in row的情况下是前者)

lob id的长度(1个byte)+ lob id(10个byte)

page number的长度(1个byte)

page number(4个byte)

Inode的结构是:

2字节的in-line的data的长度

1字节的inode标志位:1代表这是个有效的lob,2代表这个inode在LOB index里,3代表这个lob是个有效的lob,而且在LOB index里

1字节的预留位

4字节full oracle blocks数量

2字节最后一个page里有多少byte的数据

6字节的LOB version,0×0001.00000000的形式

所以这里01 40 00 8f就是第一个chunk的RDBA的地址。

SELECT DBMS_UTILITY.data_block_address_block(TO_NUMBER(LTRIM('0140008f'),'xxxxxxxx'))
 BNO FROM dual;
 
BNO
 
----------
 
143
 
ALTER system dump datafile 5 block 143;

可以从trace中得到CLOB字段的实际存储的物理格式:

Long field block dump:

Object Id    17725

LobId: 00010002DC8B5 PageNo        0

Version: 0×0000.00000000  pdba: 20971656

30 d3 30 b8 30 cd 30 b9 90 4b 55 b6 00 20 00 20 00 20 00 20 00 20 00 20

我们验证一下这里的十六进制码到底跟哪个CharSet里的文字的物理格式一样:

CREATE TABLE aaa (col1 varchar2(100),col2 nvarchar2(100));
 
INSERT INTO aaa VALUES ('ビジネス運営','ビジネス運営');
 
SELECT dump(col1,1016),dump(col2,1016) FROM aaa;
 
DUMP(COL1,1016)
 
--------------------------------------------------------------------------------
 
DUMP(COL2,1016)
 
--------------------------------------------------------------------------------
 
Typ=1 Len=12 CharacterSet=JA16EUC: a5,d3,a5,b8,a5,cd,a5,b9,b1,bf,b1,c4
 
Typ=1 Len=12 CharacterSet=AL16UTF16: 30,d3,30,b8,30,cd,30,b9,90,4b,55,b6

可见CLOB的物理存储格式并不是JA16EUC,而是AL16UTF16。

在研究这个东西的过程中得到了崔华(http://dbsnake.com)的大力帮助,在这里表示感谢。

参考:

DSI 402e:P52-P57,P250-P254

http://dbsnake.com/2010/07/some-lob-internal.html

全文PDF版本(排版好看很多)在这里 CLOB_Storage_Charset.pdf

Categories: Knowledge Tags: , ,
  1. January 11th, 2011 at 12:08 | #1

    不错的研究。打破砂锅问到底的态度是一定需要的。

  1. No trackbacks yet.
You must be logged in to post a comment.