【问题标题】:What's the most efficient way to work through a large result in Perl DBI?在 Perl DBI 中处理大型结果的最有效方法是什么?
【发布时间】:2014-08-21 19:00:42
【问题描述】:

我有一张有几百万行的表。目前,我正在通过这样做一次处理 10,000 个:

for (my $ival = 0; $ival < $c_count; $ival += 10000)
{
    my %record;

    my $qry = $dbh->prepare
            ( "select * from big_table where address not like '%-XX%' limit $ival, 10000");
    $qry->execute();

    $qry->bind_columns( \(@record{ @{$qry->{NAME_lc} } } ) );

    while (my $record = $qry->fetch){
        this_is_where_the_magic_happens($record)
    }
}

我进行了一些基准测试,发现准备/执行部分虽然最初很快,但在多个 10,000 行批处理后会显着减慢。这是一种愚蠢的写法吗?我只知道如果我尝试一次性选择所有内容,此查询将永远存在。

以下是日志中的一些 sn-ps:

(Thu Aug 21 12:51:59 2014) Processing records 0 to 10000
SQL Select =>  1 wallclock secs ( 0.01 usr +  0.00 sys =  0.01 CPU)
(Thu Aug 21 12:52:13 2014) Processing records 10000 to 20000
SQL Select =>  1 wallclock secs ( 0.00 usr +  0.00 sys =  0.00 CPU)
(Thu Aug 21 12:52:25 2014) Processing records 20000 to 30000
SQL Select =>  2 wallclock secs ( 0.00 usr +  0.00 sys =  0.00 CPU)
(Thu Aug 21 12:52:40 2014) Processing records 30000 to 40000
SQL Select =>  5 wallclock secs ( 0.00 usr +  0.00 sys =  0.00 CPU)
(Thu Aug 21 12:52:57 2014) Processing records 40000 to 50000
SQL Select => 13 wallclock secs ( 0.01 usr +  0.00 sys =  0.01 CPU)
...
(Thu Aug 21 14:33:19 2014) Processing records 650000 to 660000
SQL Select => 134 wallclock secs ( 0.01 usr +  0.00 sys =  0.01 CPU)
(Thu Aug 21 14:35:50 2014) Processing records 660000 to 670000
SQL Select => 138 wallclock secs ( 0.00 usr +  0.00 sys =  0.00 CPU)
(Thu Aug 21 14:38:27 2014) Processing records 670000 to 680000
SQL Select => 137 wallclock secs ( 0.00 usr +  0.00 sys =  0.00 CPU)
(Thu Aug 21 14:41:00 2014) Processing records 680000 to 690000
SQL Select => 134 wallclock secs ( 0.00 usr +  0.00 sys =  0.00 CPU)

做其他方式会更快吗?我应该删除“where”子句并在循环中丢弃我不想要的结果吗?

感谢您的帮助。

