blob及行外数据

    本文中我们假设innodb_page_size为16k,记录格式为compact.

  1 大字段

    大字段的类型可以参看这里

Data TypeStorage Required
TINYBLOBTINYTEXT L + 1 bytes, where L < 28
BLOBTEXT L + 2 bytes, where L < 216
MEDIUMBLOBMEDIUMTEXT L + 3 bytes, where L < 224
LONGBLOBLONGTEXT L + 4 bytes, where L < 232

    这里除了TINYBLOBTINYTEXT,字段长度都可能超过16k.这时一个page肯定是存储不下这个字段,于是就需要行外存储了。

  2 行外存储

   2.1 记录格式

   行外记录存储方式与记录格式有关,记录格式可以参考这里

     对于compact格式:768字节前缀数据和20字节行外指针。

     对于dynamic格式:20字节行外指针,没有前缀数据。

 

   20个字节的行外信息包括

     表空间id(4个字节)

     页号pageno(4个字节)

     行外首页页偏移(4个字节)

     剩余8字节存行外数据长度,其中高两位分表存行外数据拥有标记和继承表标记。下一节介绍。

   2.2 行外页存储(off-page)  

   innodb的文件是通过表空间(space),段(segment),簇(extent),页(page)的方式进行管理的。表空间文件按page_size划分为页。每64个页组成一个簇。簇可以属于某个段,也可以不属于任何段,不属于任何段的簇中的页称为frament页。每个段包含若干簇和一些(<=16)frament page. 每个innodb表有两个段,叶子段和非叶子段。非叶子段中都是Btree非叶子页。叶子段中的页包括叶子页和blob行外页(如果有行外数据)。innodb数据组织方式可以参考这里

    行外页包括文件页头38个字节,blob页头8字节,文件尾8字节。

        blob.png

 

   这里说明下blob页头信息

     1)当前blog页存储的行外数据长度

     2)当前blob页的下一个blob页。如果没有下一个页,则置0xFFFFFFFF

   行外也最多可以存16k-(38+8+8)=16330字节的数据。

   当一个50000字节的blob字段存储是需要((50000-768)/16330≈3.01)4个blog页来存储。在这种情况下,最后一个页才存储了242字节的数据。其余空间都浪费了。

       blob.png

   2.3 行外记录存储条件(external record)

     1 索引列不会产生行外数据。

     2 条件:当行记录长度超过空page剩余空间的一半,则会产生行外记录。空page剩余空间是指没有任何记录的页中除去页头和页尾所占的空间。即超过(16k-(头120+尾12))/2=8126字节就会产生行外记录。(参考page_zip_rec_needs_ext)

     3 一行记录可产生多个行外数据。生成行外数据的规则根据2中的条件需产生行外数据时,是每次从记录中选择最长的字段转为行外存储,如果转化后行列记录还超过8126字节,那么会选择次长的字段作为行外数据存储。这个过程会递归下去,直至行内数据小于8126字节。还有种极端情况就是,将所有可以转为行外存储的字段都转换后,行内数据还超过6866字节的情况,这是就会报错。ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 8126. You have to change some columns to TEXT or BLOBs

