MySQL的alter table查询非常慢

Posted

技术标签:

【中文标题】MySQL的alter table查询非常慢【英文标题】:MySQL very slow for alter table query 【发布时间】:2012-09-28 06:57:04 【问题描述】:

为什么简单地更新此表以添加一列需要一个多小时?该表有 15M 行。它有 2 个索引和一个单键主键。 ALTER TABLE 查询已处于“复制到 tmp 表”状态 1 小时 15 分钟。

ALTER TABLE `frugg`.`item_catalog_map` 
ADD COLUMN `conversion_url` TEXT NULL DEFAULT NULL

表:

mysql> describe item_catalog_map;
+------------------------+---------------+------+-----+---------+-------+
| Field                  | Type          | Null | Key | Default | Extra |
+------------------------+---------------+------+-----+---------+-------+
| catalog_unique_item_id | varchar(255)  | NO   | PRI | NULL    |       |
| catalog_id             | int(11)       | YES  | MUL | NULL    |       |
| item_id                | int(11)       | YES  | MUL | NULL    |       |
| price                  | decimal(10,2) | YES  |     | 0.00    |       |
+------------------------+---------------+------+-----+---------+-------+

mysql> show index from item_catalog_map;
+------------------+------------+----------------------+--------------+------------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table            | Non_unique | Key_name             | Seq_in_index | Column_name            | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+------------------+------------+----------------------+--------------+------------------------+-----------+-------------+----------+--------+------+------------+---------+
| item_catalog_map |          0 | PRIMARY              |            1 | catalog_unique_item_id | A         |    15485115 |     NULL | NULL   |      | BTREE      |         |
| item_catalog_map |          1 | IDX_ACD6184FCC3C66FC |            1 | catalog_id             | A         |          18 |     NULL | NULL   | YES  | BTREE      |         |
| item_catalog_map |          1 | IDX_ACD6184F126F525E |            1 | item_id                | A         |    15485115 |     NULL | NULL   | YES  | BTREE      |         |
+------------------+------------+----------------------+--------------+------------------------+-----------+-------------+----------+--------+------+------------+---------+

【问题讨论】:

可能有类似问题的这个帖子会帮助你...***.com/questions/1359097/… 也通过这个线程...***.com/questions/5677932/… 如果有某种进度条、状态消息或者至少是一个旋转的光标,LOL 会很酷。尤其是“修改列”使我的网站离线。 【参考方案1】:

MySQL 的 ALTER TABLE 性能可能会成为非常大的表的问题。 MySQL 执行 大多数更改是通过创建一个具有所需新结构的空表,将旧表中的所有数据插入新表,然后删除旧表。这可能需要很长时间,尤其是在内存不足且表很大并且有很多索引的情况下。许多人都曾经历过需要数小时或数天才能完成的 ALTER TABLE 操作。

无论如何,如果您需要继续更改表,也许以下资源可以帮助您:

https://www.percona.com/doc/percona-toolkit/2.2/pt-online-schema-change.html https://github.com/soundcloud/lhm https://githubengineering.com/gh-ost-github-s-online-migration-tool-for-mysql/

【讨论】:

如果你的存储系统出现问题需要那么长时间,那不是 MySQL 的问题。 我正在从 varchar 更改为 30 行表中的文本,等待了 20 分钟,但仍在继续。会是什么? VARCHAR 存储在表中,而文本单独存储,引用存储在表中。所以他们是非常不同的。我假设提示更改是因为您有一个非常大的 varchar 并且您希望允许更大的值。在这种情况下,它需要移动所有的值。 @AndreKR - 不,我认为这很明显是 MySQL 问题。将一列添加到包含 900MB 数据的表(大约 25K 行包含小的 blob)不应该花费超过 3 个小时,这就是我现在所处的位置。我的存储系统也没有问题;数据库存储在一个相当不错的 SSD 上,我有足够的空闲 RAM 用于缓存。我认为 InnoDB 在这种操作期间维护索引很糟糕;我猜它没有优化的批量插入机制,所以一次重建新索引一行。 @Jules 你可能是对的。我想当我写评论时,我认为它是一个 MyISAM 表,因为我认为 InnoDB 不会发生“复制到 tmp 表”,但确实在问题中没有给出表类型。对于 InnoDB,通常会为 ALTER TABLE 操作计划几天,这就是存在 github.com/facebookincubator/OnlineSchemaChange 之类的软件的原因。【参考方案2】:

