MySQL 笔记整理(13) --为什么数据表删掉一半,表文件大小不变?
笔记记录自林晓斌(丁奇)老师的《MySQL实战45讲》
(本篇内图片均来自丁奇老师的讲解,如有侵权,请联系我删除)
13) --为什么数据表删掉一半,表文件大小不变?
我们还是以MySQL中应用最广泛的InnoDB引擎为基础来展开讨论。一个表中包含两部分:表结构定义和数据。在MySQL8.0版本以前,表结构是以.frm为后缀的文件存储的。而在MySQL8.0版本,已经允许把表结构定义放在系统数据表中了。因为表结构定义占用的空间很小,索引我们今天主要讨论的是表数据。
参数innodb_file_per_table
表数据既可以放在共享表空间里,也可以单独存储在文件里。这个行为是由参数innodb_file_per_table控制的。OFF时表的数据存放在共享表空间里,也就是跟数据字典放在一起。ON时每个表存储在一个以.ibd为后缀的文件中。从MySQL5.66版本开始,这个值默认是ON了。建议你无论使用MySQL的哪个版本都将这个值设置为ON,因为单独存储为文件的表更方便管理。而且,当你不需要的时候,通过drop table命令,系统就会直接删除这个文件。而如果是在共享表空间中,即使表删掉了,空间也是不会回收的。
我们接下来的讨论都是基于这个设置展开的。(innodb_file_per_table设置为ON)。
数据删除流程:
我们在删除整个表的时候,可以使用drop table命令回收表空间。但是,我们遇到的更多的是删除某些行,这时就遇到了我们文章开头的问题:表中的数据被删除了,但是表空间却没有被回收。

