InnoDB导入性能


10

我在批量导入一个由大约1000万行(或7GB)组成的相当大的InnoDB表时遇到了麻烦(对我来说,这是迄今为止我使用过的最大表)。

我做了一些研究来提高Inno的导入速度,目前我的设置如下所示:

/etc/mysql/my.cnf/
[...]
innodb_buffer_pool_size = 7446915072 # ~90% of memory
innodb_read_io_threads = 64
innodb_write_io_threads = 64
innodb_io_capacity = 5000
innodb_thread_concurrency=0
innodb_doublewrite = 0
innodb_log_file_size = 1G
log-bin = ""
innodb_autoinc_lock_mode = 2
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit=2
innodb_buffer_pool_instances=8


import is done via bash script, here is the mysql code:
SET GLOBAL sync_binlog = 1;
SET sql_log_bin = 0;
SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET AUTOCOMMIT = 0;
SET SESSION tx_isolation='READ-UNCOMMITTED';
LOAD DATA LOCAL INFILE '$filepath' INTO TABLE monster
COMMIT;

数据以CSV文件形式提供。
目前,我使用较小的“测试转储”测试我的设置,每个转储分别有200万,300万,…行,并用于time import_script.sh比较性能。

缺点是我只能获得整体运行时间,因此我必须等待完整的导入完成才能获得结果。

到目前为止,我的结果是:

  • 1万行:<1秒
  • 10万行:10秒
  • 30万行:40秒
  • 200万行:18分钟
  • 300万行:26分钟
  • 400万行:(2小时后取消)

似乎没有“食谱”解决方案,因此必须自己找出最佳设置组合。
除了提出有关更改设置方面的建议外,我还将非常感谢您提供更多信息,以了解如何更好地对导入过程进行基准测试/获得更多信息,了解正在发生的事情以及可能出现的瓶颈。
我试图阅读有关要更改的设置的文档,但随后我又不知道有任何副作用,甚至可能因选择错误的值而降低性能。

目前,我想尝试从聊天中获得的建议,以便MyISAM在导入和更改表引擎之后使用。
我想尝试一下,但目前我的DROP TABLE查询还需要几个小时才能完成。(这似乎是我的设置低于最佳指标的另一个指标)。

附加信息:
我当前正在使用的计算机具有8GB的RAM和一个带5400RPM的固态混合硬盘。
虽然我们还打算从相关表中删除过时的数据,但我仍然需要快速导入到
a)automatic data cleanup feature开发过程中进行测试,以及
b)万一服务器崩溃,我们希望使用第二台服务器作为替代(需要升级)最新数据,最后一次导入耗时超过24小时)

mysql> SHOW CREATE TABLE monster\G
*************************** 1. row ***************************
       Table: monster
Create Table: CREATE TABLE `monster` (
  `monster_id` int(11) NOT NULL AUTO_INCREMENT,
  `ext_monster_id` int(11) NOT NULL DEFAULT '0',
  `some_id` int(11) NOT NULL DEFAULT '0',
  `email` varchar(250) NOT NULL,
  `name` varchar(100) NOT NULL,
  `address` varchar(100) NOT NULL,
  `postcode` varchar(20) NOT NULL,
  `city` varchar(100) NOT NULL,
  `country` int(11) NOT NULL DEFAULT '0',
  `address_hash` varchar(250) NOT NULL,
  `lon` float(10,6) NOT NULL,
  `lat` float(10,6) NOT NULL,
  `ip_address` varchar(40) NOT NULL,
  `cookie` int(11) NOT NULL DEFAULT '0',
  `party_id` int(11) NOT NULL,
  `status` int(11) NOT NULL DEFAULT '2',
  `creation_date` datetime NOT NULL,
  `someflag` tinyint(1) NOT NULL DEFAULT '0',
  `someflag2` tinyint(4) NOT NULL,
  `upload_id` int(11) NOT NULL DEFAULT '0',
  `news1` tinyint(4) NOT NULL DEFAULT '0',
  `news2` tinyint(4) NOT NULL,
  `someother_id` int(11) NOT NULL DEFAULT '0',
  `note` varchar(2500) NOT NULL,
  `referer` text NOT NULL,
  `subscription` int(11) DEFAULT '0',
  `hash` varchar(32) DEFAULT NULL,
  `thumbs1` int(11) NOT NULL DEFAULT '0',
  `thumbs2` int(11) NOT NULL DEFAULT '0',
  `thumbs3` int(11) NOT NULL DEFAULT '0',
  `neighbours` tinyint(4) NOT NULL DEFAULT '0',
  `relevance` int(11) NOT NULL,
  PRIMARY KEY (`monster_id`),
  KEY `party_id` (`party_id`),
  KEY `creation_date` (`creation_date`),
  KEY `email` (`email`(4)),
  KEY `hash` (`hash`(8)),
  KEY `address_hash` (`address_hash`(8)),
  KEY `thumbs3` (`thumbs3`),
  KEY `ext_monster_id` (`ext_monster_id`),
  KEY `status` (`status`),
  KEY `note` (`note`(4)),
  KEY `postcode` (`postcode`),
  KEY `some_id` (`some_id`),
  KEY `cookie` (`cookie`),
  KEY `party_id_2` (`party_id`,`status`)
) ENGINE=InnoDB AUTO_INCREMENT=13763891 DEFAULT CHARSET=utf8