drop table t1;
create table t1(c1 blob,c2 blob,c3 blob,c4 blob,c5 blob,c6 blob,c7 blob,c8 blob,c9 blob,c10 blob,c11 blob);
insert into t1 values(repeat('0', 10000),repeat('1', 10000),repeat('2', 10000),repeat('3', 10000),repeat('4', 10000),repeat('5', 10000),repeat('6', 10000),repeat('7', 10000),repeat('8', 10000),repeat('9', 10000),repeat('10', 10000));

     这条记录所有字段转化为行外数据后记录长度为8716>8126.因此innodb认为这条记录太大了存不下。

    2*11+2+5+6+6+7+11*788=8716> 8126

     Compact记录格式:物理存储的记录包括记录头和记录体 详细可以参考这里这里

       记录头:变长非空字段长度数组+可空字段bitmap+extrabytes

       记录体: 主键+trx_id+roll_ptr+其它字段

     对于上面的记录:

     记录头:变长非空字段长度数组(2*11)+可空字段bitmap(2)+extrabytes(5)

     记录体: 主键(row_id 6)+trx_id(6)+roll_ptr(7)+其它字段(11*788)     

    4 并不是只有大字段会产生行外数据。其他变长类型如varachar, varbinary.当其长度超过M时,也可能产生行外数据。compact格式M=788,dynamic格式M=20.同样大字段也不一定都产生行外数据。

  3 行外页的管理

    3.1 何时申请行外页

      1 insert:插入数据时如果有字段满足行外记录存储的条件,则会从叶子段中申请blob page。

      2 update: 更新变长字段时,innodb先delete 老记录(真正删除,不是delete mark),再插入新记录。插入的新记录又会进行是否满足行外记录存储的判断。如满足条件则会从叶子段中申请blob page。因此,更新变长记录有可能导致原来不是行外存储的非更新字段变为行外存储。如果更新的是行外存储的字段,新记录字段有可能变为行内存储。 

    3.2 何时释放行外页

       1 rollabak:

         1) insert产生行外页回滚时需将申请的行外页释放

         2) update操作产生新的行外页回滚时需将申请的行外页释放

       2 purge

         1)delete 包含行外数据的行,提交后,数据没有真正删除,而只是在记录头extrabytes中打上了删除标记。在purge清理undo时,根据undo记录真正删除记录时,将行外页释放。

         2)update更新有行外数据的列,真正删除老记录时不会把行外页释放。而是等到提交后purge时才真正释放。

    3.4 行外页的重用

         1 当更新变长字段时,innodb先delete 老记录,再插入新记录。delete老记录并没有释放行外页。这个就是为了重用。例如,当更新的字段为非行外字段数据时,其他有行外数据的字段并没有更新。此时插入的新记录行外并没有改变,仍然指向老的行外数据页。这样新插入记录不需要新分配blog页,再copy行外数据。

         2)更新的字段为行外字段数据时。老记录行外页并没有释放。这样回滚日志中不需要记录整个行外数据,只记录行内数据即可。回滚时可以重用老的行外数据。  

    3.5 拥有标记和继承标记

        前面讲到行外信息中有拥有标记和继承标记。标记的意义:

        高一位0表示拥有者,只有拥有者purge时才可以free

        高二位1表示继承者,继承者在回滚时不能free

        行外页的释放原则:

          1)rollback时free,必须是拥有者,并且不能是继承者。    

          2)purge时free,必须是拥有者。

  4 行外页的使用实践

   初始化空表  

    drop table t;  

    CREATE TABLE t(a INT PRIMARY KEY,b TEXT, c varchar(100),d int)ENGINE=InnoDB;

   4.1 insert 

     INSERT INTO t VALUES(1,repeat('a',12345),'cc',1);

     insert: 产生的行外记录,行外信息标记为拥有者(0)和非继承者(0)

 

     回滚:释放blob页

     purge:insert 不需要purge

   4.2 update

     1 更新主键             

       update t set a=10 where a=1;

       老记录delete mark, insert新记录,blog 页重用。

       修改老记录行外信息标记为非拥有者(1)

       新记录行外信息标记为拥有者(0)和继承者(1)

     blob.png

       回滚:1)删除新插入的记录,跟据回滚释放blog页规则不释放blob page.

                2)在delete mark的记录上的基础上修改。并将所有行外数据置为拥有者。

       purge: 删除delete mark记录,但也不释放blob page.

 

    2 更新行外数据字段

      update t set b=repeat('b',12345) where a=1;

      老记录真正删除,插入新记录。

      老记录真正删除时blob页不free

      新老记录的标记均为拥有者(0)和非继承者(0)

    blob.png

      回滚:1)在插入的记录的基础上构造还原记录。释放更新新产生的blob page,删除插入的记录。

            2)插入还原记录,并修改所有行外字段为拥有者。

      purge: 释放老的blob page.

 

   3 更新其他变长字段

     update t set c='ccccc' where a=1;

     老记录真正删除,插入新记录。老记录真正删除时blob页不free

     新老记录的标记均为拥有者(0)

   blob.png

     回滚:1)根据插入的记录更新构造还原记录,删除插入的记录。

           2)插入还原记录,并修改所有行外字段为拥有者。

     purge:不释放blob页。

 

    4 更新定长字段

      update t set d=2 where a=1;

      原地更新,行外字段没有变化。

      blob.png

      回滚和purge时均不需要操作blob 页。

   4.3 delete

     delete from t where a=1;

     delete时记录头delete mark, blob页不free

    blob.png

     回滚不需要处理blob页。

     purge时free blob页。

    4.4 混合实践

      上节介绍了insert,delete,udpate这些单一原子操作,混合操作都由这些原子操作组成。

      1 更新主键,再更新行外字段

       update t set a=10 where a=1;

       update t set b=repeat('b',12345) where a=10;

      blob.png

       回滚:反向应用undo日志,只释放更新行外产生的日志。

       purge:正向purge undo日志,purge delete mark时,不释放。purge更新主键时才释放。

     2 更新主键,再更新变长字段
     update t set a=10 where a=1;
     update t set c='ccccc' where a=10;

     blob.png   

       回滚:反向应用undo日志,不释放blob页。

       purge:正向purge undo日志,purge delete mark时,不释放。purge更新主键时才释放。

 思考:

1 blob pape属于叶子段。叶子段存储真正的行内数据。blog page也在页子段。这样会出现同一个extent中即有叶子页也有blob页。一个extent中的页在物理上是连续的,这样可以增加记录在物理上的连续性。而blog page则破坏了这种连续性。对于只查询非行外字段的查询有较影响,同时还会影响预读机制。因此,blog page可以用单独的段存储,从而不会影响叶子段。

2 同一个page只存储一个行外字段数据。多个行外数据不能公用blob page.造成blob page的浪费。

原文地址:https://www.cnblogs.com/justfortaste/p/3949732.html