一路走来,所有遇到的人,帮助过我的、伤害过我的都是朋友,没有一个是敌人。如有侵权,请留言,我及时删除!
一、MySQL数据存储结构解析
1、mysql数据存储结构的组成
从 InnoDB 逻辑存储结构来看,所有的数据都被逻辑的存放在一个空间中,这个空间就叫做表空间(tablespace)。表空间由 段(segment)、区(extent)、页(page)组成。
2、mysql对应的数据文件
当我们创建一个表之后,在磁盘上会有对应的表名称.ibd
的磁盘文件。表空间的磁盘文件里面有很多的数据页,一个数据页最多16kb,因为不可能一个数据页一个磁盘文件,所以数据区的概念引入了
一个数据区对应64个数据页,就是16kb,一个数据区是1mb,256个数据区被划分为一组,对于表空间而言,他的第一组数据区的第一个数据区的前3个数据页,都是固定的,里面存放了一些描述性的数据。比 如FSP_HDR这个数据页,他里面就存放了表空间和这一组数据区的一些属性。IBUF_BITMAP 数据页,存放的就是insert buffer的信息,INODE 数据页存放的也是特殊信息。
再次强调一遍:我们平时创建的那些表都是有对 应的表空间的,每个表空间就是对应了磁盘上的数据文件,在表空间里有很多组数据区,一组数据区是256个数据区, 每个数据区包含了64个数据页,是1mb
3、mysql数据存储结构的大小
4、表空间(tablespace)详解
InnoDB存储引擎将InnoDB表保存在一个表空间内,该表空间可由数个文件创建。这样,表的大小就能超过单独文件的最大容量。表空间可包括原始磁盘分区,从而使得很大的表成为可能。表空间的最大容量为64TB。
表空间可以看做是InnoDB存储引擎逻辑结构的最高层,所有的数据都存放在表空间中
。
表空间是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段。
表空间从管理上可以分为系统表空间、独立表空间、撤销表空间和临时表空间等。
5、数据段(segment)详解
段(segment)
段(Segment)分为索引段,数据段,回滚段等。其中索引段就是非叶子结点部分,而数据段就是叶子结点部分,回滚段用于数据的回滚和多版本控制。一个段包含256个区(256M大小)。
一个段包含多少区:256个区
对于范围查询,其实是对B+树叶子节点中的记录进行顺序扫描,而如果不区分叶子节点和非叶子节点,统统把节点代表的页面放到申请到的区中的话,进行范围扫描的效果就大打折扣了。
所以InnoDB对B+树的叶子节点和非叶子节点进行了区别对待,叶子节点有自己独有的区,非叶子节点也有自己独有的区。存放叶子节点的区的集合和存放非叶子节点的区的集合各自是一个段。
数据段即为B+树的叶子节点,索引段即为B+树的非叶子节点。段不对应表空间中某一个连续的物理区域,而是一个逻辑上的概念,由若干个零散的页面以及一些完整的区组成。
6、区(extent)详解
区是页的集合,一个区包含64个连续的页,默认大小为 1MB (64*16K)。
B+树的每一层中的页都会形成一个双向链表,如果是以页为单位来分配存储空间的话,双向链表相邻的两个页之间的物理位置可能离得非常远。如果链表中相邻的两个页物理位置离得非常远,就是所谓的随机I/0,非常慢。我们应该尽量让链表中相邻的页的物理位置也相邻,也就是所谓的顺序I/0,提高速度。一个区就是在物理位置上连续的64个页,在表中数据量大的时候,为某个索引分配空间的时候就不再按照页为单位分配了,而是按照区为单位分配,虽然这样会浪费一点空间,但可以消除很多的随机/O。
碎片(fragment)区的概念
默认情况下,一个使用InnoDB存储引擎的表只有一个聚簇索引,一个索引会生成2个段,而段是以区为单位申请存储空间的,一个区默认占用1M,所以默认情况下一个只存了几条记录的小表也分配2M的存储空间,以后每次添加一个索引都要多申请2M的存储空间,这是极大的浪费。
为了考虑以完整的区为单位分配给某个段对于数据量较小的表太浪费存储空间的这种情况,InnoDB提出了一个碎片(fragment)区的概念。在一个碎片区中,并不是所有的页都是为了存储同一个段的数据而存在的,而是碎片区中的页可以用于不同的目的,比如有些页用于段A,有些页用于段B,有些页甚至哪个段都不属于。碎片区直属于表空间,并不属于任何一个段。
所以此后为某个段分配存储空间的策略是:
在刚开始向表中插入数据的时候,段是从某个碎片区以单个页面为单位来分配存储空间的。
当某个段已经占用了32个碎片区页面之后,就会申请以完整的区为单位来分配存储空间。
7、页(page)详解
页是 InnoDB 管理的最小单位,常见的有 FSP_HDR,INODE, INDEX 等类型。所有页的结构都是一样的,分为文件头(前38字节),页数据和文件尾(后8字节)。页数据根据页的类型不同而不一样。
每个空间都分为多个页,通常每页16 KiB。空间中的每个页面都分配有一个32位整数页码,通常称为“偏移量”(offset),它实际上只是页面与空间开头的偏移量(对于多文件空间,不一定是文件的偏移量)。因此,页面0位于文件偏移量0,页面1位于文件偏移量16384,依此类推。 (InnoDB 的数据限制为64TiB,这实际上是每个空间的限制,这主要是由于页码是32位整数与默认页大小的组合
数据页的内部结构
数据页的16KB大小的存储空间被划分为七个部分,分别是 文件头(File Header)、页头(Page Header)、最大最小记录(Infimum+supremum)、用户记录(User Records)、空闲空间(Free Space)、页目录(Page Directory)和文件尾(File Tailer)。
数据页加载的三种方式
InnoDB从磁盘中读取数据的 最小单位 是数据页。而你想得到的id =xxx的数据,就是这个数据页众多行中的一行对于MySQL存放的数据,逻辑概念上我们称之为表,在磁盘等物理层面而言是按 数据页形式进行存放的,当其加载到MySQL中我们称之为 缓存页。
内存读取
如果该数据存在于内存中,基本上执行时间在1ms左右,效率还是很高的。
随机读取
如果数据没有在内存中,就需要在磁盘上对该页进行查找,整体时间预在 10ms 左右,这1ms 中有6ms 是磁盘的实际繁忙时间 (包括了寻道和半圈旋转时间),有 3ms 是对可能发生的排队时间的估计值,另外还有 1ms的传输时间,将页从磁盘服务器缓冲区传输到数据库缓冲区中。这 10ms 看起来很快,但实际上对于数据库来说消耗的时间已经非常长了,因为这还只是一个页的读取时间。
顺序读取
顺序读取其实是一种批量读取的方式,因为我们请求的 数据在磁盘上往往都是相邻存储的,顺序读取可以帮我们批量读取页面,这样的话,一次性加载到缓冲池中就不需要再对其他页面单独进行磁盘IO 操作了。如果一个磁盘的吞吐量是 40MB/S,那么对于一个16KB 大小的页来说,一次可以顺序读取 2560 (40MB/16KB)个页,相当于个页的读取时间为 0.4ms。探用批量读取的方式,即使是从磁盘上进行读取,效率也比从内存中只单独读取一个页的效率要高。
8、大佬博客推荐
MySQL高级第四篇:InnoDB存储结构之页、区、段和表空间_数据库 区 断 页-CSDN博客
Mysql基础(八):表空间、段、区、页的关系_mysql 区 段 页-CSDN博客
Mysql ——区、段、表空间 、碎片区_mysql 段和区-CSDN博客
https://www.cnblogs.com/wuyifu/p/8026243.html
mysql本地 表空间_mysql 学习 - InnoDB的表空间-CSDN博客
MySQL(InnoDB剖析):15---table之(表空间:段(segment)、区(extent)、页(page))_mysql索引存储结构和页、区、段的关系-CSDN博客
二、InnoDB行格式
1)、数据存储形式
首先明确在 innodb 引擎中数据是以页为基本单位读取的,而一个页中又包含多个行数据,那么对应地就会有不同的行格式来存储数据,innodb 中的行格式有四种:compact、redundant、dynamic、compressed。redundant 是 5.0 之前用的行格式,MySQL8.0 5.7默认行格式为 Dynamic,数据以行为单位来向表中插入数据,这些记录在磁盘上的存放方式被称为行格式或者记录格式
。
2)、compact 行格式解析
可以看到 compact 行格式中将一行分成了两个部分,一个是真实数据的存储,一个是一些记录的信息。接下来一个一个看。
① 变长字段长度列表在 MySQL 中有 char 和 varchar 两种字符串类型,他们的区别是 varchar 是变长的类型,对于一列二进制流,我们通过变长字段长度列表就可以得到真实长度。在这里会逆序地存储变长字段的真实长度,真实长度用 1 个/2 个字节来存储,同时将长度的第一个二进制位作为标志符,如果为 0 则说明长度是 1 个字节,如果为 1 则说明长度是 2 个字节,这也就可以解释为什么 varchar 只能存储最大 65535 个字节,也解释了为什么真实长度在 127 以下就可以用一个字节存储。这里不是说 char 类型就一定不会用到该列表,对于定长的字符集来说,char 只要定义之后相应的内存空间分配就固定下来了,也就无需去记录长度,但对于变长的字符集(utf8、utf8mb4)来说,即使是 char 类型的数据也需要记录在该列表中。拿 utf8 来讲,其允许1~3个字节的数据存储,那么对于char(10)来说就允许存储10~30个字节的数据,那么具体多少个字节还需要变长字段长度列表去记录,同时与 varchar 不同的是,char(10)要求至少占 10 个字节,即使我们插入 1 个字节的数据,也会使用空格补全到 10 个字节,避免更新时产生碎片的问题。当然这一块并不是必须存在的,如果表中没有变长字段则不会有这个列表。
② NULL 值列表
对于允许存在 NULL 值的列如果我们把 NULL 值也存储在真实数据中那么会占据较多的空间,其实对于 NULL 值的存储只有是或不是两种可能,因此可以用一个二进制位来表示一个列是否为 NULL,那么对应到行格式中就是 NULL 值列表。
和变长字段长度列表一样,这里的存储也是逆序的,如果一个列是 NULL 则该位为 1,否则为 0,如果出现不足整数个字节的二进制数还需要在高位补 0,例如存储 3 个 NULL 值则需要在头部补 5 个 0。当然这里需要注意,NULL 值列表只存储那些可以为 NULL 的列,如果表中没有允许 NULL 值的列则该列表也不会存在。
③ 记录头信息
记录头信息固定为 5 个字节 40 个二进制位组成,主要有:
delete_mark:行删除标记,在 innodb 中对于行数据的删除并不会马上去刷盘,而是先打上一个标记,待后续刷盘时机到了再把脏页刷入。 min_rec_mark:最小记录标记,用来标记非叶子节点的最小记录。 record_type:记录类型,0 代表普通记录,1 代表非叶子结点记录,2 代表最小记录,3 代表最大记录。 n_owned:因为一个行可能是非叶子结点,所以用这个字段来代表其下的子节点数目。 next_record:指向下一条记录的指针,这里可以对应到 B+树的结构特点。
④ 真实数据
在 innodb 中对于数据的存储不只是我们定义的那些列,还包括一些引擎自动生成的隐藏列,其中包括 db_row_id,db_trx_id、db_roll_ptr。
如果我们定义的表中既没有主键也没有唯一字段,那么 innodb 会自动帮我们创建一个 db_row_id 充当主键,因为 innodb 是索引组织表,必须要有主键索引,该字段占 6 个字节,如果我们自己定义了主键或者唯一键则可以节省该空间占用。
db_trx_id 是用来标识当前事务的 id 的,db_roll_ptr 是一个指向回滚事务链的指针,这两个字段搭配应用在事务与 MVCC 中,db_trx_id 占 6 个字节,db_roll_ptr 占 7 个字节。
3)dynamic 与 compressed 行格式
dynamic 与 compact 基本相同,只不过对于大长度字符串的处理略有不同。compact 会记录前 768 个字节,其余字节存储到其他页,之后用一个指针指向它,而 dynamic 则会将全部数据都存储到其他页,之后用一个指针去指向它。 compressed 于 dynamic 的差别就是,compressed 采用了压缩算法,让行数据更加节省内存。
4)对于大字符串溢出的处理
MySQL 限制一个行中除了 text、blob 之外的其他所有列合起来最大只能存储 65535 个字节,如果超过该值会报错,只能使用 blob 或者 text 类型来存储。那么对于 varchar 类型的长字符串来说,除了真实数据之外还需要有 2 个字节来存储字段长度,1 个字节来存储是否为 NULL(如果列不允许为 NULL 则不需要),那么实际上 varchar 最大只能存储 65533 个字节(在一个表中只有一个列的情况下),那么允许存储的最大字符数目就除以字符集单个字符的最大字节数即可。 前面也讲到了,innodb 中数据存储的基本单位是页,一个页只有 16KB(16384 字节),那么如果字符串的数据量大于这个值怎么办呢?溢出到其他页处理。 在 compact 和 redundant 中,如果行的数据超过了 16384 字节,那么在本行中只会存储其中的前 768 个字节,将其他数据放到其他的页中(溢出页),再用 20 字节的指针指向其他页。在 dynamic 和 compressed 中只会存储这个 20 字节的指针,数据都放到溢出页去。 那么存储多大的数据才会让行溢出呢?innodb 中规定了一个页最少要存储两条记录,除了存储行数据之外,每个页还要有 136 个字节来存储记录信息,同时每个行需要有 27 个字节来存真实数据以外的信息,那么最终每行的真实数据大小的最大值就是 8097 字节 ( (16384-136) / 2 - 27 ),也就是说如果列数据大于 8097 字节将会导致行溢出。
三、行溢出的原理及处理
一、从常见的报错说起
故事的开头我们先来看一个常见的sql报错信息:
相信对于这类报错大家一定遇到过很多次,“数据大”也是生产过程中绕不开的一个话题。这里的数据“大”,远不止存储空间占用多,其中也包括了单个(表)字段存储多、大,数据留存时间长,数据冗余多,冷热数据不明显导致的体量大,访问峰值随着热点变化明显,逻辑处理复杂导致数据存储压力放大等等。回到这个报错的问题上来,我们先来看一下这个表的结构:
看到这里,我相信大家会有不同的处理方式了,这里就不对各种处理方式的优劣做比较了,仅仅叙述使用频率较高的两种处理方式。
• 根据报错的指引,把两个大的varchar(22288)改成text、blob
• 根据业务特点,缩小varchar的存储长度,或者按照规则拆分成多个小的vachar和char
这两种的处理方式也各有优缺点,把字段改成text或者blob,不仅增大了数据存储的容量,对这个字段的索引页只能采用前缀或者全文索引了,如果业务侧存储的是json格式的数据,5.7支持json数据类型是个不错的选择,可以针对单个子类进行查询和输出。同样如果缩小和拆分的话就比较依赖业务的场景和逻辑需求了,业务使用的逻辑上需要修改,工程量也需要评估。
二、深入探索
接着我们再来深入分析下关于限制大小“65535”的一些容易混淆的概念。
1. “65535”不是单个varchar(N)中N的最大限制,而是整个表非大字段类型的字段的bytes总合。
-----------------------------------------------------
Every table (regardless of storage engine) has a maximum row size of 65,535 bytes. Storage engines may place additional constraints on this limit, reducing the effective maximum row size.
------------------------------------------------------------------------------------------------
2. 不同的字符集对字段可存储的max会有影响,例如,UTF8字符需要3个字节存储,对于VARCHAR(255)CHARACTER SET UTF8列,会占用255×3 =765的字节。故该表不能包含超过65,535/765=85这样的列。GBK是双字节的以此类推。
3. 可变长度列在评估字段大小时还要考虑存储列实际长度的字节数。例如,VARCHAR(255)CHARACTER SET UTF8列需要额外的两个字节来存储值长度信息,所以该列需要多达767个字节存储,其实最大可以存储65533字节,剩余两个字节存储长度信息。
4. BLOB、TEXT、JSON列不同于varchar、char等字段,列长度信息独立于行长存储,可以达到65535字节真实存储。
5. 定义NULL列会降低允许的最大列数。
• InnoDB表,NULL和NOT NULL列存储大小是一样
• MyISAM表,NULL列需要额外的空间记录其值是否为NULL。每个NULL需要一个额外的位(四舍五入到最接近的字节)。最大行长度计算如下:
row length = 1 + (sum of column lengths) + (number of NULL columns + delete_flag + 7)/8 + (number of variable-length columns)
• 静态表,delete_flag = 1,静态表通过在该行记录一个位来标识该行是否已被删除。
• 动态表,delete_flag = 0,该标记存储在动态行首
6. 对于InnoDB表,NULL和NOT NULL列存储大小是一样
7. InnoDB允许单表最多1000个列
8. varchar主键只支持不超过767个字节或者768/2=384个双字节 或者767/3=255个三字节的字段 而GBK是双字节的,UTF8是三字节的
9. 不用的引擎对索引的限制有区别
• innodb每个列的长度不能大于767 bytes;所有组成索引列的长度和不能大于3072 bytes
• myisam 每个列的长度不能大于1000 bytes,所有组成索引列的长度和不能大于1000 bytes
三、真正的故障
下面来说下今天遇到的业务故障,线上业务出现了大量的如下报错,导致程序无法写入数据:
按照提示和正常的思路,我们先第一反应认为业务存在如下的问题:
1. 设置的表结构中字段超过了限制
2. 某个字段插入的数据长度超过了改字段设置的max值
接着查看了业务的库表结构,如下:
很快排除了第一个原因,因为首先业务的报错不是在建立表的时候出现的,如果是表中非大字段之和65535,在建表的时候就会出错,而业务是在写入的时候才报错的,而且通过库表结构也能发现大量的都是mediumblob类型字段,非大字段加起来远小于65535。
接着根据业务提供的具体SQL,appversion、datadata、elt_stamp、id这几个非大字段,也并没有超过限制,mediumblob类型字段最大可存储16M,业务的数据远远没有达到这个量级。按照报错的提示把 appversion、datadata、elt_stamp、id这几个非大字段均改成blob类型,还是无法解决(根据之前的分析,必然不是问题的根源)。
冷静下来后,发现其实还有个细节被忽略掉了,业务的失败率不是100%,说明还是有成功的请求,通过对比成功和失败的sql,发现果然数据量差异的还是mediumblob类型字段。那么现在第一个想到的就是,max_allowed_packet这个参数,是不是调小了,是的单个请求超过大小被拒绝了,查了下配置的值(如下图),配置的大小1G,sql的数据长度远没有这么大,这个原因也排除了。
查到这里基本上排除了常见几个问题,接着再看一下另一个参数的限制:innodb_page_size,这个的默认值是16K,每个page两行数据,所以每行最大8k数据。
查看了下数据表Row_format是Compact,那么我们可以推断问题的原因应该就是innodb默认的approach存储格式会把每个blob字段的前864个字节存储在page里,所以blob超过一定数量的话,单行大小就会超过8k,所以就报错了。通过对比业务写成功和失败的SQL也应征了这个推论,那么现在要怎么解决这个问题?
1. 业务拆分表,大字段进行分表存储
2. 通过解决Row_format的存储方式解决问题
由于业务单表的存储条数并不大,而且业务逻辑不适合拆分,所以我们要在Row_format上来解决这个问题。
Barracuda文件格式下拥有两种新的行记录格式Compressed和Dynamic两种,新的两种格式对于存放BLOB的数据采用了完全的行溢出的方式,在数据页中只存放20个字节的指针,实际的数据都存放在BLOB Page中。Compressed行记录格式的另一个功能就是存储在其中的数据会以zlib的算法进行压缩。
相关的变更操作就相对简单了:
- 修改MySQL全局变量: SET GLOBAL innodb_file_format='Barracuda';
- 平滑变更原表的属性: ROW_FORMAT=COMPRESSED
四、继续学习
通过这个案例我们可以从中提炼出两个值得深入研究一下的点:
1. 关于innodb_page_size
从MySQL5.6开始,innodb_page_size可以设置Innodb数据页为8K,4K,默认为16K。这个参数在一开始初始化时就要加入my.cnf里,如果已经创建了表,再修改,启动MySQL会报错。
那么在5.6的版本之前要修改这个值,怎么办?那只能是在源码上做点文章了,然后重新rebuild一下MySQL。
UNIV_PAGE_SIZE是数据页大小,默认的是16K,该值是可以设置必须为2的次方。对于该值可以设置成4k、8k、16k、32K、64K。同时更改了UNIV_PAGE_SIZE后需要更改UNIV_PAGE_SIZE_SHIFT 该值是2的多少次方为UNIV_PAGE_SIZE,所以设置数据页分别情况如下:
接着再来说一下innodb_page_size设置成不同值的对于mysql性能上的影响,测试的表含有1亿条记录,文件大小30G。
①读写场景(50%读50%写)
16K,对CPU压力较小,平均在20%
8K,CPU压力为30%~40%,但select吞吐量要高于16K
②读场景(100%读)
16K和8K差别不明显
InnoDB Buffer Pool管理页面本身也有代价,Page数越多,那么相同大小下,管理链表就越长。因此当我们的数据行本身就比较长(大块插入),更大的页面更有利于提升速度,因为一个页面可以放入更多的行,每个IO写的大小更大,可以更少的IOPS写更多的数据。当行长超过8K的时候,如果是16K的页面,就会强制转换一些字符串类型为TEXT,把字符串主体转移到扩展页中,会导致读取列需要多一个IO,更大的页面也就支持了更大的行长,64K页面可以支持近似32K的行长而不用使用扩展页。但是如果是短小行长的随机读取和写入,则不适合使用这么大的页面,这会导致IO效率下降,大IO只能读取到小部分。
2. 关于Row_format
Innodb存储引擎保存记录,是以行的形式存放的。在InnoDB 1.0.x版本之前,InnoDB 存储引擎提供了 Compact 和 Redundant 两种格式来存放行记录数据。MySQL 5.1 中的innodb_plugin 引入了新的文件格式:Barracuda,该文件格式拥有新的两种行格式:compressed和dynamic。并且把 compact 和 redundant 合称为Antelope。可以通过命令SHOW TABLE STATUS LIKE 'table_name';来查看当前表使用的行格式,其中 row_format 列表示当前所使用的行记录结构类型。
MySQL 5.6 版本中,默认 Compact ,msyql 5.7.9 及以后版本,默认行格式由innodb_default_row_format变量决定,默认值是DYNAMIC,也可以在 create table 的时候指定ROW_FORMAT=DYNAMIC(通过这个可动态调整表的存储格式)。如果要修改现有表的行模式为compressed或dynamic,必须先将文件格式设置成Barracuda(set global innodb_file_format=Barracuda;)。再用ALTER TABLE tablename ROW_FORMAT=COMPRESSED;去修改才能生效,否则修改无效却无提示。
①compact
如果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。
②compressed或dynamic
对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的使用,减少存储空间50%左右,但要求更高的CPU,buffer pool里面可能会同时存储数据的压缩版和非压缩版,所以也多占用部分内存。
五、DBbrain如何轻松处理
数据库智能管家DBbrain对于用户在数据库中使用BLOB这类变长大字段类型也会根据具体场景给出如下优先建议,帮助用户更好的规避一些由于变长大字段带来的业务问题。
1. 大字段在InnoDB里可能浪费大量空间。例如,若存储字段值只是比行的要求多了一个字节,也会使用整个页面来存储剩下的字节,浪费了页面的大部分空间。同样的,如果有一个值只是稍微超过了32个页的大小,实际上就需要使用96个页面。
2. 太长的值可能使得在查询中作为WHERE条件不能使用索引,因而执行很慢。在应用WHERE条件之前,MySQL需要把所有的列读出来,所以可能导致MySQL要求InnoDB读取很多扩展存储,然后检查WHERE条件,丢弃所有不需要的数据。
3. 一张表里有很多大字段,建议组合起来单独存到一个列里面。让所有的大字段共享一个扩展存储空间,比每个字段用自己的页要好。
4. 把大字段用COMPRESS()压缩后再存为BLOB,或者在发送到MySQL前在应用程序中进行压缩,可以获得显著的空间优势和性能收益。
5. 扩展存储禁用了自适应哈希,因为需要完整的比较列的整个长度,才能发现是不是正确的数据。
大佬博文推荐
MySQL专题(四):行溢出是什么东西?表空间以及划分多个数据页的数据区的概念 数据库服务器使用RAID存储架构 深入理解redo log redo log block redo log buffer_mysql 行溢出-CSDN博客