2
您是否尝试过使用较小的导入,例如1万或10万行?
ypercubeᵀᴹ

1
请运行SHOW CREATE TABLE yourtable\G以向我们展示该1000万行表的表结构。
RolandoMySQLDBA 2014年

@RolandoMySQLDBA,所以我做到了(字段名称模糊)
nuala

通过禁用双重写入缓冲区(innodb_doublewrite = 0),您的MySQL安装将不会崩溃安全:如果发生电源故障(而非MySQL崩溃),则数据可能会被无提示破坏。
jfg956

Answers:


13

首先,当您向InnoDB表中放入数百万行时,您需要知道对InnoDB所做的事情。让我们看一下InnoDB体系结构。

InnoDB架构

左上角有一个InnoDB缓冲池的图示。注意,其中有一部分专门用于插入缓冲区。那是做什么的?可以将对二级索引的更改从缓冲池迁移到系统表空间(即ibdata1)内的插入缓冲区。默认情况下,innodb_change_buffer_max_size设置为25。这意味着最多25%的缓冲池可用于处理二级索引。

对于您的情况,您有6.935 GB的InnoDB缓冲池。最大1.734 GB的空间将用于处理二级索引。

现在,看看您的桌子。您有13个二级索引。您处理的每一行都必须生成一个辅助索引条目,并将其与该行的主键耦合,然后将它们作为一对从缓冲池中的插入缓冲区发送到ibdata1中的插入缓冲区中。每行发生13次。将其乘以1000万,您几乎可以感觉到瓶颈的到来。

不要忘记,在单个事务中导入1000万行将把所有内容堆积到一个回滚段中,并填满ibdata1中的UNDO空间。

建议

建议#1

对于导入这个相当大的表,我的第一个建议是

  • 删除所有非唯一索引
  • 导入数据
  • 创建所有非唯一索引

建议#2

摆脱重复的索引。就你而言

KEY `party_id` (`party_id`),
KEY `party_id_2` (`party_id`,`status`)

这两个索引均以开头party_id,您可以将二级索引处理至少增加7.6%,以从13中删除一个索引。您最终需要运行

ALTER TABLE monster DROP INDEX party_id;

建议#3

摆脱不使用的索引。查看您的应用程序代码,看看您的查询是否使用所有索引。您可能需要研究pt-index-usage来建议未使用哪些索引。

建议#4

您应该将innodb_log_buffer_size增加到64M,因为默认值为8M。更大的日志缓冲区可能会提高InnoDB写入I / O性能。

结语

放置前两个建议,请执行以下操作:

  • 删除13个非唯一索引
  • 导入数据
  • 除了创建的所有非唯一索引party_id的索引

也许以下可能有帮助

CREATE TABLE monster_new LIKE monster;
ALTER TABLE monster_new
  DROP INDEX `party_id`,
  DROP INDEX `creation_date`,
  DROP INDEX `email`,
  DROP INDEX `hash`,
  DROP INDEX `address_hash`,
  DROP INDEX `thumbs3`,
  DROP INDEX `ext_monster_id`,
  DROP INDEX `status`,
  DROP INDEX `note`,
  DROP INDEX `postcode`,
  DROP INDEX `some_id`,
  DROP INDEX `cookie`,
  DROP INDEX `party_id_2`;
