【问题标题】:Specific MySQL bulk insertion performance tuning具体的 MySQL 批量插入性能调优
【发布时间】:2011-07-04 08:47:22
【问题描述】:

我知道这个问题已经被问了一遍又一遍。但是,对于一个非常具体的场景,这是一个非常具体的问题。希望你能帮助我。

我运行一个日志数据库,大约有 10 个表。存储实际日志条目的主表有大约 30 个字段,其中 5 个是可搜索的。我想说数据库最近变得中等大小,因为我们在该表中达到了 2 亿个条目。其他表存储公共数据,其中最大的一个有 4 个字段,全部可搜索,有近 100 万个条目。所有其他表每个都包含少于 10 万条记录。

插入物呈尖峰状。我每天凌晨 2 点将前一天的日志保存在(格式很差的)csv 文件中,直到早上 8 点我才能将它们(大约 20 个文件,每个文件 10 万行)插入到数据库中。然后我在工作日得到的选择很少(可能每天大约 1000 个)。然后冲洗并重复。

SELECT 查询非常简单,因为它们主要由一两个连接和一两个 GROUP BY 语句组成。搜索这个数据库的人想要立即得到结果,所以我在主表中有 5 个多列索引,这有助于我进行精确搜索,目前 SELECT 性能相当不错。到目前为止,没有任何查询花费超过 0.1 秒。有一些报告,但生成这些报告大约需要 10 秒,这是可以接受的。

目前,我编写了一个 C 程序,用于从 CSV 文件中读取数据,对其进行清理,然后在每个 INSERT 查询中分批插入 1000 行。这些 INSERT 并不是完全愚蠢的,因为我需要获取公共数据,看看它是否已经在其他表上,如果没有就插入,如果有就缓存它。它还以每秒插入多少条记录的形式为我提供性能数据。这个程序非常快,并且在不将数据发送到数据库的情况下,我每秒可以获得大约 10 万行。当然,这个程序和数据库位于同一台物理计算机上。

现在,我每天获得的数据呈线性增长,INSERT 的性能呈对数下降。昨天的数据插入需要 5 个半小时,每秒插入大约 400 行。

我通过将具有不同配置的前 100 万行插入到一个空数据库中获得了一些基准数据,这几乎就是我得到的:

MyISAM 表:从每秒 1500 行开始,到插入第 1 百万行时以对数方式减少到每秒约 700 行 InnoDB 表:与 MyISAM 相同,仅快 100 行/秒左右 在主表上禁用所有索引的 InnoDB:从每秒 2100 行开始,下降到每秒 1000 行。 InnoDB 带索引,文件系统挂载数据写回 (ext3):与 InnoDB 相同,只是速度稍快但几乎没有明显的加快。

innodb_buffer_pool_size 设置为 1000MB

避免创建索引不是一种选择,但很明显它对性能有很大影响。但是,我需要更快的插入。数据显示,随着数据库的增长,插入会花费更长的时间,所以随着我每天获取的数据越来越大,我需要在插入性能上实现巨大的飞跃。如果我能把它提高到每秒 10000 次或更多,那就太好了。

系统监视器告诉我,我的主要资源消耗是磁盘 I/O,插入时几乎达到 100%。因此,我需要一种超快速的方式来插入数据。我的理论极限是 SATA 总线的极限,但这还很遥远。内存使用率似乎没有那么高,大约 20%(或者 MySQL 没有正确使用内存)

为了实现这一点,可以在几天内重新创建数据库,然后从阅读器应用程序进行热交换,可以更改 OS 和 MySQL 中的任何设置,如果添加内存是可以接受的必需的。如有必要,甚至可以更改数据库结构。

所以我对这里的想法非常开放。有人知道什么可以帮助我吗?

编辑:我目前正在考虑在 MEMORY 表中插入新行,然后执行 SELECT INTO 实际表。希望它只会在插入所有行后更新和刷新一次索引。我会在星期一试试这个。有没有人尝试过这样的事情?

【问题讨论】:

标签: mysql database linux insert performance


【解决方案1】:

在做了一整天的小事之后,我建立了一个巨大的东西。最重要的是,我将插入性能提高了大约 8 倍,达到每秒近 10000 条记录。