【问题讨论】:

    标签: mysql sql perl dbi


    【解决方案1】:

    其他人提出了有用的建议。我只是补充一些想到的想法......

    • 首先,看看我以前的但仍然非常相关的Advanced DBI Tutorial。特别是第 80 页,它解决了对大型结果集的分页问题,​​这与您的情况相似。它还包括分析和fetchrow_hashrefbind_columns

    • 考虑创建一个带有自动递增字段的临时表,通过 INSERT ... SELECT ... 语句将所需数据加载到其中,然后在自动递增字段上构建/启用索引(这将比加载已启用索引的数据),然后使用键值从该临时表中选择行范围。这将非常快速获取,但构建临时表需要预先付费。

    • 考虑在DBD::mysql 中启用mysql_use_result。驱动程序不会将所有行加载到驱动程序的内存中,而是在行从服务器流入时开始将它们返回给应用程序。这减少了延迟和内存使用,但代价是在表上持有锁。

    • 您可以将 mysql_use_result 与我之前的建议结合使用,但将其与使用 SELECT SQL_BUFFER_RESULT ... 结合起来可能更简单。两者都可以避免锁定问题(无论如何这对您来说可能不是问题)。根据the docs,SQL_BUFFER_RESULT“强制将结果放入临时表”。 (琐事:我想我在很多个月前向 Monty 建议过 SQL_BUFFER_RESULT。)

    【讨论】:

    • 蒂姆 - 感谢您的回复。我真的很喜欢你的第二个建议,我可以接受。我也会研究 mysql_use_result 。您文件上的第 80 页是关于追踪的。这是正确的页面吗?
    【解决方案2】:

    问题是您正在运行多个查询。您的数据集也可能在查询之间发生变化——您可能会错过行或看到重复的行,因为您正在运行多个查询;在您正在搜索的项目上插入或删除会影响这一点。

    第一个速度很快的原因是数据库在达到 10,000 个项目时会截断查询。它没有得到与您的查询匹配的所有行,因此运行速度更快。它不是“变慢”,只是一遍又一遍地做更多的工作——获得前 10,000 行,获得前 20,000 行,前 30,000 行。您已经编写了 Schlemiel 画家的数据库查询。 (http://www.joelonsoftware.com/articles/fog0000000319.html)

    您应该无限制地运行查询并遍历结果集。这将确保数据的完整性。您可能还想研究使用可以利用数据库索引的 where 子句来更快地响应您的查询。

    【讨论】:

    • 这是有道理的。这就是我最初写它的方式,但看起来原始查询花了很长时间。所以,我以这种方式重写了它,显然是在踢自己的脚。没有其他方法我应该这样做,这样我就可以逐行处理而不是等待整个查询完成?
    • 当然没有LIMIT 引入的性能影响那么大,但 OP 还在每次循环迭代时调用 prepare,而不是使用占位符并在循环开始前调用一次。
    • 是的。我将其更改为仅执行一次准备和执行并删除了限制。一开始需要更长的时间,但随后会加快速度。
    【解决方案3】:

    @Oesor 说的是正确的,您不想运行多个查询(除非您知道您是唯一可以修改此表的人)。

    但是,您还有其他问题。

    1. 您没有ORDER BY 子句。否则,您的LIMIT 将毫无意义,因为您不一定每次都得到相同的订单。

    2. 考虑使用 LIMIT n OFFSET m 而不是 LIMIT m,n - 它受 PostgreSQL 支持,对其他数据库系统的用户来说更清晰。

    3. 决定您是使用 bind_columns 还是返回行引用 - 我不明白您为什么要同时尝试这两种方法。也许fetchrow_hashref 是你想要的。

    4. 哦 - 使用 bind_colunmnsSELECT * 时要特别小心。如果向表中添加新列会发生什么?如果该新列名为 ival 怎么办?

    好的,现在让我们看看你在做什么。实际上这并不明显,因为 ...magic_happens 不是一个非常具有描述性的名称。如果是更新,则尝试在数据库中执行所有操作。 MySQL 的功能不如 PostgreSQL,但您最好还是在 RDBMS 中执行批量更新等操作,而不是前后移动大量数据。

    如果没有,并且您想对结果集进行批处理或“分页”,则: 1.按主键(或其他一些独特的列集)排序 2. 跟踪该批次中的最终密钥 3. 在查询语句的“大于”测试中使用它。

    这将允许您在相关的唯一列上使用索引(如果有的话),并且应该让数据库向前跳到第 30000 行,而不必先读取和丢弃其他 29,999 行。

    【讨论】:

    • 谢谢,正如您在我对@Oesor 的回复中看到的那样,我回去做完整的查询,并删除了限制。不过,在 #3 上,我使用的是 fetchrow_hashref,并在看到 Tim Bunce 的回答 here 时将其删除。不过,我承认我并不完全理解它,所以我可能没有正确实现它。
    • 使用 fetchrow_hashref 不太可能严重拖慢您的速度。除非您尝试每秒处理数百万行
    • 是的,我只是把厨房水槽扔了,直到我做了一些实际的基准测试,看看问题出在哪里。
    【解决方案4】:

    根据您的基准测试数据,CPU 时间非常短,因此您需要在 DBI 层上进行配置文件。尝试使用DBI::Profile 运行您的代码以收集该统计信息。

    您可能需要在表上定义一个索引以避免对该查询进行全扫描。

    【讨论】:

      猜你喜欢
      • 2020-09-27
      • 1970-01-01
      • 2012-12-29
      • 2011-04-29
      • 2023-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-16
      相关资源
      最近更新 更多