ALTER TABLE monster RENAME monster_old;
ALTER TABLE monster_new RENAME monster;

将数据导入monster。然后,运行这个

ALTER TABLE monster
  ADD INDEX `creation_date`,
  ADD INDEX `email` (`email`(4)),
  ADD INDEX `hash` (`hash`(8)),
  ADD INDEX `address_hash` (`address_hash`(8)),
  ADD INDEX `thumbs3` (`thumbs3`),
  ADD INDEX `ext_monster_id` (`ext_monster_id`),
  ADD INDEX `status` (`status`),
  ADD INDEX `note` (`note`(4)),
  ADD INDEX `postcode` (`postcode`),
  ADD INDEX `some_id` (`some_id`),
  ADD INDEX `cookie` (`cookie`),
  ADD INDEX `party_id_2` (`party_id`,`status`);

试试看 !!!

替代

您可以创建一个monster_csv没有索引的称为MyISAM表的表,并执行以下操作:

CREATE TABLE monster_csv ENGINE=MyISAM AS SELECT * FROM monster WHERE 1=2;
ALTER TABLE monster RENAME monster_old;
CREATE TABLE monster LIKE monster_old;
ALTER TABLE monster DROP INDEX `party_id`;

将数据导入monster_csv。然后,使用mysqldump创建另一个导入

mysqldump -t -uroot -p mydb monster_csv | sed 's/monster_csv/monster/g' > data.sql

mysqldump文件data.sql将扩展INSERT命令,一次导入10,000-20,000行。

现在,只需加载mysqldump

mysql -uroot -p mydb < data.sql

最后,摆脱MyISAM表

DROP TABLE monster_csv;

我什至都不知道所有这些键(这不是我的设计),但是您的解释似乎很有说服力。对于今天来说,再次尝试已为时已晚,但我看到一些很好的建议,明天再尝试。会及时通知您!<3
nuala

1
monster当InnoDB表上没有键时,我在不到20分钟的时间内导入了完整的数据库(不仅是表)。添加密钥大约需要花费时间。再过20分钟 在这种情况下,我会说这几乎解决了我的问题。非常感谢你!
nuala 2014年

8

我想写评论(因为这不是确定的答案),但是它太长了:

我将为您提供一些广泛的建议,如果您愿意,我们可以详细介绍每个建议:

  • 降低耐用性(您已经做过一些)。最新版本甚至允许执行更多操作。您可以最大程度地禁用双重写入缓冲区,因为对于导入而言,损坏不是问题。
  • 通过以下方式增加缓冲:增加事务日志大小并增加可用的缓冲池大小。监视事务日志文件的使用情况和检查点。不要担心大量的进口原木。
  • 避免大量交易-您的回滚将充满不需要的数据。这可能是您最大的问题。
  • SQL将成为瓶颈,避免SQL开销(handlersocket,memcached)和/或同时与多个线程并发加载。并发必须达到一个最佳点,不要太多,也不要太少。
  • 以主键顺序分段加载数据可能是一个麻烦
  • 如果IO是您的瓶颈,并且CPU和内存不会使其变慢,请测试InnoDB压缩
  • 之后尝试创建辅助密钥(在某些情况下更快),不要加载索引数据-DISABLE KEYS不会影响InnoDB。如果不是,请监视您的插入缓冲区(可能超过一半的缓冲池)。
  • 更改或禁用校验和算法-可能不是您的问题,但它成为高端闪存卡的瓶颈。
  • 不得已:监视服务器以找到当前的瓶颈并尝试缓解(InnoDB在此方面非常灵活)。

请记住,其中一些对于非导入来说是不安全的或不可取的(正常操作)。


非常感谢你!我喜欢先尝试Rolando关于索引想法,但我猜这种“事务回滚”的问题仍然会成为问题。您能详细说明一下吗?我想我想在导入期间尽可能多地禁用此功能,而只是在投入生产时才重新启用〜我认为...
nuala 2014年

