InnoDB表都包含哪些部分
一个InnoDB表包含两部分,表结构定义和数据。
在MySQL8.0之前,表结构存在以.frm为后缀的文件里,MySQL8.0版本,已经允许把表结构定义存放在系统数据表中了(因为表结构定义占用的空间很小)
简单的删除表数据,表占用空间不会减小
表数据既可以存在共享表空间里,也可以是单独的文件,这个行为是由参数innodb_file_per_table
控制的:
- 设置为OFF:表的数据放在系统共享表空间,也就是根数据字典放在一起
- 设置为ON:每个InnoDB表数据存储在一个
.ibd
为后缀的文件中
从5.6.6版本开始,默认值就为ON了。
建议设置为ON,因为一个表单独存储为一个文件更容易管理,而且不需要这个表的时候,通过drop table命令,系统就会直接删除这个文件,而如果是放在共享表空间中,即使表删除了,空间也是不会回收的。
数据的删除流程
我们知道InnoDB是以B+数来存储数据的,假设主键索引树上的数据如下
假如我们要删掉R4这个记录,InnoDB引擎只会把R4这个记录标记为删除(但是磁盘文件的大小并不会缩小),如果之后要再插入一个ID在300和600之间的记录时,可能会复用这个位置。
假如删除了整个数据页的数据,那整个数据页就可以被复用了。
数据页的复用和记录的复用的不同
记录的复用,只限于符合范围条件的数据;数据页的复用可以复用任何位置,任何新数据需要数据页都可以复用这个数据页。
数据页合并
如果相邻的两个数据页的利用率都很小,系统就会把这两个页上的数据合到其中一个页上,另外一个数据页就被标记为可复用。
delete 整个表?
使用delete命令把整个表的数据删除,所有的数据页都会被标记为可复用,但是磁盘上的文件不会变小。
数据空洞
这些可以被复用,而没有被使用的空间,看起来就像是“空洞”
插入数据造成数据空洞
- 如果数据是按照索引递增顺序插入的,那么索引是紧凑的,不会造成数据空洞
- 如果数据是随机插入的,就可能会造成数据页分裂,比如PageA已经满了,再要往PageA插入一条记录时,就不得不再申请一个新的PageB来保存数据了,这时就会产生页分裂,如果页分裂是从PageA的中间分裂的,页分裂完成后,PageA就会产生一半的数据空洞。
更新造成数据空洞
更新索引上的值,可以理解为删除一个旧的值,再插入一个新的值,和插入一样,这也会造成空洞
删除数据空洞
如果要我们做,我们可以新建一张与表A结构相同的表B,然后按照主键ID递增的顺序,把数据一行一行地从表A里读出来再插入到表B中,这样由于表B是新建的表,所以表A主键索引上的空洞,在表B中就都不存在了,使用表B替换A,从效果上看,就起到了收缩表A空间的作用。
MySQL的重建表
使用alter table A engine=InnoDB
命令来重建表,在MySQL5.5版本之前,这个命令的执行流程和上边我们说的流程差不多,区别只是这个临时表B不需要我们创建,MySQL会自动完成转存数据,交换表名,删除旧表的操作。
缺点
耗时: 花时间最多的步骤是往临时表插入数据的过程
阻塞写: 如果在这个过程中有新数据要写入到表A的话,就会造成数据丢失,因此在整个DDL过程中,表A不能有些操作,也就是说这个DDL不是Online的
优化
MySQL5.6版本开始引入了Online DDL,对上边的操作流程做了优化。流程如下:
- 建立一个临时文件,扫描表A主键的所有数据页
- 用数据页中标A的记录生成B+树,存储到临时文件中
- 生成临时文件的过程中,将对A的操作记录在一个日志文件(row log)中,对应的是图中state2的状态
- 临时文件生成后,将日志文件中的操作应用到临时文件,得到一个逻辑数据上与表A相同的数据文件,对应的就是图中state3的状态
- 用临时文件替换表A的数据文件
通过日志文件记录+重放操作,就可以在拷贝数据的过程中仍然接收DML操作,达到近Online的重建表,为什么说是近Online呢,因为Alter命令在启动的时候要获取MDL写锁,但是这个写锁在拷贝数据之前就已经退化为MDL读锁了。
为什么要退化为MDL读锁?
MDL读锁不影响DML操作
为什么不直接解锁?
MDL读锁能防止其他线程对这个表的DDL操作
工具
GitHub 开源的 gh-ost
optimize table、analyze table 和 alter table 这三种方式重建表的区别
- 从MySQL5.6版本开始,alter table t engine=InnoDB(也就是recreate)默认的就是上边的优化过后的流程了
- analyze table t并不是重建表,只是对表的索引信息做重新统计,并没有修改数据,这个过程中加了MDL读锁
- optimize table t等于recreate + analyze。