【问题标题】:Optimizing MySQL InnoDB insert through PHP通过 PHP 优化 MySQL InnoDB 插入
【发布时间】:2015-08-28 13:13:33
【问题描述】:

我有一个 Cronjob 脚本,用 PHP 编写,具有以下要求:

  • 第 1 步(数据库服务器 1): 从多个表中获取一些数据(我们这里有很多数据)
  • 第 2 步(应用服务器):执行一些计算
  • 第 3 步(数据库服务器 2): 计算后,将该数据插入另一个数据库(MySQL)/表(InnoDB)以进行报告。该表包含 97 列,实际上是不同的比率,无法进一步标准化。这是不同的物理数据库服务器,只有一个数据库。

脚本在开发过程中运行良好,但在生产过程中,第 1 步返回了大约 5000 万条记录。结果,很明显,脚本运行了大约 4 天,然后失败了。 (粗略估计,以目前的速度,大约需要 171 天才能完成)

请注意,我们使用的是准备好的语句,第 1 步是一次获取 1000 条记录中的数据。

到目前为止我们做了什么

优化步骤1:插入和删除所有索引中的多个值

一些测试显示插入(上面的第 3 步)花费了最长的时间(超过 95% 的时间)。为了优化,经过一番谷歌搜索,我们从表中删除了所有索引,而不是一个插入查询/行,我们没有一个插入查询/100 行。这给了我们更快的插入速度,但根据粗略估计,运行一次 cron 需要 90 天,而且我们需要每个月运行一次,因为每个月都会有新数据可用。

优化步骤2,不写DB,写csv文件,然后用linux命令导入mysql。

这一步似乎不起作用。在 CSV 文件中写入 30000 行需要 16 分钟,我们仍然需要在 MySQL 中导入该 CSV 文件。我们为所有写入操作提供了单个文件处理程序。

当前状态

我现在似乎不知道还能做什么。一些关键要求:

  • 脚本需要插入大约 50,000,000 条记录(会随着时间增加)
  • 每条记录有 97 列,我们可以跳过一些,但至少 85 列。
  • 根据输入,我们可以将脚本分成三个不同的 cron 以在三个不同的服务器上运行,但插入必须在一个数据库服务器(主服务器)上完成,因此不确定它是否会有所帮助。

但是:

  • 我们愿意更改数据库/存储引擎(包括 NoSQL)
  • 在生产环境中,我们可以有多个数据库服务器,但插入只能在主服务器上完成。所有读取操作都可以定向到从属,这是最小的和偶尔的(只是为了生成报告)

问题

我不需要任何描述性的答案,但简而言之,有人可以提出可能的解决方案。我只需要一些优化提示,我会做剩余的研发。

我们对一切都开放,更改数据库/存储引擎、服务器优化/多台服务器(数据库和应用程序)、更改编程语言或任何满足上述要求的最佳配置。

最终预期,cron 必须在最多 24 小时内完成。

在优化步骤 2 中编辑

为了进一步了解生成 csv 需要时间的原因,我创建了我的代码的副本,其中仅包含必要的代码。该代码存在于 git https://github.com/kapilsharma/xz

实验输出文件为https://github.com/kapilsharma/xz/blob/master/csv/output500000_batch5000.txt

如果你检查上面的文件,我一次插入 500000 条记录并从数据库中获取 5000 条记录,使循环运行 100 次。第一个循环花费的时间是 0.25982284545898 秒,但第 100 个循环是 3.9140808582306。我假设它是因为系统资源和/或 csv 文件的文件大小。在这种情况下,它变成了更多的编程问题,然后是数据库优化。不过,有人可以提出为什么在下一个循环中需要更多时间吗?

如果需要,除了生成虚拟数据库的 csv 文件和 sql 文件之外的整个代码都会提交,因为这些文件非常大。然而,它们可以很容易地用代码生成。

【问题讨论】:

  • 什么实际上不适用于 csv 方法? LOAD DATA INFILE 查询只是失败还是仍然太慢?我了解在您的情况下生成 csv 需要时间。但这种方法通常会大大促进批量插入
  • 感谢@Mat 的回复。 30000 条记录耗时 16 分钟。以同样的速度,50000000 条记录将需要 18.5 天,这也仅用于写入 CSV 文件。这就是为什么我没有在这条路上尝试太多。您认为这是给定场景中的最佳解决方案吗?有没有机会在 24 小时内获得整个 cron?
  • 首先,让我们关注 30K CSV 行花费了这么长时间。只是 LOAD DATA 语句需要 16 分钟吗?或者您是否包括创建 30K 行?请提供SHOW CREATE TABLE。 (由此,我们也可以进一步讨论索引。)服务器 2 上的SHOW VARIABLES LIKE '%buffer%';
  • 感谢@RickJames,我用代码更新了问题,并在一次使用虚拟记录的实验中得到了结果。
  • 时间线性增长,好像第一个测试做了 N 行,第二个做了 2N 行,第三个做了 3N 行,等等。我没有发现构建行数以确认或拒绝的代码那个观察。

标签: php mysql optimization query-optimization innodb


【解决方案1】:

这是一个非常广泛的问题。我首先要弄清楚“插入”语句的瓶颈是什么。运行代码,并使用您的操作系统为您提供的任何内容来查看机器在做什么。

如果瓶颈是CPU,你需要找到最慢的部分并加速它。鉴于您的示例代码,这不太可能,但可能。

如果瓶颈是 I/O 或内存,您几乎可以肯定需要更好的硬件,或进行基本的重新设计。

重新设计它的明显方法是找到一种仅处理 50M 记录中的增量的方法。例如,如果您可以在记录更改时写入审计表,那么您的 cron 作业可以查看该审计表并挑选出自上次批处理运行以来修改的所有数据。

【讨论】:

    【解决方案2】:

    我在 CakePHP 上有一个邮件程序 cron 作业,它仅在 600 行获取并发送电子邮件给注册用户时失败。它甚至无法在批处理操作中执行这项工作。我们最终选择了mandrill,从那以后一切顺利。

    我建议(考虑到接触生产中的遗留系统是个坏主意):

    • 考虑在 golang 或 node.js 中安排一个 mirco 解决方案 performance benchmarks,因为涉及到数据库交互—— 你对这些都很好。让这个微型解决方案执行 cron 作业。 (获取 + 计算)
    • 来自 NoSQL 的报告将 具有挑战性,因此您应该尝试使用可用的服务,例如 Google Big Query。让 cron 作业存储数据到 google big 查询,即使在 生成报告。

    • 将每一行插入到原始数据库服务器 1 中,设置一个消息传递机制,该机制在每次插入时执行 cron 作业的操作(某种触发器)并将其存储到您的报告服务器中。您可以使用的可能服务是:Google PubSubPusher。我认为每次插入时间消耗会少得多。 (您也可以使用异步服务设置来完成存储到报告数据库中的任务)。

    希望这会有所帮助。

    【讨论】:

      【解决方案3】:

      使用 OFFSETLIMIT 遍历一张桌子是 O(N*N),这比您想要或预期的要慢得多。

      相反,请在表格中“记住你离开的地方”。最好使用PRIMARY KEY。由于 id 看起来像 AUTO_INCREMENT 没有空格,因此代码很简单。 My blog 讨论了这一点(以及更复杂的分块技术)。

      它不会是整整 100 (500K/5K) 倍,但它会明显更快。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-20
        • 2013-06-02
        • 1970-01-01
        • 1970-01-01
        • 2018-04-08
        相关资源
        最近更新 更多