InnoDB分两部分,表结构定义和数据。
MySQL8.0之前表结构在.frm的文件中,8.0以后的版本允许放在系统表中,因为表结构定义占用空间其实很小。每个 InnoDB 表数据存储在一个以 .ibd为后缀的文件中。
数据表可以存在共享表空间,也可以存单独的文件,由innodb_file_per_table这个参数控制,参数的值有两种:
- OFF:表示表数据存放在共享表空间,和数据字典放在一起
- ON:表示表数据存储再.ibd后缀的文件中
MySQL5.6.6版本以后默认设置为ON。不过建议无论在哪里都设置为ON,因为:
- 单独存一个文件更容易管理
- 不需要这个表的时候可以通过drop table命令删除这个文件,而如果是共享表空间里面就算删掉表,空间依然不会回收。
设置为ON后,drop table可以删除表文件,但是业务中很多情况下我们只是删除数据的一部分行,用delete,这样就会出现我们要讨论的问题:数据删除了,但空间没有被回收。
delete删除部分记录,部分记录会被标记,可复用。
InnoDB上的数据都是按页存储的,如果我们直接删除整页数据会出现什么情况?
答案是:整页数据都会被标记,可复用。
delete命令只是把数据页或记录位置标记为可复用,表空间并没有被回收,该现象我们称之为”空洞“
ps:磁盘可能会产生了碎片,造成空间浪费
数据页的复用和部分纪录的复用是不同的。系统会把利用率较小的相邻两个页的数据合并到一个页里面,将另一个页标记为可复用。
但是如果部分纪录的复用需要按照索引
删除id=7这条记录,InnoDB引擎只是把id=7这条记录标记为删除,但是空间保留。如果后面有id位于(6,19)区间内的数据插入时,可以重复使用这个空间。
如果一直不被复用呢?那磁盘就产生了碎片,造成空间浪费。
事实上不但是delete纪录会产生碎片,insert纪录也会产生碎片,update纪录可能会产生碎片
insert数据
如果是按递增的顺序插入数据,索引的位置是紧凑的,但是如果是随机插入数据,情况就有些乱了,会造成页的分裂,少量的数据被分裂到多个页中,造成磁盘碎片
上图可以看到,假如page number=5的数据页已经满了,此时插入id=15的记录,需要申请一个新的页page number=6来保存数据。待页分裂完成后,page number=5的最后位置就会留下一个可复用的空洞。
相反,如果数据是按照索引递增顺序插入的,那么索引是紧凑的,不会出现数据页分裂。
update数据
如果修改的是非索引值,那么并不会影响B+树的结构
但是,如果修改的内容包含了索引,那么操作步骤是先删除一个旧的值,然后再插入一个新值。可能会造成空洞。
怎么消去碎片
新建一个影子表B与原表A的结构一致,然后按主键id由小到大,把数据从表A迁移到表B。由于表B是新表,并不会有空洞,数据页的利用率更高。
待表A的数据全部迁移完成后,再用表B替换表A。
alter table 表名 engine=InnoDB
但是,该方案有个致命缺点,表重构过程中,如果有新的数据写入表A时,不会被迁移,会造成数据丢失。
为了解决上面问题,MySQL 5.6 版本开始引入 online DDL,对流程做了优化。
执行步骤:
- 新建一个临时文件
- 扫描表A主键的所有数据页,生成B+ 树,存储到临时文件中
- 在生成临时文件过程中,如果有对表A做写操作,操作会记录到一个日志文件(row log)中
- 当临时文件生成后,再重放日志文件,将操作应用到临时文件
- 用临时文件替换表A的数据文件
- 删除旧的表A数据文件
与新建表的最大区别,增加了日志文件记录和重放功能。迁移过程中,允许对表A做增删改操作。
alter table t engine=innodb
https://blog.csdn.net/merryxuan/article/details/115342693
https://blog.csdn.net/itomge/article/details/117856819



