揪出MySQL磁盘消耗迅猛的真凶

揪出MySQL磁盘消耗迅猛的真凶

背景

Part1:写在最前mysql

当一张单表10亿数据量的表放在你面前,你将面临着什么?sql

Part2:背景介绍数据库

为了提高数据库资源利用率,一个实例中,在不互相影响,保证业务高效的前提下,咱们会将同一个大业务下的不一样小业务放在一个实例中,咱们的磁盘空间是2T,告警阈值为当磁盘剩余空间10%时发出短信告警。笔者接到某业务主库磁盘剩余空间告警的短信后,通过一番查探,发现从几天前开始,有一张表的数据量增加很是快,而在以前,磁盘空间降低率仍是较为平缓的,该表存在大字段text,其大批量写入更新,致使磁盘消耗迅猛。bash

咱们首先来看下该表的表结构:session

mysql> CREATE TABLE `tablename_v2` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `No` varchar(64) NOT NULL DEFAULT '',
  `Code` varchar(64) NOT NULL DEFAULT '' ,
  `log` varchar(64) DEFAULT '' ,
  `log1` varchar(64) DEFAULT '' ,
   .....
  `Phone` varchar(20) DEFAULT '',
  `createTime` bigint(20) unsigned NOT NULL DEFAULT '0',
  `updateTime` bigint(20) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_mailNo` (`No`,`Code`),
  KEY `idx_Phone` (`Phone`)
) ENGINE=InnoDB AUTO_INCREMENT=9794134664 DEFAULT CHARSET=utf8;
复制代码

与业务了解得知,该表几乎没有删除操做,因为数据量过大,咱们模糊使用auto_increment来做为表数量预估值,避免count()操做对线上形成的影响。并发

Part3:案例分析app

与业务沟通了解后得知,该表能够清理4个月之前的老旧数据,所以可使用delete的方式清除,而咱们经过表结构能够看出,该表在设计之初,并无对updateTime列建立索引,所以以时间为范围去进行delete操做,这显然是不合理的。工具

通过与业务协商,咱们肯定了能够将id做为删除条件,删除id<2577754125以前的数据ui

也就是说,此时的delete语句变为了:this

mysql> delete from tablename_v2 where id <2577754125;
复制代码

且不说delete操做有多慢,直接执行这样的SQL也会有诸如长事务告警,从库大量延迟等并发症产生,所以毫不能在生产库上进行这种大批量的危险操做。

实战

Part1:监控

从监控图咱们能看出磁盘降低的趋势:


监控显示,从6月14日-6月18日期间,磁盘消耗最为严重,与业务沟通得知,618期间大促引起该表存储量激增致使。

Part2:实战操做

咱们经过查看binlog发现,集群中binlog的刷新量虽不说像笔者上个案例那样多么迅猛,但也毫不是老实本分


咱们能够看出,在高峰期间,binlog的刷新间隔最短达到了2分钟写满1.1GB的binlog。所以笔者与业务沟通后,首先清理binlog日志,将 expire_logs_days从7天调整至3天。

同时,清理一些可以清理的无用日志、废旧文件等等。

咱们也能在上面的监控图看到在作完这些清理操做后,磁盘空间剩余从4%提高至12%,但随后依旧保持原有速率降低。

Part3:pt-archiver

真凶找到了,咱们怎么办,别急,使用pt-archiver。pt-archiver工具是percona工具集的一员,是归档MySQL大表数据的最佳轻量级工具之一。他能够实现分chunk分批次归档和删除数据,能避免一次性操做大量数据带来的各类问题。

闲话很少说,一贯本着实战的原则,咱们直接上命令:

pt-archiver --source
h=c3-helei-db01.bj,D=helei,t=tablename_v2,u=sys_admin,p=MANAGER
--where 'id<2577754125' --purge --progress 10000 --limit=10000
--no-check-charset --txn-size=10000 --bulk-delete --statistics --max-lag=20
--check-slave-lag c3-helei-db02.bj</pre>
复制代码

简单说下经常使用的参数:

| source | 目标节点 |
| where | 条件 |
| purge | 删除source数据库的相关匹配记录 |
| progress | 每处理多少行显示一次信息 |
| limit | 每次取出多少行处理 |
| no-check-charset | 不检查字符集 |
| txn-size | 每多少行提交一次 |
| bulk-delete | 并行删除 |
| statistics | 结束后输出统计信息 |
| max-lag | 最大延迟 |
| check-slave-lag |

检查某个目标从库的延迟

|

****Warning:警********告****这里就又有个小坑了,的确,咱们使用bulk-delete参数可以增长删除速率,相比不使用bulk-delete速度可以提高10倍左右,但问题也就显现出来,在使用上述命令期间,发现binlog每秒写入量激增,这又回到了咱们说的,哪些状况会致使binlog转为row格式。

首先咱们须要了解到使用bulk-delete时,sql是以下执行的:

mysql> delete from tablename_v2 where id >xxx and id < xxx limit 10000.
复制代码

若是您以前关注过笔者的文章,应该知道,当使用了delete from xxx where xxx limit 语法时,会将binlog_format从mixed转为row,这样的话,删除的同时,binlog因为转为了row格式也在激增,这与咱们的预期是不符的。

所以最终的命令为:

pt-archiver --source
h=c3-helei-db01.bj,D=helei,t=tablename_v2,u=sys_admin,p=MANAGER
--where 'id<2577754125' --purge --progress 10000 --limit=10000
--no-check-charset --txn-size=10000  --statistics --max-lag=20
--check-slave-lag c3-helei-db02.bj</pre>
复制代码

去掉了bulk-delete,这样的话就可以保证正常的delete,而不加limit,binlog不会转为row格式致使磁盘消耗继续激增。

对于Innodb引擎来讲,delete操做并不会当即释放磁盘空间,新的数据会优先填满delete操做后的“空洞”,所以从监控来看就是磁盘不会进一步消耗了,说明咱们的pt-archiver工具删除是有效的。

Part4:困惑

首先咱们要知道,当你面对一张数据量庞大的表的时候,有些东西就会受限制,例如:

  1. 不能alter操做,由于这会阻塞dml操做。

  2. 对于本案例,空间本就不足,也不能使用pt-online工具来完成。

对于不能alter实际上是比较要命的,好比开发要求在某个时间段尽快上线新业务,而新业务须要新增列,此时面对这么庞大的量级,alter操做会异常缓慢。

所以,笔者与研发沟通,尽快采用物理分表的方式解决这个问题,使用物理分表,清理表的操做就会很容易,无需delete,直接drop 老表就能够了。其次,物理分表让alter语句不会卡住过久,使用pt-online工具也不会一次性占据过多的磁盘空间诱发磁盘空间不足的告警。

再有就是迁移TiDB,TiDB相较MySQL更适合存储这类业务。

Part5:再谈binlog_format

咱们选取其中高峰期的binlog发现其update操做转为了row格式,记录了全部列变动先后的全部信息,而binlog中并未出现update xxx limit这种操做,那又会是什么引起的row格式记录呢?

这里这篇文章又抛出一个新的案例,在官网那篇什么时候mixed转row格式中又一个没有记录的状况

官方文档:

When running in MIXED logging format, the server automatically switches from statement-based to row-based logging under the following conditions:
When a DML statement updates an NDBCLUSTER table.
When a function contains UUID().
When one or more tables with AUTO_INCREMENT columns are updated and a trigger or stored function is invoked. Like all other unsafe statements, this generates a warning if binlog_format = STATEMENT.
When any INSERT DELAYED is executed.
When a call to a UDF is involved.
If a statement is logged by row and the session that executed the statement has any temporary tables, logging by row is used for all subsequent statements (except for those accessing temporary tables) until all temporary tables in use by that session are dropped.
This is true whether or not any temporary tables are actually logged.
Temporary tables cannot be logged using row-based format; thus, once row-based logging is used, all subsequent statements using that table are unsafe. The server approximates this condition by treating all statements executed during the session as unsafe until the session no longer holds any temporary tables.
When FOUND_ROWS() or ROW_COUNT() is used. (Bug #12092, Bug #30244)
When USER(), CURRENT_USER(), or CURRENT_USER is used. (Bug #28086)
When a statement refers to one or more system variables. (Bug #31168)
复制代码

咱们这个案例中又出现了一个新的因素就是:

当表结构中存在多个惟一索引(包括主键id),本案例中存在主键和UNIQUE KEY uk_mailNo这个惟一索引,且使用了

INSERT ... ON DUPLICATE KEY UPDATE</pre>
复制代码

也就是说,只要业务解决了使用这种语法插入的话,磁盘空间降低迅猛的缘由也可以缓解很多。咱们统计发现,qps更高的其余业务中,binlog保留7天的磁盘消耗量在60GB

而该业务咱们仅仅保留3天binlog,却依旧消耗了430GB的磁盘空间,这已经超过了咱们整个2T磁盘空间的5分之一了。

——总结 ——

经过这个案例,咱们可以了****解到什么状况下binlog_format会由MIXED格式转为ROW格式,以及触发的一系列并发症和解决办法,还有pt工具pt-archiver的使用。因为笔者的水平有限,编写时间也很仓促,文中不免会出现一些错误或者不许确的地方,不妥之处恳请读者批评指正。喜欢笔者的文章,点一波关注,谢谢!

相关文章
相关标签/搜索