pt-archiver 主键自增


Posted in MySQL onApril 26, 2022

本文Percona Blog 的译文,原文移步文章末尾的 阅读原文。

前言

pt-archiver 是一款常见的 表清理或者归档工具。

MySQL 中删除大表之前可以使用 pt-archiver 批量删除所有记录。这样助于避免在某些情况下您的服务器可能会意外的情况,比如磁盘 IO 满导致数据库hang或者影响正常 SQL 慢查。

笔者最近遇到一个案例 ,有客户反馈 "使用 pt-archiver  删除数据时,最后一行数据未被删除。这个是不是bug?"

分析

在解决客户的问题之前,我们需要解释为什么在删除大表之前使用 pt-archiver 当我们在 MySQL 中删除一个表时, MySQL 系统会做如下动作:

删除表数据/索引 (ibd) 和定义 (frm) 文件。
删除触发器。
通过删除要删除的表来更新表定义缓存。
扫描 InnoDB 缓冲池以查找关联页面以使其无效。--内存到的表会遇到系统hang。

需要注意的是,DROP 是一个 DDL 语句,它需要持有元数据锁 (MDL) 才能完成,这样会导致所有其他线程必须等待DDL完成,清除表相关的大量数据页会对缓冲池产生额外的压力。

最后,table_definition_cache 操作需要 LOCK_open mutex 来清理,这会导致所有其他线程等待直到删除完成。

为了降低此操作的严重性,我们可以使用 pt-archiver 通过批量的形式删除大量数据,从而显着降低表大小。一旦我们从大表中删除了记录,DROP 操作就会快速进行而不会对系统性能产生影响。

社区成员注意到此行为,在 pt-archiver 完成后,该表仍有一行待处理。