1
罗兰多的建议是我的观点7。避免回滚开销很简单SET SESSION tx_isolation='READ-UNCOMMITTED';(仅当您并行导入多个线程时才有用)和@ypercube有关批处理插入的注释的组合很容易。这里有一个完整的例子:mysqlperformanceblog.com/2008/07/03/...确保您在最新版本的InnoDB中获得的所有功能优势:mysqlperformanceblog.com/2011/01/07/...
jynus

1
我给人的总体印象是,应该避免导入较小的卡盘,而是进行“全包”操作,但是我看到多线程可以打开某些可能性。猜猜这是非常具体的情况。但是,我接受了Rolando的回答,因为仅此一项调整(您的#7)就帮助我在不到1小时的时间内完成了全部导入,但是您的清单绝对不是一文不值,我想它将随着数据库的增长而很快地用作参考。吓到我了:)
nuala 2014年

我同意@yoshi。在故障排除和性能改进方面,您的答案更加全面。+1
RolandoMySQLDBA 2014年

3

到目前为止,大多数最佳提示都已给出,但是对于最佳提示没有很多解释。我会提供更多细节。

首先,延迟索引的创建是一个好方法,其他响应中有足够的细节。我不会再说了。

较大的InnoDB日志文件将为您提供很多帮助(如果您使用的是MySQL 5.6,因为在MySQL 5.5中无法增加它)。您要插入7 GB的数据,我建议日志总大小至少为8 GB(保留innodb_log_files_in_group默认值(2),增大为innodb_log_file_size4 GB)。这8 GB并不准确:它至少应为REDO日志中的导入大小,并且可能是该大小的两倍或四倍。InnoDB日志大小增加的原因是,当日志几乎将满时,InnoDB将开始积极地将其缓冲池刷新到磁盘上,以避免日志被填满(当日志已满时,InnoDB无法执行任何数据库写操作,直到一些缓冲池的页面将写入磁盘)。

较大的InnoDB日志文件将为您提供帮助,但您还应该以主键顺序插入(在插入文件之前对文件进行排序)。如果按主键顺序插入,InnoDB将填充一页,然后填充另一页,依此类推。如果不按主键顺序插入,则下一次插入可能会在页面已满的情况下结束,并会导致“页面拆分”。对于InnoDB,此页面拆分将很昂贵,并且会减慢导入速度。

您已经拥有一个与RAM允许的大小一样大的缓冲池,并且如果表不适合其中,那么除了购买更多RAM以外,您无能为力。但是您的表可以容纳在缓冲池中,但大于缓冲池的75%,因此您可以innodb_max_dirty_pages_pct在导入期间尝试将其增加到85或95(默认值为75)。当脏页百分比达到此限制时,此配置参数告诉InnoDB开始主动刷新缓冲池。通过增加此参数(如果您对数据大小感到幸运),可以避免在导入期间使用过多的IO,并将这些IO推迟到以后。

也许(这是一个猜测)在许多小交易中导入数据会有所帮助。我不确切知道REDO日志的构建方式,但是如果在事务进行过程中将其缓存在RAM(需要太多RAM的情况下为磁盘)中,则可能会导致不必要的IO。您可以尝试:将文件排序后,将其拆分为多个块(尝试使用16 MB和其他大小),然后逐一导入。这也将允许您控制导入进度。如果在导入时不希望数据对其他阅读器部分可见,则可以使用其他表名进行导入,稍后创建索引,然后重命名该表。

关于您的混合SSD / 5400RPM磁盘,我不知道这些磁盘以及如何对其进行优化。5400RPM对于数据库来说看起来很慢,但是SSD可以避免这种情况。也许您正在通过顺序写入REDO日志来填充磁盘的SSD部分,但SSD会影响性能。我不知道。

您不应该尝试(或注意)的坏提示:不要使用多线程:要避免在InnoDB中进行页面拆分,很难进行优化。如果要使用多线程,请插入不同的表(或同一表的不同分区)。

如果您考虑使用多线程,则可能有一台多插槽(NUMA)计算机。在这种情况下,请确保避免MySQL交换混乱问题

如果您使用的是MySQL 5.5,请升级到MySQL 5.6:它可以选择增加REDO日志大小,并具有更好的缓冲池刷新算法。

祝您进口顺利。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.