InnoDB行格式 innodb_file_format 对TEXT/BLOB的影响:

    摘录自:

        http://seanlook.com/2016/05/18/mysql-blob-row_format/

        http://hidba.org/?p=551

        https://www.percona.com/blog/2008/01/11/mysql-blob-compression-performance-benefits/

        https://www.percona.com/blog/2012/05/30/data-compression-in-innodb-for-text-and-blob-fields/

        http://blog.opskumu.com/mysql-blob.html   # MySQL大字段溢出导致数据回写失败


2.1 compact

    在 Antelope 两种行格式下,如果blob列值长度 <= 768 bytes,就不会发生行溢出(page overflow),内容都在数据页(B-tree Node);如果列值长度 > 768字节,那么前768字节依然在数据页,而剩余的则放在溢出页(off-page)


    上面所讲的讲的blob或变长大字段类型包括blob,text,varchar,其中varchar列值长度大于某数N时也会存溢出页,在latin1字符集下N值可以这样计算:innodb的块大小默认为16kb,由于innodb存储引擎表为索引组织表,树底层的叶子节点为一双向链表,因此每个页中至少应该有两行记录,这就决定了innodb在存储一行数据的时候不能够超过8k,减去其它列值所占字节数,约等于N。


    我们知道对于InnoDB来说,内存是极为珍贵的,如果把768字节长度的blob都放在数据页,虽然可以节省部分IO,但相对来说能缓存行数就变少,也就是能缓存的索引值变少了,降低了索引效率。


2.2 dynamic

    Barracuda 的两种行格式对blob采用完全行溢出,即聚集索引记录(数据页)里面只保留20字节的指针,指向真实存放它的溢出段地址。


    dynamic 行格式,列存储是否放到off-page页,主要取决于行大小,它会把行中最长的那一列放到off-page,直到数据页能存放下两行。TEXT/BLOB列 <=40 bytes 时总是存放于数据页。这种方式可以避免compact那样把太多的大列值放到 B-tree Node,因为dynamic格式认为,只要大列值有部分数据放在off-page,那把整个值放入都放入off-page更有效。


    compressed 物理结构上与dynamic类似,但是对表的数据行使用zlib算法进行了压缩存储。在long blob列类型比较多的情况下用,可以降低off-page的使用,减少存储空间(一般40%左右),但要求更高的CPU,buffer pool里面可能会同时存储数据的压缩版和非压缩版,所以也多占用部分内存。这里 MySQL 5.6 Manual innodb-compression-internals 讲的十分清楚。

    

    压缩过程:

当使用压缩存储的页面,当Buffer Pool载入后,会将其解压。这时,该页面在Buffer Pool中同时存在“压缩版”和“解压版”。当Buffer Pool需要驱逐这些页的时候,有两种情况会发生:如果InnoDB认为当前应用是IO-Bound,相比CPU还有额外能力来做解压操作,则InnoDB选择仅驱逐页面的“解压版”;否则InnoDB会将页面的两个版本同时驱逐出去。


    另外,由于 ROW_FORMAT=DYNAMIC 和 ROW_FORMAT=COMPRESSED 是从 ROW_FORMAT=COMPACT 变化来的,所以他们处理 CHAR类型存储的方式和 COMPACT 一样。





> show variables like 'innodb_file_format';   # mysql5.6 默认row_format格式 是 Antelope

+--------------------+----------+

| Variable_name      | Value    |

|--------------------+----------|

| innodb_file_format | Antelope |

+--------------------+----------+


> show table status like 'students'\G

***************************[ 1. row ]***************************

Name            | students

Engine          | InnoDB

Version         | 10

Row_format      | Compact

Rows            | 26

Avg_row_length  | 630

Data_length     | 16384

Max_data_length | 0

Index_length    | 0

Data_free       | 0

Auto_increment  | 101

Create_time     | 2017-01-16 17:29:34

Update_time     | None

Check_time      | None

Collation       | utf8_general_ci

Checksum        | None

Create_options  |

Comment         |



> show variables like 'innodb_file_format';  # mysql5.7 默认row_format格式 是 Barracuda

+--------------------+-----------+

| Variable_name      | Value     |

+--------------------+-----------+

| innodb_file_format | Barracuda |

+--------------------+-----------+


> show table status like 'aa'\G

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

Name: aa

Engine: InnoDB

Version: 10

Row_format: Dynamic

Rows: 4

Avg_row_length: 4096

Data_length: 16384

Max_data_length: 0

Index_length: 0

Data_free: 0

Auto_increment: NULL

Create_time: 2017-01-14 01:37:55

Update_time: 2017-01-14 01:50:39

Check_time: NULL

Collation: utf8_general_ci

Checksum: NULL

Create_options:

Comment:



行格式对磁盘空间的占用情况

示例:

MYSQL5.7.20


create database test;

use test;


[test] 22:02:42 > show variables like '%innodb%format%';

+---------------------------+-----------+

| Variable_name             | Value     |

+---------------------------+-----------+

| innodb_default_row_format | dynamic   |

| innodb_file_format        | Barracuda |

| innodb_file_format_check  | ON        |

| innodb_file_format_max    | Barracuda |

+---------------------------+-----------+

4 rows in set (0.01 sec)



[test] 22:01:57 > show create table sbtest3 \G

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

Table: sbtest3

Create Table: CREATE TABLE `sbtest3` (

`id` int(10) unsigned NOT NULL AUTO_INCREMENT,

`k` int(10) unsigned NOT NULL DEFAULT '0',

`c` char(120) NOT NULL DEFAULT '',

`pad` char(60) NOT NULL DEFAULT '',

`dd` varchar(300) NOT NULL DEFAULT 'dd' COMMENT 'dd',

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=1891725 DEFAULT CHARSET=utf8

1 row in set (0.00 sec)



[test] 22:01:02 > select count(*)  from sbtest3;

+----------+

| count(*) |

+----------+

|  1815808 |

+----------+

1 row in set (52.57 sec)





[root@localhost /bdata/data/3306/data/test ]# l

total 409M

-rw-r-----. 1 mysql mysql   61 2018-07-03 21:52 db.opt

-rw-r-----. 1 root  root  8.5K 2018-07-31 21:44 sbtest3.frm

-rw-r-----. 1 root  root  408M 2018-07-31 21:57 sbtest3.ibd




alter table sbtest3  ROW_FORMAT=COMPRESSED ;


[test] 22:02:44 > show create table sbtest3 \G

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

Table: sbtest3

Create Table: CREATE TABLE `sbtest3` (

`id` int(10) unsigned NOT NULL AUTO_INCREMENT,

`k` int(10) unsigned NOT NULL DEFAULT '0',

`c` char(120) NOT NULL DEFAULT '',

`pad` char(60) NOT NULL DEFAULT '',

`dd` varchar(300) NOT NULL DEFAULT 'dd' COMMENT 'dd',

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=1891725 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED

1 row in set (0.00 sec)



[root@localhost /bdata/data/3306/data/test ]# l

total 229M

-rw-r-----. 1 mysql mysql   61 2018-07-03 21:52 db.opt

-rw-r-----. 1 root  root  8.5K 2018-07-31 21:58 sbtest3.frm

-rw-r-----. 1 root  root  228M 2018-07-31 22:00 sbtest3.ibd



算了下,压缩比率还是挺高的,改成compressed行格式后,大约少了 40% 的体积。

[test] 22:02:56 > select (408-228) / 408;

+-----------------+

| (408-228) / 408 |

+-----------------+

|          0.4412 |

+-----------------+