行溢出数据

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的行存储基本是没有区别的。

InnoDB的行溢出数据,Char的行结构存储的更多相关文章

  1. MySQL 行溢出数据

    MySQL 行溢出数据 MySQL 对一条记录占用的最大储存空间是有限制的,除了 BLOB 和 TEXT 类型之外,其他所有列 (不包括隐藏列和记录头信息) 占用的字节长度不能超过 65535 个字节 ...

  2. 了解 MySQL的数据行、行溢出机制吗?

    目录 一.行 有哪些格式? 二.紧凑的行格式长啥样? 三.MySQL单行能存多大体量的数据? 四.Compact格式是如何做到紧凑的? 五.什么是行溢出? 六.行 如何溢出? 七.思考一个问题 关注送 ...

  3. blob及行外数据

    本文中我们假设innodb_page_size为16k,记录格式为compact. 1 大字段 大字段的类型可以参看这里, Data Type Storage Required TINYBLOB, T ...

  4. 【MySQL经典案例分析】关于数据行溢出由浅至深的探讨

    本文由云+社区发表 一.从常见的报错说起 ​ 故事的开头我们先来看一个常见的sql报错信息: ​ 相信对于这类报错大家一定遇到过很多次了,特别对于OMG这种已内容生产为主要工作核心的BG,在内容线的存 ...

  5. MySQL数据行溢出的深入理解

    一.从常见的报错说起 故事的开头我们先来看一个常见的sql报错信息: 相信对于这类报错大家一定遇到过很多次了,特别对于OMG这种已内容生产为主要工作核心的BG,在内容线的存储中,数据大一定是个绕不开的 ...

  6. 多行溢出隐藏显示省略号功能的JS实现

    在页面重构中,经常需要将过多的内容隐藏而显示部分.在单行文本中实现非常简单,但是在多行文本中,则需要根据实际选择不同的方式. 用CSS实现多行溢出隐藏的代码非常简单,但是兼容性也相对较低. displ ...

  7. QDataStream类参考(串行化数据,可设置低位高位,以及版本号),还有一个例子

    QDataStream类提供了二进制数据到QIODevice的串行化. #include 所 有成员函数的列表. 公有成员 QDataStream () QDataStream ( QIODevice ...

  8. 数据仓库之抽取数据:通过bcp命令行导入数据

    原文:数据仓库之抽取数据:通过bcp命令行导入数据 在做数据仓库时,最重要的就是ETL的开发,而在ETL开发中的第一步,就是要从原OLTP系统中抽取数据到过渡区中,再对这个过渡区中的数据进行转换,最后 ...

  9. .net dataGridView当鼠标经过时当前行背景色变色;然后【给GridView增加单击行事件,并获取单击行的数据填充到页面中的控件中】

    1.首先在前台dataGridview属性中增加onRowDataBound属性事件 2.然后在后台Observing_RowDataBound事件中增加代码 protected void Obser ...

随机推荐

  1. 动态链接库dll,导入库lib,静态链接库lib

    目前以lib后缀的库有两种,一种为静态链接库(Static Libary,以下简称“静态库”),另一种为动态连接库(DLL,以下简称“动态库”)的导入库(Import Libary,以下简称“导入库” ...

  2. 最小点集覆盖/HDU2119

    题目连接 先试一下题/?/ 最小点集覆盖=最大匹配 /*根据i.j建图,跑一边最大匹配 */ #include<cstdio> #include<cstring> using ...

  3. jenkins插件 build-name-setter

    #${BUILD_NUMBER}-${PROJECT_NAME}-${ENV,var="VARIABLENAME"}-${ENV,var="BUILD_USER" ...

  4. hdu-1978_How many ways dfs+记忆化搜索

    How many ways Time Limit : 3000/1000ms (Java/Other)   Memory Limit : 32768/32768K (Java/Other) Total ...

  5. HDU 2671 Can't be easier

    简单的几何题目 点(a,b)关于直线Ax+By+C=1对称点的公式 #include<cstdio> #include<cstring> #include<cmath&g ...

  6. Leetcode389

    Find the Difference Given two strings s and t which consist of only lowercase letters. 给出两个字符串,s和t,都 ...

  7. haar_adaboost_cascade阅读资料

    1,AdaBoost中利用Haar特征进行人脸识别算法分析与总结1——Haar特征与积分图 2,浅谈 Adaboost 算法 3,浅析人脸检测之Haar分类器方法 4,http://wenku.bai ...

  8. angularjs ng-switch

    <p> <a href="#" ng-click="toggle()">Toggle Section</a> </p& ...

  9. VS2010 C#调用C++ DLL文件

    http://www.soaspx.com/dotnet/csharp/csharp_20110406_7469.html http://www.cnblogs.com/warensoft/archi ...

  10. 关于自定义jar包(tomcat)的添加

    1 鼠标右击工程 选择 properties 或者 Ait + Enter 2 选择Libraries 3 点击Add Library... 4 选择User Library  点击 Next 5 如 ...