INSERT INTO SELECT 在集群上花费很长时间
INSERT INTO SELECT takes long time on cluster
我的 mysql 集群:Ver 5.6.30-76.3-56 for debian-linux-gnu on x86_64 (Percona XtraDB Cluster (GPL), Release rel76.3, Revision aa929cb, WSREP version 25.16, wsrep_25.16)
我有一个复杂的 sql 查询,它使用以下语法将大约 36k 行插入到 table 中:
INSERT INTO `sometable` (SELECT ...);
select有点复杂但不慢(0.0023s)但是插入大约需要40-50s。当我插入行时 table 没有被使用。
我的问题是:
- 我可以加快速度吗?
- 缓慢的插入导致其他 table 的锁定问题(因为 select)
- 这个工作流程是好的还是坏的做法?还有更好的吗?
谢谢
更新:
table 架构:
CREATE TABLE `sometable` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) unsigned DEFAULT NULL,
`a` varchar(255) DEFAULT NULL,
`b` smallint(6) unsigned DEFAULT NULL,
`c` smallint(6) unsigned DEFAULT NULL,
`d` smallint(6) unsigned DEFAULT NULL,
`e` smallint(6) unsigned DEFAULT NULL,
`f` varchar(255) DEFAULT '',
`country_id` int(10) unsigned DEFAULT NULL,
`city_id` int(10) unsigned DEFAULT NULL,
`g` smallint(6) unsigned DEFAULT NULL,
`h` smallint(6) unsigned DEFAULT NULL,
`i` smallint(6) unsigned DEFAULT NULL,
`j` smallint(6) unsigned DEFAULT NULL,
`k` smallint(6) unsigned DEFAULT NULL,
`l` varchar(3) DEFAULT NULL,
`m` varchar(3) DEFAULT NULL,
`n` text,
`o` varchar(255) DEFAULT NULL,
`p` varchar(32) DEFAULT NULL,
`q` varchar(32) DEFAULT NULL,
`r` varchar(32) DEFAULT NULL,
`s` time DEFAULT NULL,
`t` time DEFAULT NULL,
`u` text,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `country_id` (`country_id`),
KEY `city_id` (`city_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
更新2:
当我尝试 运行 查询时,在某些情况下出现错误:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
我的解决方案:
如果有人对以下内容感兴趣,这是我的最终解决方案:
gist
主要问题是,当我填写 mytable
时,其他查询卡住了,集群出现了严重的性能问题。在这个解决方案中,我创建了一个临时 table 并在 "dirty read" 模式下用数据填充它,然后我将这些数据分块复制到 mytable
所以它需要更多时间但是没有性能问题并没有卡住查询。
一个SELECT
操作returns一行你描述的长度每64纳秒是非常快的。这就是 2.3 毫秒内 36 千克的结果。您的 SELECT
查询时间似乎没有考虑将结果集传输到 MySQL 客户端。无论如何,将该性能用作与 INSERT 操作的比较会使您的期望过高。
您可以尝试在开始操作之前发出此命令。它将允许您的 SELECT
操作继续进行,减少与您的应用程序在 SELECT
的源 table 上的流量的争用。看这里https://dev.mysql.com/doc/refman/5.7/en/set-transaction.html
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
您可以尝试一个包含临时 table 的两步过程。这样做的好处是不必在 SELECT
操作的同时更新 some_table
中的所有索引。该操作将如下所示。
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
CREATE TEMPORARY TABLE insert_batch AS SELECT ... ;
INSERT INTO some_table SELECT * FROM insert_batch;
DROP TEMPORARY TABLE insert_batch;
您应该了解 InnoDB 将您的批量插入作为单个事务发布到您的 table。如果您能够以一次处理大约 500 行而不是 36K 行的方式执行此操作,那么您将拥有更多事务,但它们会更小。这通常是获得更高吞吐量的一种方式。
如果一切都失败了,这可能是一个可行的解决方案。首先,请参阅 http://mysql.rjweb.org/doc.php/deletebig#deleting_in_chunks
- 将您的更正加载到临时 table(或非复制的 MyISAM table)中。
- 遍历临时 table(使用类似于 link 的代码)。一次选择 100 行。
- 在 单独的 事务中执行 100 行的
INSERT ... SELECT ...
。
此技术可能(或可能不会)花费超过 40-50 秒的时间,但至少不太可能出现超时或死锁。
一般来说,避免运行任何持续时间超过几秒的交易。 link 是关于如何 "chunk" 冗长(和重复)操作以避免长时间交易的通用方法。
我的 mysql 集群:Ver 5.6.30-76.3-56 for debian-linux-gnu on x86_64 (Percona XtraDB Cluster (GPL), Release rel76.3, Revision aa929cb, WSREP version 25.16, wsrep_25.16)
我有一个复杂的 sql 查询,它使用以下语法将大约 36k 行插入到 table 中:
INSERT INTO `sometable` (SELECT ...);
select有点复杂但不慢(0.0023s)但是插入大约需要40-50s。当我插入行时 table 没有被使用。
我的问题是:
- 我可以加快速度吗?
- 缓慢的插入导致其他 table 的锁定问题(因为 select)
- 这个工作流程是好的还是坏的做法?还有更好的吗?
谢谢
更新:
table 架构:
CREATE TABLE `sometable` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) unsigned DEFAULT NULL,
`a` varchar(255) DEFAULT NULL,
`b` smallint(6) unsigned DEFAULT NULL,
`c` smallint(6) unsigned DEFAULT NULL,
`d` smallint(6) unsigned DEFAULT NULL,
`e` smallint(6) unsigned DEFAULT NULL,
`f` varchar(255) DEFAULT '',
`country_id` int(10) unsigned DEFAULT NULL,
`city_id` int(10) unsigned DEFAULT NULL,
`g` smallint(6) unsigned DEFAULT NULL,
`h` smallint(6) unsigned DEFAULT NULL,
`i` smallint(6) unsigned DEFAULT NULL,
`j` smallint(6) unsigned DEFAULT NULL,
`k` smallint(6) unsigned DEFAULT NULL,
`l` varchar(3) DEFAULT NULL,
`m` varchar(3) DEFAULT NULL,
`n` text,
`o` varchar(255) DEFAULT NULL,
`p` varchar(32) DEFAULT NULL,
`q` varchar(32) DEFAULT NULL,
`r` varchar(32) DEFAULT NULL,
`s` time DEFAULT NULL,
`t` time DEFAULT NULL,
`u` text,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `country_id` (`country_id`),
KEY `city_id` (`city_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
更新2:
当我尝试 运行 查询时,在某些情况下出现错误:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
我的解决方案:
如果有人对以下内容感兴趣,这是我的最终解决方案: gist
主要问题是,当我填写 mytable
时,其他查询卡住了,集群出现了严重的性能问题。在这个解决方案中,我创建了一个临时 table 并在 "dirty read" 模式下用数据填充它,然后我将这些数据分块复制到 mytable
所以它需要更多时间但是没有性能问题并没有卡住查询。
一个SELECT
操作returns一行你描述的长度每64纳秒是非常快的。这就是 2.3 毫秒内 36 千克的结果。您的 SELECT
查询时间似乎没有考虑将结果集传输到 MySQL 客户端。无论如何,将该性能用作与 INSERT 操作的比较会使您的期望过高。
您可以尝试在开始操作之前发出此命令。它将允许您的 SELECT
操作继续进行,减少与您的应用程序在 SELECT
的源 table 上的流量的争用。看这里https://dev.mysql.com/doc/refman/5.7/en/set-transaction.html
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
您可以尝试一个包含临时 table 的两步过程。这样做的好处是不必在 SELECT
操作的同时更新 some_table
中的所有索引。该操作将如下所示。
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
CREATE TEMPORARY TABLE insert_batch AS SELECT ... ;
INSERT INTO some_table SELECT * FROM insert_batch;
DROP TEMPORARY TABLE insert_batch;
您应该了解 InnoDB 将您的批量插入作为单个事务发布到您的 table。如果您能够以一次处理大约 500 行而不是 36K 行的方式执行此操作,那么您将拥有更多事务,但它们会更小。这通常是获得更高吞吐量的一种方式。
如果一切都失败了,这可能是一个可行的解决方案。首先,请参阅 http://mysql.rjweb.org/doc.php/deletebig#deleting_in_chunks
- 将您的更正加载到临时 table(或非复制的 MyISAM table)中。
- 遍历临时 table(使用类似于 link 的代码)。一次选择 100 行。
- 在 单独的 事务中执行 100 行的
INSERT ... SELECT ...
。
此技术可能(或可能不会)花费超过 40-50 秒的时间,但至少不太可能出现超时或死锁。
一般来说,避免运行任何持续时间超过几秒的交易。 link 是关于如何 "chunk" 冗长(和重复)操作以避免长时间交易的通用方法。