图1 B+树索引示意图
我们之前有提到过,InnoDB里的数据是用B+树的结构组织的。如果要删除R4这条记录,InnoDB引擎会把R4这个记录标记为删除。如果之后要插入一个ID在300~600之间的记录时,可能会复用这个位置。但是,磁盘的大小不会缩小。
我们知道,InnoDB的数据是按页存储的,如果我们删掉一个数据页上的所有数据会怎样呢?答案是,整个数据页都可以被复用。数据页复用与行记录的复用不同,记录的复用只限于符合范围条件的数据。比如上面这个例子中,如果插入的ID是400则可以复用,如果ID是800就不能复用记录的空间。而当整个数据页都被删掉时,这个数据页可以被复用到任何位置。比如page A上的所有记录被删除时,如果要插入一条ID=50的记录并需要使用新页时,page A是可以被复用的。
进一步的说,如果我们使用delete命令把整个表的数据删除,所有的数据页都会被标为可复用,但是磁盘上,文件不会变小。你现在知道了,delete命令其实只是把记录的位置,活着数据页标记为“可复用”,但磁盘的大小是不会改变的。也就是说,通过delete命令是不能回收表空间的。这些可以复用,而没有被使用的空间,看起来就像是“空洞”。
实际上,不止是删除数据会造成空洞,插入数据也会。
如果数据是按照索引递增顺序插入的,那么索引是紧凑的。但如果数据是随机插入的,就可能造成索引的数据页分裂。还使用图1做例子,假设page A已经满了,如果我要插入一条ID=550的记录,由于索引的组织关系,它应该在R4与R5之间。但由于page A已满,这个插入操作会导致page A分裂成两个新的数据页来存储数据。并且值得注意的是,我们之前假定page A已满,即这个页可以存放3条记录(只是个假设,实际上会远多于三条记录),分裂之后的两个新的数据页每个都只存放了两条记录。这两个新的数据页上剩下的空间就是空洞了。实际上,可能不止1个记录的位置是空洞。
另外,更新索引上的值,可以理解为删除一个旧的值,再插入一个新的值。不难理解,这也是会造成空洞的。
也就是说,经过大量的增删改的表,都是可能存在空洞的。所以,如果能够把这些空洞去掉,就能达到收缩表空间的目的。而重建表,就可以达到这样的目的。
重建表:
你可以使用alter table A engine=InnoDB命令来重建表。MySQL会自动完成转存数据,交换表名,删除旧表的操作。在MySQL5.6版本之前,当在重建表的过程中,如果向旧表插入数据,会造成数据丢失,因此整个DDL过程中,旧表是不能有更新的。也就是整个DDL不是Online的。而在这个版本开始引入了Online DDL,对这个操作流程做了优化。
- 建立一个临时表A(旧表),扫描表A主键的所有数据页。
- 用数据页中表A的记录生成B+树,存储到临时文件中
- 生成临时文件的过程中,将所有对A的操作记录在一个日志文件中(row log)
- 临时文件生成后,将日志文件中的操作应用到临时文件,得到一个逻辑数据上与表A相同的数据文件,对应的就是同种state3的状态。
- 用临时文件替换表A的数据文件。
这个过程还存在一个细节。我们知道,alter语句在启动的时候需要获取MDL写锁。这样还能叫Online嘛?其实,这个写锁在真正拷贝数据之前就会退化成读锁。为什么要退化呢?为了实现Online,MDL读锁不会阻塞增删改操作。那么为什么不直接就用读锁呢?为了保护自己,禁止除自身外其他线程对这个表同时做DDL。而相对来讲,这个过程中最耗时的是拷贝数据到临时表的过程。因此对于整个DDL过程来说,锁的时间非常短,可以认为是Online的。
需要额外说明的是,对于大表来说,这个操作很消耗IO和CPU资源,因此,如果是线上服务,你要小心地控制操作时间。
Online和inplace:
对于非Online模式,在重建表A的时候,会生成一个tmp table用来存放导出的数据。这个一个临时表,是在server层创建的。而Online模式,表A重建出来的数据是放在“tmp_file”里的,这个临时文件是InnoDB在内部创建出来的。整个DDL过程都在InnoDB内部完成。对于Server层来说,没有把数据挪动到临时表,是一个“原地”操作,这就是“inplace”名称的来源。因此,如果你有一个1TB的表,现在磁盘间是1.2TB,能不能做一个inplace的DDL呢?答案是不能。因为,tmp_file也是要占用临时空间的。我们重建表的语句alter table t engine = InnoDB,其实隐含的含义是:alter table t engine = InnoDB,ALGORITHM=inplace;跟inplace对应的是拷贝表的方式,用法是alter table t engine = InnoDB,ALGORITHM=copy;而强制拷贝表即off line 方式。
因此,举一个例子,我要给InnoDB表的一个字段加全文索引,写法是alter table t add FULLTEX(filed_name);整个过程是inplace的,但会阻塞增删改操作,是非Online的。
DDL过程如果是Online的,就一定是inplace的。
反过来未必,也就是说inplace的DDL,有可能不是Online的。截止到MySQL8.0,添加全文索引(FULLTEXT index)可空间索引(SPATIAL index)就属于这种情况。
上期问题:
一个内存配置为128GB,innodb_io_capacity设置为20000的大规格实例,正常会建议你将redo log设置成4个1GB的文件,但是如果你配置时不小心将redo log设置为了4个100M的文件,会发生什么情况呢?为什么呢?
对于一个高配置的机器,如果redo log设置太小,很快就会被写满。也就是会不停地去移动redo log中的checkpoint,这个checkpoint可以表示哪些内容是已经更新到硬盘可以去掉的位置。而要移动check point就要去刷脏页,这时系统不得不停止所有更新。所以你会看到磁盘压力很小,但是数据库出现间歇性的性能下跌。
问题:
如果有一个情况是这样的:想要收缩表空间,结果适得其反,看上去是:
- 一个表t文件大小为1TB;
- 对这个表执行alter table t engine = InnoDB;
- 执行完成后,空间不仅没有变小,还稍微大了一点,变为了1.01TB
请问这是什么原因导致的呢?
MySQL 笔记整理(13) --为什么数据表删掉一半,表文件大小不变?的更多相关文章
- 《Mysql - 为什么表数据删掉一半,表文件大小不变?》
一:概念 - 这里,我们还是针对 MySQL 中应用最广泛的 InnoDB 引擎展开讨论. - 一个 InnoDB 表包含两部分,即:表结构定义和数据. - 在 MySQL 8.0 版本以前,表结构是 ...
- 最全mysql笔记整理
mysql笔记整理 作者:python技术人 博客:https://www.cnblogs.com/lpdeboke Windows服务 -- 启动MySQL net start mysql -- 创 ...
- 教你如何6秒钟往MySQL插入100万条数据!然后删库跑路!
教你如何6秒钟往MySQL插入100万条数据!然后删库跑路! 由于我用的mysql 8版本,所以增加了Timezone,然后就可以了 前提是要自己建好库和表. 数据库test, 表user, 三个字段 ...
- MySQL 笔记整理(6) --全局锁和表锁:给表加个字段怎么有这么多阻碍
笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> 6) --全局锁和表锁:给表加个字段怎么有这么多阻碍 数据库锁设计的初衷是处理并发问题.作为多用户共享的资源,当出现并发访问的时候, ...
- Mysql笔记【2】-数据表的基本操作
1.创建数据库表 create table <表名> ( 字段名1 类型 <列级别限制> , 字段名2 类型 <列级别限制> , 字段名3 类型 <列级别限制 ...
- MySQL 笔记整理(1) --基础架构,一条SQL查询语句如何执行
最近在学习林晓斌(丁奇)老师的<MySQL实战45讲>,受益匪浅,做一些笔记整理一下,帮助学习.如果有小伙伴感兴趣的话推荐原版课程,很不错. 1) --基础架构,一条SQL查询语句如何执行 ...
- MySQL 笔记整理(17) --如何正确地显示随机消息?
笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> (本篇内图片均来自丁奇老师的讲解,如有侵权,请联系我删除) 17) --如何正确地显示随机消息? 如果有这么一个英语单词表,需要每次 ...
- MySQL 笔记整理(16) --“order by”是怎么工作的?
笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> (本篇内图片均来自丁奇老师的讲解,如有侵权,请联系我删除) 16) --“order by”是怎么工作的? 在林老师的课程中,第15 ...
- MySQL 笔记整理(11) --怎么给字符串字段加索引?
笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> (本篇内图片均来自丁奇老师的讲解,如有侵权,请联系我删除) 11) --怎么给字符串字段加索引? 日常工作中的登录系统,你很可能会使 ...
随机推荐
- Mybatis配置详解
一.SqlSession的使用范围说明 1.SQLSessionFactoryBuilder 通过SqlSessionFactoryBuilder创建会话工厂SqlSessionFactory, ...
- Mysql存储过程历史表备份
应用背景 SCADA采集系统需要将实时数据存入历史表.问题1:如何更简单的添加历史数据?2.海量历史数据,比如年数据,如何快速筛选 画曲线? 利用mysql的事件,每小时存一次采集数据: 每月备份历史 ...
- Redis 实战篇之搭建集群
Redis 集群简介# Redis Cluster 即 Redis 集群,是 Redis 官方在 3.0 版本推出的一套分布式存储方案.完全去中心化,由多个节点组成,所有节点彼此互联.Redis 客户 ...
- Kubernetes的DaemonSet(下篇)
用Daemon Pod来进行通信 使用Pod来再DaemonSet中通信的手段有: 推的方式:在DaemonSet中的Pod会被配置成发送更新到如状态数据库这样的服务.这些都没有客户端. IP+端口方 ...
- xamarin forms中的Button文本默认大写
问题来源 使用xamarin forms创建的android项目中,Button.Toolbar的右侧菜单按钮上的如果是字母的话,在android5.0以上,默认的文本都是大写,这种情况iOS项目不存 ...
- WebWorker 简单使用方式
WebWorker 一定程度上可以算得上是浏览器中的多线程技术了,在项目中适当使用 Worker 来做一些耗时的操作能大大提高页面整体流畅度. Worker的使用也是非常简单的,通过向 Worker ...
- 软件测试自动化的最新趋势对开源测试管理软件ITEST的启示
https://www.infoq.cn/article/c-LHJS2ksuDxp1WkrGl4 理面提到几点,DevOps 的关键原则是开发团队.测试团队和运营团队协作,无缝发布软件.这意味着集中 ...
- VulDeePecker:基于深度学习的脆弱性检测系统
最近的两款软件,VUDDY和VulPecker,假阴性率高而假阳性率低,用于检测由代码克隆引发的漏洞.而如果用于非代码克隆引起的漏洞则会出现高误报率. 本文使用深度学习处理程序中的代码片段,不应由专家 ...
- Java线程状态间的互相转换
ava中线程的状态分为6种. 1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法. 2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running) ...
- Android 项目中用得最多最火的第三方框架可能都在这里了
分类 二级分类 框架名称 简介 Star 数 最近更新 UI 刷新 SmartRefreshLayout 智能下拉刷新框架 14k 18天 UI 刷新 Android-PullToRefresh 比较 ...