这是我做过的事情:

  1. 重写加载程序。我说它是用 C 编写的,但实际上它是用 C++ 编写的。将字符串更改为 char*、使用 mmap 的 fstream 以及其他类似的东西,我几乎将性能提高了一倍。 (而且很多人仍然声称 C++ 与 C 一样快,甚至更快。我什至不想在 C#/Java 中尝试这个)

  2. 我找到了这个页面: http://kevin.vanzonneveld.net/techblog/article/improve_mysql_insert_performance/ 这是一个很好的资源(我不隶属于他们),它解释了我要尝试的几乎所有东西,以及所有不同的结果。几乎,唯一可以提高插入性能的方法是使用 LOAD DATA INFILE。调整我的表格结构,这样我就可以像这样插入几乎翻了两番!我的插入效果。

  3. 我使用 ON DUPLICATE KEY UPDATE 中的复杂表达式将无法使用 LOAD DATA INFILE 执行的插入重写为批量插入(每个插入命令几行),而不是对每一行执行 SELECT/INSERT。这也带来了非常好的性能提升。这也需要对表结构进行一些修改。

  4. 在重新创建已经超过 20 亿行的数据库时,请创建在没有索引的情况下获得 LOAD DATA INFILE 插入的表,并在完成后重新创建它们。我所有的基准测试都表明插入没有索引的时间加上创建它们的时间比插入有索引的表的时间短。差异不是很大,但很明显(大约快 1.2 倍)。我认为 B-trees 也会以这种方式更好地平衡。

  5. 使用 MyISAM。我之前的基准测试并没有那么确定,但是当使用 LOAD DATA INFILE 时,InnoDB 每次都会失败。在本地测试,我在 MyISAM/no 索引中获得了大约 16000 条记录/秒,在 MyISAM/indexes 中获得了 12000 条记录/秒,在 InnoDB/无索引中获得了 9000 条记录/秒,在 InnoDB/indexes 中获得了大约 7500 条记录。 MySQL 版本为 5.1.47。

  6. 对于 LOAD DATA INFILE 的文件,在 tmpfs 挂载的分区中创建它们。这也是一个巨大的性能提升,特别是因为您需要写入文件并将其刷新到磁盘,以便 MySQL 可以读取它。如果这个 tmpfs 是不可能的,那么应该可以使用命名管道来做到这一点。

经验教训:当 MySQL 速度较慢时,您很可能通过更改代码而不是获得更强大的硬件来做更多事情。

祝你好运,感谢大家的帮助。

【讨论】:

    【解决方案2】:

    我会尝试增加 innodb 缓冲池大小,看看会发生什么。对于 Innodb,我还会使用 innodb_flush_log_at_trx_commit=0(或 =2)禁用永久刷新。默认设置为 1,这是写入密集型工作负载的瓶颈。 0 或 2 将在两次刷新之间延迟 1 秒。您还可以使用事务处理更大的批次(如果您不明确使用事务,那么每个插入都是它自己的事务)。

    如前所述,预排序输入(按主键)可以通过消除页面加载的随机性来帮助减少缓冲池中的数据量。

    以上都是innodb相关的。

    【讨论】:

      【解决方案3】:

      6.5 小时内 200 万行?
      您存储的数据集有多大?

      我使用以下粗略计算得出一个有用的数字:
      假设1 单个垃圾磁盘每秒吞下35 mb,您应该能够在该时间范围内写入 (35 * 6,5 * 3600) = ~800 gb。向后计算(800 gb / 2 mrows),平均行大小为400 kb。

      如果这些数字看起来正确,您需要加强硬件以提高速度。如果它们完全关闭,则可能存在其他问题。

      另外,请查看 ServerFault 上的 comparisons of disk i/o for a dedicated MySQL server,了解测量 I/O 的方法。

      这里有一些随机建议(以防您怀疑其他问题)

      • 确保在加载过程中消除所有逐行操作
      • 如果最终存储了大部分 csv 数据,请考虑将bulk loading 放入中间表中,并使用基于集合的处理来处理数据库内的数据。
      • 如果大部分数据被丢弃,请考虑将参考表移动/缓存到数据库之外,以便能够过滤 C 代码中的 csv 数据
      • MySQL 没有散列连接,但依赖于索引循环。确保那些其他表具有适当的索引
      • 尝试对数据库外部的数据进行预排序,以匹配进程中使用的其他一些表的索引(以增加相关数据不会从缓存中清除的可能性)
      • 阅读partitioning,看看您是否可以用智能分区方案替换一些索引,而不是维护所有这些索引。

      已编辑
      修正后的计算 (400kb)

      【讨论】:

      • 800gb/2m 行平均每行 400Kb,而不是 400 字节。因为我的行大约是 400 字节,在你描述的磁盘上进行原始复制,我应该能够比我现在得到的速度快 1000 倍,所以我的问题不是磁盘速度,而是 MySQL 正在做我写的每个字节都有 1000 字节的 I/O,这就是我需要解决的问题。正如我在另一条评论中提到的,除了原始速度之外,我还需要我的行插入不会随着数据库的增长而变慢。
      • @oscar,你是对的。我的计算失败了。如果您能告诉我们更多关于表格、您正在加载的数据以及您对源数据执行的逻辑类型的信息,我可能会为您提供更多帮助。
      【解决方案4】:

      在提到磁盘 I/O 时,您会遇到它。如果您的磁盘被插入最多,那么除非您升级,否则您将不会获得任何更快的速度。您没有提到进行磁盘升级是否可以接受,但我会考虑使用 SCSI 或基于闪存的磁盘。即使您没有达到 SATA 的总线限制,但您的磁盘肯定是瓶颈。

      【讨论】:

      • 不幸的是,磁盘吞吐量并不是最容易升级的。但是,我更担心随着数据库的增长行插入速度会降低。不管我的磁盘有多快,如果我保证每次插入行都会变慢,那么无论它的速度如何,我都会超过任何总线。我宁愿有 4000 行/秒,而与数据库大小无关,而不是 10000 行/秒,在说 10 亿行之后低于 1000 行。
      猜你喜欢
      • 2013-01-20
      • 2016-08-15
      • 2016-09-29
      • 1970-01-01
      • 2014-09-12
      • 2013-07-12
      • 2011-02-28
      • 2014-09-17
      • 2012-01-28
      相关资源
      最近更新 更多