如果您不关心停机时间,我的建议是使用三个分开的 ALTER TABLE 语句。第一条语句删除所有现有的二级索引。第二条语句应用所有与列相关的更改。最后一条语句将删除的二级索引添加回来并应用其他索引更改。

另外两个提示:

    在应用索引更改之前,执行以下两条语句,并在完成索引更改后将值更改回1。

    SET unique_checks=0;
    SET foreign_key_checks=0;
    

    创建多个二级索引时,将它们放在一个ALTER TABLE语句中,而不是多个单独的ALTER TABLE语句。

下图显示了性能差异。方法 1 是您的方法,方法 2 是我的方法。 对于 50m 的表,方法 2 与方法 1 相比大约需要 3.47% 的时间。 该解决方案仅适用于 MySQL (>=5.5) InnoDB 引擎。

【讨论】:

应该注意的是,我相信 MySQL 的 Percona 变体通过设置 expand_fast_index_creation=ON 内置了这个功能【参考方案3】:

为了最大程度地减少我要更改的大表的锁定,我执行以下操作:

在现有表的基础上新建一个空表,并修改这个新的空表。 对大表执行 mysqldump,使其在大表中的每条记录都有一个完整的插入语句(开关 -c 和 --skip-extended-insert) 将此 mysqldump 导入到另一个(空)数据库中,并使用重命名为 large_table 的空数据库。 从另一个数据库中获取这个新重命名表的 mysqldump 并将其导入到原始数据库中

在原数据库中重命名 large_table 和 large_table_new。

mysql> create table DATABASE_NAME.LARGE_TABLE_NEW like DATABASE_NAME.LARGE_TABLE;
mysql> alter table DATABASE_NAME.LARGE_TABLE_NEW add column NEW_COLUMN_NAME COL_DATA_TYPE(SIZE) default null;

$ mysqldump -c --no-create-info --skip-extended-insert --no-create-db -u root -p DATABASE_NAME LARGE_TABLE > LARGE_TABLE.sql

mysql> create table test.LARGE_TABLE like DATABASE_NAME.LARGE_TABLE;

$ mysql -u root -p -D test < LARGE_TABLE.sql

mysql> rename table test.LARGE_TABLE to test.LARGE_TABLE_NEW;

$ mysqldump -c --no-create-info --skip-extended-insert --no-create-db -u root -p test LARGE_TABLE_NEW > LARGE_TABLE_NEW.sql

$ mysql -u root -p -D DATABASE_NAME < LARGE_TABLE_NEW.sql

mysql> rename table DATABASE_NAME.LARGE_TABLE to DATABASE_NAME.LARGE_TABLE_OLD, DATABASE_NAME.LARGE_TABLE_NEW to DATABASE_NAME.LARGE_TABLE;

【讨论】:

【参考方案4】:

Percona 工具是这些带有大桌子的东西的救星。

http://www.percona.com/doc/percona-toolkit/2.1/pt-online-schema-change.html

他们基本上:

    创建重复表 创建触发器以同步表 批量复制数据 验证 交换表

需要很长时间,但谁在乎呢,因为这意味着您可以在不停机的情况下更改列。

【讨论】:

如果您的表上有外键,则无济于事,因为您无法自动重命名两个表 显然,你不能改变外键一侧的数据类型而不改变另一侧。这是给定的。然后,您必须删除外键,更改两列,然后重新引入外键。【参考方案5】:

您的表有 1500 万行,这很重要。 ALTER TABLE 涉及复制表中的所有数据并重新创建索引。作为第一次测量,尝试在文件系统中复制数据文件(item_catalog_map.MYD,如果它是 MyISAM)并查看需要多长时间。这是 ALTER TABLE 至少需要的时间。

【讨论】:

【参考方案6】:

我遇到了同样的问题,我刚刚使用以下方法重新启动了 MySQL 服务:

sudo service mysql restart

在 Ubuntu 中,然后ALTER TABLE 命令立即通过。

【讨论】:

以上是关于MySQL的alter table查询非常慢的主要内容,如果未能解决你的问题,请参考以下文章

MySQL - SELECT WHERE field IN(子查询) - 为啥非常慢?

MySQL高级查询

MySQL Left Join 运行速度非常慢,拆分为 2 个查询要快得多

执行ALTER TABLE语句时如何避免长时间阻塞并发查询

mysql 慢查询日志,灾难日志恢复,错误日志

mysql数据库以加索引方式提高性能