InnoDB的行溢出數據,Char的行結構存儲


行溢出數據

InnoDB存儲引擎可以將一條記錄中的某些數據存儲在真正的數據頁面之外,即作為行溢出數據。一般認為BLOB、LOB這類的大對象列類型的存儲會把數據存放在數據頁面之外。但是,這個理解有點偏差,BLOB可以不將數據放在溢出頁面,而即使是varchar列數據類型,依然有可能存放為行溢出數據。

varchar(n) 65535的詳解

我們先來對varchar類型進行研究。很多DBA喜歡MySQL的VARCHAR類型,因為相對於Oracle VARCHAR2最大存放4000個字節,SQL Server最大存放的8000個字節,MySQL的VARCHAR數據類型可以存放65 535個字節。但是,這是真的嗎?真的可以存放65 535個字節嗎?

如果創建varchar長度為65 535的表,我們會得到下面所示的出錯信息:

create table test (a varchar(65535)) charset=latin1 engine=innodb;

ERROR 1118(42000):Row size too large.The maximum row size for the used table

type,not counting BLOBs,is 65535.You have to change some columns to TEXT or BLOBs

從出錯消息可以看到,InnoDB存儲引擎並不支持65 535長度的varchar。這是因為還有別的開銷,因此實際能存放的長度為65 532。

下面的表創建就不會報錯了:

create table test (a varchar(65532)) charset=latin1 engine=innodb;

Query OK,0 rows affected(0.15 sec)

需要注意的是,如果在做上述例子的時候並沒有將sql_mode設為嚴格模式,則可能會出現可以建立表,但是會有一條警告信息

create table test (a varchar(65535)) charset=latin1 engine=innodb;

Query OK,0 rows affected,1 warning(0.14 sec)

show warnings\G

***************************1.row***************************

Level:Note

Code:1246

Message:Converting column'a'from VARCHAR to TEXT

1 row in set(0.00 sec)

警告信息提示了,之所以這次可以創建,是因為MySQL自動將VARCHAR轉換成了TEXT類型。如果我們看test的表結構,會發現MySQL自動將VARCHAR類型轉換為了MEDIUMTEX類型:

show create table test\G

***************************1.row***************************

Table:test

Create Table:CREATE TABLE 'test' (

  'a'mediumtext

)ENGINE=InnoDB DEFAULT CHARSET=utf8

1 row in set(0.00 sec)

還需要注意的是,上述創建VARCHAR長度為65 532的表其字符類型是latin1的。

如果換成GBK或者UTF-8,又會產生怎樣的結果呢?

create table test (a varchar(65532)) charset=gbk engine=innodb;

ERROR 1074(42000):Column length too big for column 'a'(max=32767);use BLOB

or TEXT instead

create table test (a varchar(65532)) charset=utf8 engine=innodb;

ERROR 1074(42000):Column length too big for column'a'(max=21845);use BLOB

or TEXT instead

這次即使創建列的VARCHAR長度為65 532也會報錯,但是兩次報錯中對於max值的提示是不同的。因此我們應該理解VARCHAR(N)中,N指的是字符的長度VARCHAR類型最大支持65 535指的是65 535個字節

此外,MySQL官方手冊中定義的65 535長度是指所有VARCHAR列的長度總和,如果列的長度總和超出這個長度,依然無法創建,如下所示:

create table test2 (a varchar(22000),b varchar (22000),c varchar (22000)) charset=latin1 engine=innodb;

ERROR 1118(42000):Row size too large.The maximum row size for the used table

type,not counting BLOBs,is 65535.You have to change some columns to TEXT or BLOBs

3個列長度總和是66 000,因此InnoDB存儲引擎再次報了同樣的錯誤。

溢出數據的存儲

即使我們能存放65 532個字節了,但是有沒有想過,InnoDB存儲引擎的頁為16KB,即16 384個字節,怎么能存放65 532個字節呢?一般情況下,數據都是存放在B-tree Node的頁類型中,但是當發生行溢處時,則這個存放行溢處的頁類型為Uncompress BLOB Page

我們來看個例子:

create table t (a varchar (65532));

insert into t select repeat ('a',65532);

這里創建了擁有一個長度為65 532的varchar類型表,

接着用py_innodb_page_info工具看下面的表空間文件,看看頁的類型有哪些。可以看到一個B-tree Node頁類型,另外有4個為Uncompressed BLOB Page,這些頁中才是真正存放了65 532個字節的數據。既然實際存放的數據都放到BLOB頁中,那數據頁中又存放了些什么東西呢?同樣,通過之前的hexdump來讀取表空間文件,可以看到,從0x0000c093到0x0000c392數據頁面其實只保存了varchar(65 532)的前768個字節的前綴(prefix)數據(這里都是a),之后跟的是偏移量,指向行溢出頁,也就是前面我們看到的Uncompressed BLOB Page。因此,對於行溢出數據,其存放方式下圖4所示:

那多少長度VARCHAR是保存在數據頁里的,多少長度開始又保存在BLOB頁呢?我們來思考一下,InnoDB存儲引擎表是索引組織的,即B+樹的結構。因此每個頁中至少應該有兩個行記錄(否則失去了B+樹的意義,變成鏈表了)。因此如果當頁中只能存放下一條記錄,那么InnoDB存儲引擎會自動將行數據存放到溢出頁中。考慮下面表的一種情況:

create table t (a varchar (9000));

insert into t select repeat ('a',9000);

表t的變長字段長度為9000,能放在一個頁中,但是不能保證2條記錄都能存放在一個頁中,所以此時如果用py_innodb_page_info工具查看,可知是存放在BLOB頁中。

