如何在不挂起 MySQL 进程的情况下使用 innodb_file_per_table=1 删除 1000 多个数据库?
How to drop a 1000+ databases with innodb_file_per_table=1 without hanging the MySQL process?
我们有一个重复的过程,我们希望并且需要清理我们的数据库。每个客户或潜在客户都有自己的数据库(有 300 table 秒,并且每个月都在增加),它会在几秒钟内启动,并植入一些基本数据。
几个月后,需要清理数据库。我们只需为每个数据库调用 DROP DATABASE customer_1
(在每个语句与 'rest' 之间给 MySQL 服务器 10 秒),然后是 DROP USER 'customer_1'@'127.0.0.1')
.
每隔一段时间,整个数据库就会挂起。 SHOW PROCESSLIST
给出
Id User Command Time State Info
[pid] adm-user Query 300 System lock DROP DATABASE `customer_1`
不会完成任何新查询。杀死相关查询 pid 将导致 Command=Killing,仅此而已。什么都没发生。 MySQL 守护程序也无法停止,因为它仍在等待完成查询。
我们关闭了整个服务器,重新启动它,并让 MySQL 执行自动崩溃恢复,效果很好。之后,我们可以删除另外 10-30 个数据库,然后这个事件会重复。
我们已经阅读了大量有关该主题的资料,包括但不限于:
- https://www.percona.com/blog/2011/02/03/performance-problem-with-innodb-and-drop-table/
- https://www.percona.com/blog/2009/06/16/slow-drop-table/
- https://dba.stackexchange.com/questions/41995/drop-database-locked-the-server
似乎共识是,是的,MySQL 在 table(space) 上使用了全局互斥锁,并结合了较大的缓冲池大小。
我们的my.cnf:
innodb_file_per_table = 1
innodb_buffer_pool_size = 9G
innodb_log_file_size = 256M
innodb_flush_method = O_DIRECT
table_open_cache = 200000
table_definition_cache = 110000
innodb_flush_log_at_trx_commit = 2
有什么方法可以让我们负责任地删除数据库——即,不让服务器因其他潜在客户而停机?
我读过,只需删除所有 table 文件即可,然后删除数据库,其中 MySQL 应该 只需删除对数据库。
您应该告诉数据库引擎不要建立独占(全局)锁。您可以通过两种方式执行此操作:
- 使用 LOCK clause(NONE 或共享)。
- 使用ALTER ONLINE TABLE(与LOCK=NONE相同)。
ALTER TABLE tbl_name ADD PRIMARY KEY (column), ALGORITHM=INPLACE, LOCK=NONE;
更改所有表后,或者如果您使用共享锁创建,您应该能够删除所有表。
您应该做的一件重要事情是为您的 MySQL 数据目录使用 XFS 文件系统。
将大文件拖放到 ext3 文件系统上会花费太多时间,正如您在所链接的 Percona 博客中所读到的那样。使用 XFS 可以更快地删除大文件,因此全局互斥锁的保留时间更短。
我还会一次删除一个 tables,以进一步减少互斥量的持有时间。然后在删除所有 table 之后,删除数据库。
MySQL 中的数据库根本不是物理对象。它是 MySQL datadir 的一个子目录,还有一个名为 db.opt
的小文件,它存储了数据库的一些属性,比如它的默认字符集(这甚至不再是 MySQL 中的一个单独文件) 8.0).在删除所有 table 之后,删除数据库本身就很简单了。
另一个建议是先把客户的MySQL用户去掉,然后让MySQL运行几个小时,直到那个客户的table的数据都没有缓存在缓冲池中的时间更长。当您删除一个大 table 时,MySQL 必须扫描缓冲池以释放属于该 table 的页面。缓冲池越大,所需的时间越长。因此,如果让该客户的 table 的页面过期并离开缓冲池,则可以最大限度地减少这种影响。这可能需要一些时间,因为它更多地是由对其他 table 的需求驱动的。除了删除 table.
之外,没有什么好的方法可以强制 table 的页面离开缓冲池
我在某些环境中这样做过。将 "DROP TABLE" 请求放入 RENAME TABLE 以将 table 移动到另一个用户无权访问的架构中。然后定期 运行 一个脚本来真正删除已经在该控制笔中超过 7 天的 tables。当来自其他 table 的数据取代它们时,这为页面逐渐从缓冲池中逐出提供了时间。此外,如果用户决定放弃他们需要的 table,它还为用户提供了改变主意的宽限期。
基于此评论链:
Is a script-based solution viable to you? E.g. Create a php script and run that? – treyBake Feb 14 at 17:33
@treyBake Yep, definitely. We've got full control over everything. – Mave 5 mins ag
您可以通过 PHP 执行此操作(例如将其命名为 rm_databases.php
:
$tableName = 'customer_';
for ($i = 1; $i <= 300; $++)
{
# set up db conn
$conn = new \PDO(
'mysql:dbname='. $tableName .$i. ';host=localhost;',
'user',
'pass'
);
# create the SQL statement
$sql = 'DROP DATABASE IF EXISTS '. $tableName .$i. ';';
# exec it
$conn->prepare($sql);
$conn->execute();
}
echo 'done!';
然后你可以选择 运行ning 手动 运行ning:
php -f rm_databases.php
或者您可以通过 cronjob 设置为每 3 个月 运行:
0 0 12 ? 1/3 MON#1 * php -f rm_databases.php
这将 运行 每 3 个月的第一个星期一。
旁注: 如果 每个 客户数据库的前缀为 customer_$i
- 但如果它更动态不仅如此,尽管它可能很累人,但创建一个数据库名称的巨型数组并循环遍历它可能是值得的。初始设置时间会更长,但完成后,添加新用户需要 2 秒:
$databases = [
'foo', 'bar3', 'foobar', 'treyisawesome', 'wp-firesf'
# etc etc
];
foreach ($databases as $el)
{
# set up db conn
$conn = new \PDO(
'mysql:dbname='. $el .';host=localhost;',
'user',
'pass'
);
# create the SQL statement
$sql = 'DROP DATABASE IF EXISTS '. $el .';';
# rest of the script stays the same
}
Bill Karwin 的建议似乎是合理的(尽管 RENAME TABLE
过去曾引发过一些与 DROP TABLE
相同的问题),但大部分内容应该已修复:Bug 51325 was fixed in 2011-12-20 in 5.6.4 and Bug 64284 was fixed in 2012-08-09 in 5.6.6.
您在删除 tables/databases 时可能遇到与 MySQL bug 91977, for which one suggested workaround is to disable the Adaptive Hash Index 有关的问题。
SET GLOBAL innodb_adaptive_hash_index = OFF;
DROP TABLE ...
SET GLOBAL innodb_adaptive_hash_index = ON;
或者完全放弃自适应哈希索引。请参阅上面链接的文档,其中指出它们是否是净收益取决于工作负载,您应该进行性能测试来决定是否使用它们。
您可能想要升级到当前的 MySQL 5.7.x,即 5.7.25,如果您仍然可以重现问题,请提交错误报告。
我们有一个重复的过程,我们希望并且需要清理我们的数据库。每个客户或潜在客户都有自己的数据库(有 300 table 秒,并且每个月都在增加),它会在几秒钟内启动,并植入一些基本数据。
几个月后,需要清理数据库。我们只需为每个数据库调用 DROP DATABASE customer_1
(在每个语句与 'rest' 之间给 MySQL 服务器 10 秒),然后是 DROP USER 'customer_1'@'127.0.0.1')
.
每隔一段时间,整个数据库就会挂起。 SHOW PROCESSLIST
给出
Id User Command Time State Info
[pid] adm-user Query 300 System lock DROP DATABASE `customer_1`
不会完成任何新查询。杀死相关查询 pid 将导致 Command=Killing,仅此而已。什么都没发生。 MySQL 守护程序也无法停止,因为它仍在等待完成查询。
我们关闭了整个服务器,重新启动它,并让 MySQL 执行自动崩溃恢复,效果很好。之后,我们可以删除另外 10-30 个数据库,然后这个事件会重复。
我们已经阅读了大量有关该主题的资料,包括但不限于:
- https://www.percona.com/blog/2011/02/03/performance-problem-with-innodb-and-drop-table/
- https://www.percona.com/blog/2009/06/16/slow-drop-table/
- https://dba.stackexchange.com/questions/41995/drop-database-locked-the-server
似乎共识是,是的,MySQL 在 table(space) 上使用了全局互斥锁,并结合了较大的缓冲池大小。
我们的my.cnf:
innodb_file_per_table = 1
innodb_buffer_pool_size = 9G
innodb_log_file_size = 256M
innodb_flush_method = O_DIRECT
table_open_cache = 200000
table_definition_cache = 110000
innodb_flush_log_at_trx_commit = 2
有什么方法可以让我们负责任地删除数据库——即,不让服务器因其他潜在客户而停机?
我读过,只需删除所有 table 文件即可,然后删除数据库,其中 MySQL 应该 只需删除对数据库。
您应该告诉数据库引擎不要建立独占(全局)锁。您可以通过两种方式执行此操作:
- 使用 LOCK clause(NONE 或共享)。
- 使用ALTER ONLINE TABLE(与LOCK=NONE相同)。
ALTER TABLE tbl_name ADD PRIMARY KEY (column), ALGORITHM=INPLACE, LOCK=NONE;
更改所有表后,或者如果您使用共享锁创建,您应该能够删除所有表。
您应该做的一件重要事情是为您的 MySQL 数据目录使用 XFS 文件系统。
将大文件拖放到 ext3 文件系统上会花费太多时间,正如您在所链接的 Percona 博客中所读到的那样。使用 XFS 可以更快地删除大文件,因此全局互斥锁的保留时间更短。
我还会一次删除一个 tables,以进一步减少互斥量的持有时间。然后在删除所有 table 之后,删除数据库。
MySQL 中的数据库根本不是物理对象。它是 MySQL datadir 的一个子目录,还有一个名为 db.opt
的小文件,它存储了数据库的一些属性,比如它的默认字符集(这甚至不再是 MySQL 中的一个单独文件) 8.0).在删除所有 table 之后,删除数据库本身就很简单了。
另一个建议是先把客户的MySQL用户去掉,然后让MySQL运行几个小时,直到那个客户的table的数据都没有缓存在缓冲池中的时间更长。当您删除一个大 table 时,MySQL 必须扫描缓冲池以释放属于该 table 的页面。缓冲池越大,所需的时间越长。因此,如果让该客户的 table 的页面过期并离开缓冲池,则可以最大限度地减少这种影响。这可能需要一些时间,因为它更多地是由对其他 table 的需求驱动的。除了删除 table.
之外,没有什么好的方法可以强制 table 的页面离开缓冲池我在某些环境中这样做过。将 "DROP TABLE" 请求放入 RENAME TABLE 以将 table 移动到另一个用户无权访问的架构中。然后定期 运行 一个脚本来真正删除已经在该控制笔中超过 7 天的 tables。当来自其他 table 的数据取代它们时,这为页面逐渐从缓冲池中逐出提供了时间。此外,如果用户决定放弃他们需要的 table,它还为用户提供了改变主意的宽限期。
基于此评论链:
Is a script-based solution viable to you? E.g. Create a php script and run that? – treyBake Feb 14 at 17:33
@treyBake Yep, definitely. We've got full control over everything. – Mave 5 mins ag
您可以通过 PHP 执行此操作(例如将其命名为 rm_databases.php
:
$tableName = 'customer_';
for ($i = 1; $i <= 300; $++)
{
# set up db conn
$conn = new \PDO(
'mysql:dbname='. $tableName .$i. ';host=localhost;',
'user',
'pass'
);
# create the SQL statement
$sql = 'DROP DATABASE IF EXISTS '. $tableName .$i. ';';
# exec it
$conn->prepare($sql);
$conn->execute();
}
echo 'done!';
然后你可以选择 运行ning 手动 运行ning:
php -f rm_databases.php
或者您可以通过 cronjob 设置为每 3 个月 运行:
0 0 12 ? 1/3 MON#1 * php -f rm_databases.php
这将 运行 每 3 个月的第一个星期一。
旁注: 如果 每个 客户数据库的前缀为 customer_$i
- 但如果它更动态不仅如此,尽管它可能很累人,但创建一个数据库名称的巨型数组并循环遍历它可能是值得的。初始设置时间会更长,但完成后,添加新用户需要 2 秒:
$databases = [
'foo', 'bar3', 'foobar', 'treyisawesome', 'wp-firesf'
# etc etc
];
foreach ($databases as $el)
{
# set up db conn
$conn = new \PDO(
'mysql:dbname='. $el .';host=localhost;',
'user',
'pass'
);
# create the SQL statement
$sql = 'DROP DATABASE IF EXISTS '. $el .';';
# rest of the script stays the same
}
Bill Karwin 的建议似乎是合理的(尽管 RENAME TABLE
过去曾引发过一些与 DROP TABLE
相同的问题),但大部分内容应该已修复:Bug 51325 was fixed in 2011-12-20 in 5.6.4 and Bug 64284 was fixed in 2012-08-09 in 5.6.6.
您在删除 tables/databases 时可能遇到与 MySQL bug 91977, for which one suggested workaround is to disable the Adaptive Hash Index 有关的问题。
SET GLOBAL innodb_adaptive_hash_index = OFF;
DROP TABLE ...
SET GLOBAL innodb_adaptive_hash_index = ON;
或者完全放弃自适应哈希索引。请参阅上面链接的文档,其中指出它们是否是净收益取决于工作负载,您应该进行性能测试来决定是否使用它们。
您可能想要升级到当前的 MySQL 5.7.x,即 5.7.25,如果您仍然可以重现问题,请提交错误报告。