INSERT INTO SELECT 在集群上需要很长时间

Posted

技术标签:

【中文标题】INSERT INTO SELECT 在集群上需要很长时间【英文标题】:INSERT INTO SELECT takes long time on cluster 【发布时间】:2017-07-27 10:20:52 【问题描述】:

我的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 行插入到表中:

INSERT INTO `sometable` (SELECT ...);

选择有点复杂但并不慢(0.0023s),但插入大约需要 40-50s。插入行时该表未使用。

我的问题是:

我可以加快速度吗? 慢速插入会导致其他表出现锁定问题(因为 select) 此工作流是好还是坏的做法?有没有更好的?

谢谢

更新:

表架构:

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 时,其他查询卡住了,并且集群出现了严重的性能问题。在这个解决方案中,我创建了一个临时表并在“脏读”模式下用数据填充它,然后我将这些数据分块复制到mytable,这样会花费更多时间,但没有性能问题,也没有卡住查询。

【问题讨论】:

介意与我们分享您餐桌的CREATE TABLE 声明吗? (仅包括重要的东西,如键、索引列、索引等) 请阅读meta.***.com/a/271056,尤其是关于查询性能的部分。那么请edit你的问题。 你确定你的SELECT操作真的那么快吗?阅读:meta.***.com/a/271056 @O.Jones 我已经用架构更新了问题。是的,我尝试了很多次,选择永远不会超过 0.003 秒。 如果这个过程每次都是手动的,你为什么不直接dump数据并编辑它并重新插入呢?导入时不会花费太多时间 【参考方案1】:

SELECT 操作每 64 纳秒返回您描述的长度的一行,速度非常快。这就是 2.3 毫秒内 36 krowrows 的结果。您的SELECT 查询时间似乎没有考虑将结果集传输到 MySQL 客户端。无论如何,将该性能与 INSERT 操作进行比较会使您的期望过高。

您可以在开始操作之前尝试发出此命令。它将允许您的SELECT 操作与您的应用程序在SELECT 的源表上的流量争用更少。看这里https://dev.mysql.com/doc/refman/5.7/en/set-transaction.html

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

您可以尝试一个包含临时表的两步过程。这样做的好处是不必在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 将您的一批插入作为单个事务发布到您的表中。如果您可以一次处理大约 500 行而不是 36K 行,那么您将拥有更多事务,但它们会更小。这通常是获得更高吞吐量的一种方法。

【讨论】:

谢谢,这对我帮助很大。基于此,我已经解决了这个问题。它不是那么快,但在运行该过程时没有性能问题。 不客气。如果您有时间,您能否发表评论,说明您做了什么以及它对性能的改善程度? 我已经用我的解决方案更新了这个问题。如果您对此有任何意见,我将不胜感激。【参考方案2】:

如果一切都失败了,这可能是一个可行的解决方案。一、看http://mysql.rjweb.org/doc.php/deletebig#deleting_in_chunks

    将您的更正加载到临时表(或非复制的 MyISAM 表)中。 遍历临时表(使用与该链接类似的代码)。一次选择 100 行。 在单独事务中执行 100 行的INSERT ... SELECT ...

这种技术可能(或可能不会)花费超过 40-50 秒的时间,但至少不太可能超时或死锁。

一般来说,避免运行任何持续时间超过几秒的事务。这个链接对于如何“分块”冗长(和重复)的操作以避免长事务有些通用。

【讨论】:

谢谢你,我终于使用了块的想法。

以上是关于INSERT INTO SELECT 在集群上需要很长时间的主要内容,如果未能解决你的问题,请参考以下文章

select into 与 insert into select

select into 与 insert into select

insert into linksvr select VS insert into from linksvr

insert into的用法

SELECT INTO 和 INSERT INTO SELECT比较

SELECT INTO 和 INSERT INTO SELECT 两种表复制语句