一、索引是什么?
索引是帮助 MySQL 高效获取数据的数据结构。
二、索引能干什么?
索引非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要。 索引能够轻易将查询性能提高好几个数量级,总的来说就是可以明显的提高查询效率。
三、索引的分类?
1、从存储结构上来划分:BTree 索引(B-Tree 或 B+Tree 索引),Hash 索引,full-index 全文索引,R-Tree 索引。这里所描述的是索引存储时保存的形式,
2、从应用层次来分:普通索引,唯一索引,联合索引
3、根据中数据的物理顺序与键值的逻辑(索引)顺序关系:聚集索引,非聚集索引。
平时讲的索引类型一般是指在应用层次的划分。
就像手机分类:安卓手机,IOS 手机 与 华为手机,苹果手机,OPPO 手机一样。
普通索引:即一个索引只包含单个列,一个表可以有多个单列索引,当使用不等于判断时,普通索引不会生效,只有主键和唯一索引会生效
唯一索引:索引列的值必须唯一,但允许有空值
联合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并
聚簇索引(聚集索引):并不是一种单独的索引类型,而是一种数据存储方式。具体细节取决于不同的实现,InnoDB 的聚簇索引其实就是在同一个结构中保存了 B-Tree 索引(技术上来说是 B+Tree)和数据行。
非聚簇索引:****不是聚簇索引,就是非聚簇索引
四、索引的底层实现
mysql 默认存储引擎 innodb 只显式支持 B-Tree( 从技术上来说是 B+Tree)索引,对于频繁访问的表,innodb 会透明建立自适应 hash 索引,即在 B 树索引基础上建立 hash 索引,可以显著提高查找效率,对于客户端是透明的,不可控制的,隐式的。
不谈存储引擎,只讨论实现(抽象)
Hash 索引
基于哈希表实现,只有精确匹配索引所有列的查询才有效,对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码(hash code),并且 Hash 索引将所有的哈希码存储在索引中,同时在索引表中保存指向每个数据行的指针。
缺点
Hash 表不适合做范围查询,它更适合做等值查询。
Hash 索引仅仅能满足 = , IN 和 <=>(表示 NULL 安全的等价) 查询,不能使用范围查询。
由于 Hash 索引比较的是进行 Hash 运算之后的 Hash 值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的 Hash 算法处理之后的 Hash 值的大小关系,并不能保证和 Hash 运算前完全一样。Hash 索引依然需要回表扫描。
Hash 索引是将索引键通过 Hash 运算之后,将 Hash 运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中,由于不同索引键可能存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash 索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。
B-Tree 索引(MySQL 使用 B+Tree)
B-Tree 能加快数据的访问速度,因为存储引擎不再需要进行全表扫描来获取数据,数据分布在各个节点之中。
B+Tree 索引
是 B-Tree 的改进版本,同时也是数据库索引索引所采用的存储结构。数据都在叶子节点上,并且增加了顺序访问指针,每个叶子节点都指向相邻的叶子节点的地址。相比 B-Tree 来说,进行范围查找时只需要查找两个节点,进行遍历即可。而 B-Tree 需要获取所有节点,相比之下 B+Tree 效率更高。
结合存储引擎来讨论(一般默认使用 B+Tree)
案例:假设有一张学生表,id 为主键
id | name | birthday |
---|---|---|
1 | Tom | 1996-01-01 |
2 | Jann | 1996-01-04 |
3 | Ray | 1996-01-08 |
4 | Michael | 1996-01-10 |
5 | Jack | 1996-01-13 |
6 | Steven | 1996-01-23 |
7 | Lily | 1996-01-25 |
在 MyISAM 引擎中的实现(二级索引也是这样实现的)
在 InnoDB 中的实现
聚簇索引
基于主键的索引结构,是一颗B+树,我们把这种索引叫做聚簇索引
特点:
- 聚簇索引中的叶子节点存放了用户记录的全部数据(每一列的数据,上面图片的name字段建立一个索引这种就是非聚簇索引,以id为主键包含值得这种就是聚簇索引),它就是innoDB中数据存放的格式,即数据即聚簇索引,聚簇索引即数据,这也是聚簇索引名字的由来吧,数据和索引聚集在一起。
- InnoDB要求表必须有主键。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐 含字段作为主键,这个字段长度为6个字节,类型为长整型,这样始终就会有一个聚簇索引。
联合索引
将多个字段组合成一个索引,那么这种二级索引就被称为联合索引 (结合下面的联合索引共同理解)
CREATE INDEX index_product_no_name ON product(product_no, name);
联合索引 ``(product_no, name)` 的 B+Tree 示意图如下:
可以看到,联合索引的非叶子节点用两个字段的值作为 B+Tree 的索引值。
联合索引的 B+Tree 是先按 product_no 进行排序,然后再 product_no 相同的情况再按 name 字段排序。记住这句话,很重要!
最左匹配原则
使用联合索引时,存在最左匹配原则,也就是按照最左优先的方式进行索引的匹配。
在使用联合索引进行查询的时候,如果不遵循「最左匹配原则」,联合索引会失效,这样就无法利用到索引快速查询的特性了。
比如,如果创建了一个 (a, b, c) 联合索引,如果查询条件是以下这几种,就可以利用联合索引:
- where a=1;
- where a=1 and b=2 and c=3;
- where a=1 and b=2;
需要注意的是,因为有查询优化器,所以 a 字段在 where 子句的顺序并不重要。但是,如果查询条件是以下这几种,因为不符合最左匹配原则,所以就无法匹配上联合索引,联合索引就会失效:
- where b=2;
- where c=3;
- where b=2 and c=3;
- where a=2 and c=3; a会走,c不会走
上面这些查询条件之所以会失效,是因为(a, b, c) 联合索引,是先按 a 排序,在 a 相同的情况再按 b 排序,在 b 相同的情况再按 c 排序。所以,b 和 c 是全局无序,局部相对有序的,这样在没有遵循最左匹配原则的情况下,是无法利用到索引的。
二级索引
二级索引和聚簇索引概念大致一致,只是提出来更详细的讲解,
聚簇索引只能用于主键字段的快速查询,如果想实现「非主键字段」的快速查询,我们就要针对「非主键字段」创建索引,这种索引称作为「二级索引」。二级索引同样基于 B+Tree 实现的,不过二级索引的叶子节点存放的是主键值,不是实际数据。
主键索引是InnoDB存储引擎默认给我们创建的一套索引结构,我们表里的数据也是直接放在主键索引里(所以innodb引擎时最好指定一个主键索引),作为叶子节点的数据页。
但我们在开发的过程中,往往会根据业务需要在不同的字段上建立索引,这些索引就是二级索引,今天我们就给大家讲讲二级所有的原理。
比如,你给name字段加了一个索引,你插入数据的时候,就会重新创建一棵B+树,B+树的叶子节点,也是数据页,但是这个数据页里仅仅放了主键字段和name字段。
叶子节点的数据页的name值,跟主键索引一样的,都是按照大小排序的。同一个数据页里的name字段值都是大于上一个数据页里的name字段值。
name字段的B+树也会构建多层索引页,这个索引页里放的是下一层的页号和最小name字段值。就像这样:
假设你要根据name字段来搜索数据,比如:select * from user where name=‘xxx'
,过程与主键索引一样的。从name索引的根节点开始找,一层一层的向下找,一直找到叶子节点,定位到name字段值对应的主键值。
但此时叶子节点的数据页没有完整所有字段,就需要根据主键到主键索引里去查找,从主键索引的根节点一路找到叶子节点,就可以找到这行数据的所有字段了,这个过程就叫回表 (在开发中就应该避免回表频率)。
二级索引,可以对多个字段建立联合索引,比如,name + age + sex
此时联合索引与单个字段的索引原理是一样的,只不过叶子节点的数据页里放的是id + name + age + sex
,然后默认按照name排序,name一样就按age排序,age一样就按sex排序。
每个name + age +sex
的索引页里,放的就是下层节点的页号和最小的name + age + sex
值。当你用name + age + sex
搜索的时候,就会走name + age + sex
联合索引这棵树,再回表查询。
覆盖索引
覆盖索引的概念也是基于二级索引 B+Tree
在上面的二级索引中,给name字段添加索引,其他几个字段不添加索引,进行查询
select * from product where name = '0002';
会先在二级索引的 B+Tree 中快速查找到 name 为 0002 的二级索引记录,然后获取主键值,然后利用主键值在主键索引的 B+Tree 中快速查询到对应的叶子节点,然后获取完整的记录。这个过程叫「回表」,也就是说要查两个 B+Tree 才能查到数据。
不过,当查询的数据是能在二级索引的 B+Tree 的叶子节点里查询到,这时就不用再查主键索引查,比如下面这条查询语句
select id from name where name = '0002';
这种在二级索引的 B+Tree 就能查询到结果的过程就叫作「覆盖索引」,也就是只需要查一个 B+Tree 就能找到数据
所以减少 回表也是优化的方式
四.二 执行计划
MySQL 的 EXPLAIN
是一个非常有用的工具,它可以分析和解释查询语句的执行计划,帮助开发者优化查询性能。通过执行 EXPLAIN
命令,可以获取查询执行计划的详细信息,包括以下字段:
id
: 查询的唯一标识符,用于标识每个查询的不同步骤或子查询。select_type
: 查询类型,表示查询的类型和方式,常见的取值包括:SIMPLE
: 简单查询,不包含子查询或联接。PRIMARY
: 外层查询。SUBQUERY
: 子查询。DERIVED
: 派生表的查询。UNION
: UNION 查询。UNION RESULT
: UNION 查询的结果。DEPENDENT UNION
: 依赖于外部查询的 UNION 查询。UNION RESULT
: UNION 查询的结果。DEPENDENT UNION
: 依赖于外部查询的 UNION 查询。UNION RESULT
: UNION 查询的结果。DEPENDENT UNION
: 依赖于外部查询的 UNION 查询。
table
: 表名,表示查询涉及的表名或表的别名。partitions
: 分区信息,如果查询涉及到分区表,则显示分区信息。type
: 访问类型,表示查询使用的访问方法和算法,常见的取值包括:ALL
: 全表扫描,需要扫描整个表。index
: 只访问索引,无需扫描表数据。range
: 使用索引范围进行查询。ref
: 使用非唯一索引或唯一索引前缀进行查询。eq_ref
: 使用唯一索引进行等值查询。const
: 常量查询,使用常量值进行查询。system
: 系统表查询。NULL
: 无效或未知的查询类型。
possible_keys
: 可能使用的索引,表示查询可能使用的索引列表。key
: 实际使用的索引,表示查询实际使用的索引。key_len
: 使用索引的长度,表示索引中使用的字节数。ref
: 连接条件,表示连接使用的列或常量。rows
: 估计的行数,表示查询扫描的行数估计值。filtered
: 过滤的行百分比,表示查询结果中实际返回的行数百分比。Extra
: 额外信息,表示查询的附加信息,可能包括排序、临时表、使用的文件等。
通过分析 EXPLAIN
的输出结果,可以了解查询的执行计划、访问方法和可能存在的性能问题。可以根据输出结果中的字段信息,优化查询语句、索引设计和数据库配置,以提高查询性能和效率。
五、索引在磁盘中保存的格式
myisam 存储引擎(非聚集索引):索引文件和数据文件分离。
data 目录下以.frm 、.MYD、.MYI
结尾的三个文件。
.frm:表的表结构。
.MYD:表的数据记录(表的数据行)。
.MYI:表的索引。
查找过程:根据 myi 文件中索引找到数据在 myd 文件中的位置(B+tree 的叶节点存储了数据的位置)。
innodb 存储引擎(聚集)(支持事务):
data 目录下以.frm 和.ibd(index、data)结尾的两个文件。
ibd 文件存储了索引和数据。该存储引擎要求存储数据文件时就是以 B+Tree 存储的。
表数据文件本身就是按 B+Tree 组织的一个索引结构文件。
与 myi 比少了一次磁盘 I/O。
问题: 为什么 innoDB 必须有主键?为什么推荐使用整型的自增主键。
答: 必须有一个 B+Tree 来组织数据文件。如果不指定主键,mysql 也会自动生成一个主键(使用唯一列或自动加一列)。
整型所占用的存储空间较小,而且整型数据容易做比较(检索)。
使用自增主键更便于插入,避免或减少了 B+Tree 的调整。
六、为什么索引结构默认使用 B+Tree,而不是 Hash,二叉树,红黑树?
B+tree:因为 B 树不管叶子节点还是非叶子节点,都会保存数据,这样导致在非叶子节点中能保存的指针数量变少(有些资料也称为扇出),指针少的情况下要保存大量数据,只能增加树的高度,导致 IO 操作变多,查询性能变低;
Hash:虽然可以快速定位,但是没有顺序,IO 复杂度高。
二叉树:树的高度不均匀,不能自平衡,查找效率跟数据有关(树的高度),并且 IO 代价高。
红黑树:树的高度随着数据量增加而增加,IO 代价高。
七、为什么官方建议使用自增长主键作为索引?
结合 B+Tree 的特点,自增主键是连续的,在插入过程中尽量减少页分裂,即使要进行页分裂,也只会分裂很少一部分。并且能减少数据的移动,每次插入都是插入到最后。总之就是减少分裂和移动的频率。
插入连续的数据:
插入非连续的数据
八、简单总结下
1、MySQL 使用 B+Tree 作为索引数据结构。 2、B+Tree 在新增数据时,会根据索引指定列的值对旧的 B+Tree 做调整。 4、从物理存储结构上说,B-Tree 和 B+Tree 都以页(4K)来划分节点的大小,但是由于 B+Tree 中中间节点不存储数据,因此 B+Tree 能够在同样大小的节点中,存储更多的 key,提高查找效率。 5、影响 MySQL 查找性能的主要还是磁盘 IO 次数,大部分是磁头移动到指定磁道的时间花费。 6、MyISAM 存储引擎下索引和数据存储是分离的,InnoDB 索引和数据存储在一起。 7、InnoDB 存储引擎下索引的实现,(辅助索引)全部是依赖于主索引建立的(辅助索引中叶子结点存储的并不是数据的地址,还是主索引的值,因此,所有依赖于辅助索引的都是先根据辅助索引查到主索引,再根据主索引查数据的地址)。 8、由于 InnoDB 索引的特性,因此如果主索引不是自增的(id 作主键),那么每次插入新的数据,都很可能对 B+Tree 的主索引进行重整,影响性能。因此,尽量以自增 id 作为 InnoDB 的主索引。
九、其他概念
覆盖索引:
如果索引包含所有满足查询需要的数据的索引成为覆盖索引(Covering Index),也就是平时所说的不需要回表操作。
简单的说,覆盖索引覆盖所有需要查询的字段(即,大于或等于所查询的字段)。MySQL 可以通过索引获取查询数据,因而不需要读取数据行。
覆盖索引的好处:
- 索引大小远小于数据行大小。因而,如果只读取索引,则能极大减少对数据访问量。
- 索引按顺序储存。对于 IO 密集型的范围查询会比随机从磁盘读取每一行数据的 IO 要少。
- 避免对主键索引的二次查询。二级索引的叶子节点包含了主键的值,如果二级索引包含所要查询的值,则能避免二次查询主键索引(聚簇索引,聚簇索引既存储了索引,也储存了值)。
十、在InnoDB执行了delete,为啥数据删了个寂寞?
一、删除并不是真正的删除
熟悉MySQL InnoDB
存储引擎的同学都应该知道,当我们执行delete的时候,数据并没有被真正的删除,只是对应数据的删除标识deleteMark
被打开了,这样每次执行查询的时候,如果发现数据存在但是deleteMark
是开启的话,那么依然返回空,因为这个细节,所以经常会出现“我明明删除了数据,为什么空间没释放”的现象。
15M 7 6 18:46 user_info.ibd #删除前
15M 10 4 16:47 user_info.ibd #删除后
二、为什么不直接删除,而是打个标记
我们知道InnoDB
存储引擎是支持MVCC
的,即多版本控制,得益于MVCC
,MySQL
在事务里查询数据的时候不需要加锁,可以提供很好的并发性,同时提供可重复读这个很重要的特性。那么它是怎么到的呢?答案是undo log
,可以简单的理解为,每次更新数据的时候将更新前的数据先写入undo log
中,这样当需要回滚的时候,只需要顺着undo log
找到历史数据即可。undo log
与原始数据之间是用指针链接起来的,即每条数据都有个回滚指针指向undo log
。
如果InnoDB在删除数据的时候,真的是把数据从磁盘上擦除,那么这时候:
别的事务通过undo log是无法找到原始数据。
可重复读这个特性会被破坏。
三、只是打个标记的话,岂不是很浪费空间
MySQL
里面有个purge
线程,它的工作中有一项任务就是专门检查这些有deleteMark
的数据,当有deleteMark
的数据如果没有被其他事务引用时,那么会被标记成可复用,因为叶子节点数据是有序的原因,这样当下次有同样位置的数据插入时,可以直接复用这块磁盘空间。当整个页都可以复用的时候,也不会把它还回去,会把可复用的页留下来,当下次需要新页时可以直接使用,从而减少频繁的页申请。
四、基于页的存储方式
我们知道MySQL
数据是存储在磁盘上的,磁盘的速度想必大家都知道,特别是当发生随机IO的时候。这里简单解释下什么叫IO,以机械磁盘为例,我们最终的数据都是落在磁盘的一个一个扇区上的,当一个扇区写满了,就得换下一个扇区,这时就要通过盘片的转动找到目标扇区,这是物理运动。如果要写入的下一个扇区和当前的扇区是紧挨着的,这叫顺序IO,如果要写入的扇区和当前的扇区中间隔了几个扇区,这叫随机IO,很明显随机IO需要更长的转动时间。所以查询一个数据的时候,减少IO是非常关键的,特别是随机IO。
为了减少磁盘IO,MySQL
采用B+树的索引结构来组织数据,B+树的特点是矮胖,一般树的高度就代表了IO的次数,越矮的话,树的高度越低,那么对应的IO次数就越少,还有一点需要知道的是数据最终都在叶子节点上,所以在B+树上搜索的时候,一定是要检索到最后一层叶子节点上,这是一种稳定性的表现。
1、行与页
这里需要知道的是,我们最终通过B+树检索到的不是我们的目标行数据,而是目标行数据所在的页,这个页上有很多数据,都是索引序号相邻的,当找到目标页后,会把目标页加载到内存中,然后通过二分法找到目标数据。
也许你会问,那搜索的开销不仅仅是磁盘IO,还有在二分法查找的开销。这里不可否认,但是我们一般忽略这部分开销,因为cpu在内存里检索的速度很快,并且一页也就16k,数据并不多。
2、IO次数不一定等于树的高度
前面我们说到树的高度等于IO的次数,这其实不是很准确,我们知道树的根节点一定是在内存里的,那么对于一颗高度为3的数据,只用2次IO即可,这其实可以理解,毕竟根节点只占用一页的空间,一页才16K,放在内存里绰绰有余。但有时候树的第二层也可以放在内存里。
假设现在主键是bigint,bigint
我们知道占用8个字节,对于一个索引来说除了类型本身占用空间之外,还有一个指针,这个指针占用6个字节,那么对于根节点来说它大概能存 16K/(8+6)B = 1170 个数据,每个数据都可以指向一页(也就是它的下一层),这样整个树的第二层大概占用 1170*16K = 18M 的空间,这也不是一个很大的数字,对于机器的内存来说,几乎也是沧海一粟,所以第二层往往也在内存里,所以最终在B+树上检索数据所消耗的IO应该比理论的要低。
通过上面我们知道检索一条数据的快慢,主要受树的高度影响的,这和你的数据表的大小并没有太大的关系,现实中有人可能在数据表达到百万级别的时就考虑分表,个人认为这有点低估B+树的能力了。还是以bigint
类型的主键索引为例,假设一行数据占用1K(理论上已经足够大了),那么一页可以存下 16K/1K=16 条数据,对于一颗高度为3的B+树来说,它可以存下 1170117016=21902400 的数据,将近2千万,如果你的数据行占用的空间更小,就可以存下更多的数据,所以只是简单的根据数据行数来判断是否需要分表不是那么的合理。
五、可复用的空间一直没有被利用咋办
前面我们说到删除的数据不会被真的删除,只是打上个deleteMark
的标识,然后会被复用,但是如果一直没被复用,那么空间不就是白白的浪费了,更糟糕的是,如果删除的很多数据空间都没有被复用,就会造成页空间存在大量的碎片,为了解决这种情况,MySQL
内部有个叫页合并的功能,这是什么意思呢?简单理解就是页A现在有很多可以被复用的空间,它的邻居页B也有很多可以复用的空间,此时页A就可以和页B合并,如果合并后能省出来一页,那么多出来的一页就可以被下次使用,从而达到页最大利用的效果。
合并的关键需要当前页的前一页或者后一页也有大量的碎片空间,这里为何要「大量」很关键,合并的动作可以简单理解就是把别的页的数据移动过来,如果两个页pageA
和pageB
都只有少量的可复用空间,那么合并后,即使pageA
可以填满,但是另一个页Page也还是有碎片空间的,并且碎片更大,这时候数据移动的开销可能要大于存储的开销,得不偿失。
而且还会有个严重的问题,pageB
可能会和pageC
合并,那么pageC
的碎片更大…,这样的话似乎是个无底洞,导致很多页都在移动数据。因此一个合理的合并条件很关键,InnoDB
中何时合并受MERGE_THRESHOLD这个参数影响,它的默认值是50%,50%的意图很明显,两个50%就可以省出一个页。
我们看个例子,pageA
已经有50%的数据被删除了,它的邻居pageB
只使用了不到50%的数据,这时候会将pageB
的数据移动到pageA
上,那么整个pageB
就是空页了,可以提供给别的数据使用。这里需要知道的是除了删除会触发页合并外,更新可能也会触发页合并。
六、有合并也有分裂
合并页是提升页的利用率的方式,但是有时候我们又不得不分裂页,我们知道叶子节点的页之间是用双向链表串接起来的,并且页与页之间的数据是有序的。
以上图为例,当我们要插入5这条数据,按道理应该尝试放在pageA
里面,但是pageA
目前没有足够的空间来存放一条数据,于是尝试找到pageA
的相邻页pageB
,但是此时很不幸的是pageB
也没有足够的空间来存放一条数据,由于要求数据的连续性,数据5必须在数据4和数据6之间,那么只能新建一个页,新建一个页后,会尝试从pageA
中移动一部分数据到新的页上,并且会重新组织页与页之间的关系,即在pageA
和pageB
之间会隔一道新页pageC
。
页分裂会造成页的利用率降低,造成页分裂的原因有很多,比如:
比如离散的插入,导致数据不连续。
把记录更新成一个更大记录,导致空间不够用。
还有一点需要知道的是:不管是页的合并还是页的分裂,都是相对耗时的操作,除了移动数据的开销外,InnoDB
也会在索引树上加锁。
七、手动重建表
页的合并和分裂主要是在插入、删除或更新的时候,并且正好满足某些条件才发生的,那如果这些条件一直不满足,碎片就无法得到清理,这时候往往会出现”我的表明明没多少数据,为什么还占用这么大空间“这个现象,针对这个现象有人说重建索引,这个是对的,重建索引可以让数据更加紧凑,页的利用率达到更高。但是如何重建索引?第一时间你可能会想到先drop index然后add index,这个似乎不是那么准确。
如果要重建的索引是普通索引,使用这种方式还好,需要注意的是假如你的业务TPS
很大,建议在业务低峰期执行,因为虽然MySQL
支持online ddl
,但是重建索引的过程还是很耗cpu
和io
资源的。
如果你要重建的是主键索引,那么问题来了,首先如果你的主键索引设置的是自增长,是不支持drop的。其次如果你的主键没设置成自增长,直接drop也不是我们想象的那样,我们知道普通索引除了记录本身的索引字段外,还会记录主键的值,如果drop是直接删除索引,那么通过普通引将找不到对应的行记录,所以InnoDB
是要求必须有主键索引的,这时InnoDB
会尝试去表中找个唯一索引来当主键,如果没有唯一索引,那就自动创建一个默认的主键索引rowid
,当新的主键索引建立好之后,还要去修改相关的普通索引让其存储新的主键,但是如果按照这种方法来修改的话,开销会很大,特别是普通索引很多的情况下,于是InnoDB
干脆选择重建表。对于紧接着执行的add index操作,同样也会发生主键索引的变更,所以也会选择重建表,最终可以发现在主键索引上的drop和add其实干了一样的事情。
综上所述,一般在你的表出现很多页碎片的时候,建议使用:
alter table xx engine=InnoDB
这个命令可以重建我们这个表,但是前提是我们的表是独占表空间的。基于MySQL
的online ddl
,这个过程它是不影响正常的读写的,它的过程如下:
扫描原表主键索引的所有记录
生成新的b+树记录到临时文件
生成临时文件的过程中,新的变更记录到一个中转日志row log中
在临时文件生成后,将期间row log的变更应用到新的临时文件中
然后替换临时文件为当前文件
这里需要注意的是重建表的过程涉及到数据的copy,得保证磁盘有足够的空间,至少是现在磁盘空间的1倍,如果磁盘空间不足,那么是不会重建成功的。
八、重建表不一定会收缩空间
在重建表的过程中,有一点需要知道:InnoDB
不会让重建后的页充满数据,会预留个「1/16」的空间,这个意图很明显,如果不预留,选择占满整个页,这时候去更新一条需要更大空间的老数据,就会需要新的页,写入新的页后,往往又会造成碎片,所以提前预留一点空间是有用的。
但是因为这个预留操作,某些情况下会导致重建后的表空间反而会变大。
如果你的表本身就很紧凑,因为预留1/16会变大。
在第一次重建表后,因为新的插入导致用掉了预留空间的一部分(这里需要注意的是预留空间没用完,还剩一部分),但是没有用到新的页,所以整体的空间没有变化,这时候如果再次重建表,就会因为要预留1/16,导致申请的新的页,那么空间就会变大。