# Created table
mysql> CREATE TABLE `tt1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `a` char(5) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

# Poured random test data into it
mysql> call populate('test','att1',10000,'N');

# Purged data using pt-archiver
[root@centos_2 ~]# pt-archiver --source=h=localhost,D=test,t=tt1 --purge --where "1=1"

# Verifying count (expected 0, got 1)
mysql> select count(*) from test.tt1;
+----------+
| count(*) |
+----------+
|        1 |
+----------+
1 row in set (0.00 sec)

当我们使用带有 --no-delete 参数的 pt-archiver 进行数据归档时,也会发生同样的情况。我们的工具 pt-archiver 似乎没有将最大值复制到目标表。

将表从 tt1 迁移到 tt2 
[root@centos_2 ~]# pt-archiver --source=h=localhost,D=test,t=tt1 --dest=h=localhost,D=test,t=tt2 --no-delete --where "1=1"

mysql> select count(*) from tt2;
+----------+
| count(*) |
+----------+
|     5008 |
+----------+
1 row in set (0.00 sec)

mysql> select count(*) from tt1;
+----------+
| count(*) |
+----------+
|     5009 |
+----------+
1 row in set (0.00 sec)

解析

通读 pt-archiver 文档,有一个选项 –[no]safe-auto-increment 描述了用法:“不要使用 max AUTO_INCREMENT 归档行。”

这意味着,选项 –safe-auto-increment(默认)添加了一个额外的 WHERE 子句,以防止 pt-archiver 在提升单列 AUTO_INCREMENT 时删除最新的行,如下面的代码部分所示:

https://github.com/percona/percona-toolkit/blob/3.x/bin/pt-archiver#L6449
   if ( $o->get('safe-auto-increment')
         && $sel_stmt->{index}
         && scalar(@{$src->{info}->{keys}->{$sel_stmt->{index}}->{cols}}) == 1
         && $src->{info}->{is_autoinc}->{
            $src->{info}->{keys}->{$sel_stmt->{index}}->{cols}->[0]
         }
   ) {
      my $col = $q->quote($sel_stmt->{scols}->[0]);
      my ($val) = $dbh->selectrow_array("SELECT MAX($col) FROM $src->{db_tbl}");
      $first_sql .= " AND ($col < " . $q->quote_val($val) . ")";
   }

让我们通过空运行输出看看这两个命令之间的区别:

# With --no-safe-auto-increment
[root@centos_2 ~]# pt-archiver --source=h=localhost,D=test,t=tt1 --dest=h=localhost,D=test,t=tt2 --no-delete --where "1=1" <strong>--no-safe-auto-increment</strong> --dry-run
SELECT /*!40001 SQL_NO_CACHE */ `id`,`a` FROM `test`.`tt1` FORCE INDEX(`PRIMARY`) WHERE (1=1) ORDER BY `id` LIMIT 1
SELECT /*!40001 SQL_NO_CACHE */ `id`,`a` FROM `test`.`tt1` FORCE INDEX(`PRIMARY`) WHERE (1=1) AND ((`id` > ?)) ORDER BY `id` LIMIT 1
INSERT INTO `test`.`tt2`(`id`,`a`) VALUES (?,?)
# Without --no-safe-auto-increment (default)
[root@centos_2 ~]# pt-archiver --source=h=localhost,D=test,t=tt1 --dest=h=localhost,D=test,t=tt2 --no-delete --where "1=1" --dry-run
SELECT /*!40001 SQL_NO_CACHE */ `id`,`a` FROM `test`.`tt1` FORCE INDEX(`PRIMARY`) WHERE (1=1) <strong>AND (`id` < '5009')</strong> ORDER BY `id` LIMIT 1
SELECT /*!40001 SQL_NO_CACHE */ `id`,`a` FROM `test`.`tt1` FORCE INDEX(`PRIMARY`) WHERE (1=1) <strong>AND (`id` < '5009')</strong> AND ((`id` > ?)) ORDER BY `id` LIMIT 1
INSERT INTO `test`.`tt2`(`id`,`a`) VALUES (?,?)

注意到上面的附加子句 "AND ( id< '5009')" 了吗?

如果服务器重新启动,–no-safe-auto-increment 的这个选项可以防止重新使用 AUTO_INCREMENT 值。请注意,额外的 WHERE 子句包含自归档或清除作业开始时自增列的最大值。如果在 pt-archiver 运行时插入新行,pt-archiver 将看不到它们。

好吧,现在我们知道了为什么没有删除干净的“原因”,但为什么呢?AUTO_INCREMENT 的安全问题是什么?

AUTO_INCREMENT 计数器存储在内存中,当 MySQL 8.0之前的版本 重新启动(崩溃或其他)时,计数器将重置为最大值。如果发生这种情况并且表正在接受写入,则 AUTO_INCREMENT 值将更改。

# deleting everything from table
mysql> delete from tt1;
...
mysql> show table status like 'tt1'\G
*************************** 1. row ***************************
           Name: tt1
         Engine: InnoDB
...
 Auto_increment: 10019
...

# Restarting MySQL
[root@centos_2 ~]# systemctl restart mysql

# Verifying auto-increment counter
[root@centos_2 ~]# mysql test -e "show table status like 'tt1'\G"
*************************** 1. row ***************************
           Name: tt1
         Engine: InnoDB
...
 Auto_increment: 1
...

上面的测试结果告诉我们: 这里的问题实际上并不在于 pt-archiver,而在于参数选项。在处理 AUTO_INCREMENT 列时使用 pt-archiver 时,了解使用 –no-safe-auto-increment 选项很重要。

让我们用我们的实验室数据来验证它。

# Verifying the usage of –no-safe-auto-increment option
[root@centos_2 ~]# pt-archiver --source=h=localhost,D=test,t=tt1 --purge --where "1=1" --no-safe-auto-increment

mysql> select count(*) from test.tt1;
+----------+
| count(*) |
+----------+
|        0 |
+----------+
1 row in set (0.00 sec)

使用 –no-delete 选项的复制操作也是如此。

[root@centos_2 ~]# pt-archiver --source=h=localhost,D=test,t=tt1 --dest=h=localhost,D=test,t=tt2 --no-delete --where "1=1" --no-safe-auto-increment

mysql> select count(*) from tt1; select count(*) from tt2;
+----------+
| count(*) |
+----------+
|     5009 |
+----------+
1 row in set (0.00 sec)

+----------+
| count(*) |
+----------+
|     5009 |
+----------+
1 row in set (0.00 sec)

通过上面的代码和实际测试,我们知道了 pt-archiver 的 -[no]safe-auto-increment 选项的原理和作用 。在我们得出一切都很好的结论之前,让我们多考虑一下选项本身存在的意义。

  • 默认情况下,–no-delete 操作应包含 –no-safe-auto-increment 选项。目前,safe-auto-increment 是默认行为。当我们使用 pt-archiver 的 --no-delete 选项时,没有删除操作。这意味着 safe-auto-increment 不应成为关注的原因。

  • 对于 MySQL 8.0,不需要 safe-auto-increment 选项。因为 MySQL 8.0 开始,自增的值是持久化的,并且在实例重新启动或崩溃后自增的最大值不变。参考:MySQL 工作日志 https://dev.mysql.com/worklog/task/?id=6204

而且由于 MySQL 8.0 auto-increment 是通过重做日志持久化的,这使得它们成为pt-archiver 不关心的一个原因。因此,我们根本不需要 safe-auto-increment 选项。

结论

  • pt-archiver 是归档 MySQL 数据的好工具,重要的是要了解所有选项以完全控制我们想要使用它实现的目标。

  • 以后需要根据自增id进行归档的场景,pt-archiver 默认最大的id不会进行归档,需要添加参数:--no-safe-auto-increment 才能对最大id进行处理。

  • 原文: https://www.percona.com/blog/pt-archiver-with-auto-increment-column

到此这篇关于pt-archiver和自增主键的文章就介绍到这了!


Tags in this post...

MySQL 相关文章推荐
MySQL kill不掉线程的原因
May 07 MySQL
详解MySQL 联合查询优化机制
May 10 MySQL
详解MySQL中的主键与事务
May 27 MySQL
MySQL完整性约束的定义与实例教程
May 30 MySQL
如何自己动手写SQL执行引擎
Jun 02 MySQL
MySQL8.0无法启动3534的解决方法
Jun 03 MySQL
MySQL系列之十二 备份与恢复
Jul 02 MySQL
sql注入教程之类型以及提交注入
Aug 02 MySQL
MySQL中的隐藏列的具体查看
Sep 04 MySQL
mysql5.6主从搭建以及不同步问题详解
Dec 04 MySQL
MySQL高级进阶sql语句总结大全
Mar 16 MySQL
Mysql多层子查询示例代码(收藏夹案例)
Mar 31 MySQL
提高系统的吞吐量解决数据库重复写入问题
Apr 23 #MySQL
MySQL 数据库范式化设计理论
Apr 22 #MySQL
MySQL提取JSON字段数据实现查询
mysql使用FIND_IN_SET和group_concat两个方法查询上下级机构
Apr 20 #MySQL
在MySQL中你成功的避开了所有索引
Apr 20 #MySQL
mysql中如何用命令创建联合唯一索引
Apr 20 #MySQL
mysql 8.0.27 绿色解压版安装教程及配置方法
You might like
全国FM电台频率大全 - 16 河南省
2020/03/11 无线电
PHP中的session永不过期的解决思路及实现方法分享
2011/04/20 PHP
PHP函数http_build_query使用详解
2014/08/20 PHP
农历与西历对照
2006/09/06 Javascript
获取dom元素那些讨厌的位置封装代码
2010/06/23 Javascript
Jquery中getJSON在asp.net中的使用说明
2011/03/10 Javascript
浅析jquery的作用与优势
2013/12/02 Javascript
javascript检测浏览器的缩放状态实现代码
2014/09/28 Javascript
为什么Node.js会这么火呢?Node.js流行的原因
2014/12/01 Javascript
javascript实现确定和取消提示框效果
2015/07/10 Javascript
JavaScript中数组的22种方法必学(推荐)
2016/07/20 Javascript
浅谈js中的引用和复制(传值和传址)
2016/09/18 Javascript
Vue异步组件使用详解
2017/04/08 Javascript
Webpack执行命令参数详解
2017/06/17 Javascript
纯js实现页面返回顶部的动画(超简单)
2017/08/10 Javascript
angularjs结合html5实现拖拽功能
2018/06/25 Javascript
Angular中的ng-template及angular 使用ngTemplateOutlet 指令的方法
2018/08/08 Javascript
nodejs搭建本地服务器并访问文件操作示例
2019/05/11 NodeJs
VUE单页面切换动画代码(全网最好的切换效果)
2019/10/31 Javascript
VueCli4项目配置反向代理proxy的方法步骤
2020/05/17 Javascript
[01:00]一分钟回顾2018DOTA2亚洲邀请赛现场活动
2018/04/07 DOTA
python3实现ftp服务功能(客户端)
2017/03/24 Python
Python数据结构之栈、队列的实现代码分享
2017/12/04 Python
Python中的取模运算方法
2018/11/10 Python
Python中的 sort 和 sorted的用法与区别
2019/08/10 Python
浅谈pycharm使用及设置方法
2019/09/09 Python
python将邻接矩阵输出成图的实现
2019/11/21 Python
Pytorch 中retain_graph的用法详解
2020/01/07 Python
Python异常原理及异常捕捉实现过程解析
2020/03/25 Python
Python爬虫教程知识点总结
2020/10/19 Python
CSS3中线性颜色渐变的一些实现方法
2015/07/14 HTML / CSS
小学生志愿者活动方案
2014/08/23 职场文书
《颐和园》教学反思
2016/02/19 职场文书
python实现剪贴板的操作
2021/07/01 Python
Win11软件图标固定到任务栏
2022/04/19 数码科技
Oracle使用别名的好处
2022/04/19 Oracle