如果可以在一個頁中至少放入兩行的數據,那varchar就不會存放到BLOB頁中。經過試驗我發現,這個閾值的長度為8098。如我們建立列為varchar(8098)的表,然后插入兩條記錄:

create table t (a varchar (8098));

insert into t select repeat ('a',8098);

insert into t select repeat ('a',8098);

接着用py_innodb_page_info工具對表空間t.ibd進行查看,可以發現此時的行記錄都是存放在數據頁中,而不是BLOB頁了。如果熟悉Microsoft SQL Server數據庫的DBA,可能會感覺InnoDB存儲引擎對於varchar的管理和SQL Server中的VARCHAR(MAX)類似。

對於溢出行的管理,同樣是采用段的方式,即InnoDB存儲引擎同Oracle一樣有BLOB行溢出段。另一個問題是,對於TEXT或者BLOB的數據類型,我們總是以為它們是放在Uncompressed BLOB Page中的,其實這也是不准確的,放在數據頁還是BLOB頁同樣和前面討論的VARCHAR一樣,至少保證一個頁能存放兩條記錄,如: 

create table t (a blob);

insert into t select repeat ('a',8000);

insert into t select repeat ('a',8000);

insert into t select repeat ('a',8000);

insert into t select repeat ('a',8000);

我們建立一個BLOB列的表,插入4行數據長度為8000的記錄,如果用py_innodb_page_info工具對表空間t.ibd進行查看,會發現這些記錄其實並沒有保存在BLOB頁中。當然,既然我們使用了BLOB列類型,一般情況下我們不可能存放長度這么小的數據,因此對於大多數的情況,BLOB的行數據還是會發生行溢出,實際數據保存在BLOB頁中,數據頁只保存數據的前768個字節。

Char的行結構存儲

通常的理解VARCHAR是存儲變長長度的字符類型,CHAR是存儲定長長度的字符類型從MySQL 4.1開始,CHR(N)中的N指的是字符的長度而不是之前版本的字節長度。那也就是說,在不同的字符集下,CHAR的內部存儲的不是定長的數據。

我們來看下面的這個情況: 

create table j (a char(2)) charset=gbk;

insert into j select 'ab';

set names gbk;

insert into j select '我們';

insert into j select 'a';

j表的字符集是GBK的,我們分別插入了兩個字符的數據'ab'和'我們',查看所占字節可得如下結果: 

select a, char_length(a),length(a) from j\G;

***************************1.row***************************

a:ab

char_length(a):2

length(a):2

***************************2.row***************************

a:我們

char_length(a):2

length(a):4

通過不同的字符串長度函數可以看到,前兩個記錄'ab'和'我們'字符串的長度都是2,但是內部存儲上'ab'占用兩個字節,而'我們'占用4個字節。

如果看內部十六進制的存儲,可以看到:select a,hex(a) from j\G;

***************************1.row***************************

a:ab

hex(a):6162

***************************2.row***************************

a:我們

hex(a):CED2C3C7

對於字符串'ab'的存儲內部為0x6162,而'我們'是0xCED2C3C7,這就可以很明顯地看出區別了。因此對於多字節的字符編碼CHAR類型,不再代表是固定長度的字符串了,比如UTF-8下CHAR(10)最小可以存儲10個字節的字符,而最大可以存儲30個字節的字符。所以,對於多字節字符編碼的CHAR數據類型的存儲,InnoDB存儲引擎在內部將其視為是變長的字符,這就表示了,在每行變長長度列表中會記錄CHAR數據類型的長度。通過hexdump工具我們來看j.ibd文件的內部: 

0000c070 73 75 70 72 65 6d 75 6d 02 00 00 00 10 00 1c 00|supremum……
0000c080 00 00 b6 2b 2b 00 00 00 51 52 da 80 00 00 00 2d|……++……QR……-
0000c090 01 10 61 62 04 00 00 00 18 ff d5 00 00 00 b6 2b|..ab……+
0000c0a0 2c 00 00 00 51 52 db 80 00 00 00 2d 01 10 ce d2|,……QR……-……
0000c0b0 c3 c7 00 00 00 00 00 00 00 00 00 00 00 00 00 00|……
整理后可以得到如下結果:
#第一行記錄
02/*變長字段長度2,char視作變長類型*/
00/*NULL標志位*/
00 00 10 00 1c/*記錄頭信息*/
00 00 00 b6 2b 2b/*RowID*/
00 00 00 51 52 da/*TransactionID*/
80 00 00 00 2d 01 10/*Roll Point*/
61 62/*字符'ab'*/
#第二行記錄
04/*變長字段長度4,char視作變長類型*/
00/*NULL標志位*/
00 00 18 ff d5/*記錄頭信息*/
00 00 00 b6 2b 2c/*RowID*/
00 00 00 51 52 db/*TransactionID*/
80 00 00 00 2d 01 10/*Roll Point*/
c3 d2 c3 c7/*字符'我們'*/
#第三行記錄
02/*變長字段長度2,char視作變長類型*/
00/*NULL標志位*/
00 00 20 ff b7/*記錄頭信息*/
00 00 00 b6 2b 2d/*RowID*/
00 00 00 51 53 17/*TransactionID*/
80 00 00 00 2d 01 10/*Roll Point*/
61 20/*字符'a'*/

在InnoDB存儲引擎內部對於CHAR類型在多字節字符集類型的存儲了,CHAR很明確地被視為了變長類型,對於未能占滿長度的字符還是填充0x20。內部對於字符的存儲和我們用hex函數看到的也是一致的。我們可以說,在多字節字符集的情況下,CHAR和VARCHAR的行存儲基本是沒有區別的。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM