【问题标题】:Need efficient way to do simple computation on 200million row database (PHP)需要有效的方法对 2 亿行数据库 (PHP) 进行简单计算
【发布时间】:2011-08-09 18:31:09
【问题描述】:

我正在请求针对以下问题的 PHP 解决方案:

我在数据库中有大约 15 个表,每个表有 10-50 百万行,总和为 2 亿行,列 userID、B、C、D。

我还有 9 个其他表,其中包含 userID、fbID 列。每个表有大约 200 万行。从 userID 到 fbID 存在一对一的映射。

我的目标是输出包含 fbID、B、C、D 列的 2 亿行的文件。

为了做到这一点,我必须搜索所有包含列 userID 和 fbID 的 9 个表,因为 userID 可能在一个表中找到,但在其他表中找不到。一旦在这些表中的任何一个中找到用户 ID,我就可以停止。这部分我使用 SQL 和 PHP。 SQL 查询包含 LIMIT 1,因此每当我找到用户 ID 时我只返回 1 行,因为这些表可以包含具有相同用户 ID 的多行。

不幸的是,这个算法需要 ~60s/1k 行,我需要 ~130 天才能完成。

有没有更有效的方法来做到这一点?

我不是数据库计算时间如何工作的专家,但我想到了一些想法:

-查询所有 9 个表并使用 userID 键和 fBID 值创建一个查找表。

-使用这 9 个表在数据库中创建一个新表,每个用户 ID 有一行,以及相应的 FBID,并通过该表进行搜索。

以下是有关表格的更具体信息:

总计多达 2 亿行的表格(每个看起来像这样):

Column         Type        Null      Default 

dtLogTime      datetime    Yes       NULL 

iUin           int(10)     No         

B              int(10)     No

C              int(10)     No

D              int(10)     No

索引:

Keyname   Type  Unique Packed Column    Cardinality Collation Null Comment 

dtLogTime BTREE No     No     dtLogTime 323542      A         YES  

iUin      BTREE No     No     iUin      323542      A

其他 9 个表格之一:

Column     Type        Null     Default     Comments 

dtLogTime  datetime    Yes      NULL   

iUin       int(10)     No         

vFBID      varchar(48) No    

索引:

Keyname   Type  Unique Packed Column    Cardinality Collation Null Comment 

dtLogTime BTREE No     No     dtLogTime 2179789     A         YES  

iUin      BTREE No     No     iUin      2179789     A  

我尝试过的示例代码:

// returns FBID of iuin
function getFBID($iuin){

$query = sprintf("SELECT vFBID FROM `tbReg` WHERE iuin = " . $iuin . " LIMIT 1");
$result = mysql_query($query);
if(mysql_num_rows($result) != 0){
  $row = mysql_fetch_assoc($result);
  return $row['vFBID'];
}
mysql_free_result($result);

$query = sprintf("SELECT vFBID FROM `tbOnline` WHERE iuin = " . $iuin . " LIMIT 1");
$result = mysql_query($query);
if(mysql_num_rows($result) != 0){
  $row = mysql_fetch_assoc($result);
  return $row['vFBID'];
}
mysql_free_result($result);

$query = sprintf("SELECT vFBID FROM `tbConsumeFBC` WHERE iuin = " . $iuin . " LIMIT 1");
$result = mysql_query($query);
if(mysql_num_rows($result) != 0){
  $row = mysql_fetch_assoc($result);
  return $row['vFBID'];
}
mysql_free_result($result);

$query = sprintf("SELECT vFBID FROM `tbFeed` WHERE iuin = " . $iuin . " LIMIT 1");
$result = mysql_query($query);
if(mysql_num_rows($result) != 0){
   $row = mysql_fetch_assoc($result);
   return $row['vFBID'];
}
mysql_free_result($result);

$query = sprintf("SELECT vFBID FROM `tbInvite` WHERE iuin = " . $iuin . " LIMIT 1");
$result = mysql_query($query);
if(mysql_num_rows($result) != 0){
   $row = mysql_fetch_assoc($result);
   return $row['vFBID'];
}
mysql_free_result($result);  

$query = sprintf("SELECT vFBID FROM `tbFreeGift` WHERE iuin = " . $iuin . " LIMIT 1");
$result = mysql_query($query);
if(mysql_num_rows($result) != 0){
   $row = mysql_fetch_assoc($result);
   return $row['vFBID'];
}
mysql_free_result($result); 

$query = sprintf("SELECT vFBID FROM `tbUninstall` WHERE iuin = " . $iuin . " LIMIT 1");
$result = mysql_query($query);
if(mysql_num_rows($result) != 0){
   $row = mysql_fetch_assoc($result);
   return $row['vFBID'];
}
mysql_free_result($result);  

$query = sprintf("SELECT vFBID FROM `tbDownload` WHERE iuin = " . $iuin . " LIMIT 1");
$result = mysql_query($query);
if(mysql_num_rows($result) != 0){
   $row = mysql_fetch_assoc($result);
   return $row['vFBID'];
}

$query = sprintf("SELECT vFBID FROM `tbIUserSource` WHERE iuin = " . $iuin . " LIMIT 1");
$result = mysql_query($query);
if(mysql_num_rows($result) != 0){
   $row = mysql_fetch_assoc($result);
   return $row['vFBID'];
}
mysql_free_result($result);
}

fwrite($handle, '"Time","FBID","Action","ActionID"' . "\n");

$query = sprintf("SELECT count(dtLogTime) AS length
                  FROM `tbActionWeeding`");
$result = mysql_query($query);
$row = mysql_fetch_assoc($result);
$length = ceil($row['length'] * 0.0001);
$start = 0;
$i = 0;
while($i++ < 10000)
   $query = sprintf("SELECT dtLogTime, iuin, iWeedID
                     FROM `tbActionWeeding`
                     LIMIT " . $start . "," . $length);
   $result = mysql_query($query);
   if (!$result) {
      $message  = 'Invalid query: ' . mysql_error() . "\n";
      $message .= 'Whole query: ' . $query . "\n";
      die($message);
   }
   while($row = mysql_fetch_assoc($result))
      fwrite($handle, '"' . $row['dtLogTime'] . '","' . getFBID($row['iuin']) .
                   '","0","' . $row['iWeedID'] . "\"\n");
   mysql_free_result($result);
   $start += $length;
}

【问题讨论】:

  • 这听起来像 JOIN 会帮助你:en.wikipedia.org/wiki/Join_(SQL)
  • 这个算法是更大的标准化工作的一部分吗?如果没有,我可以建议您考虑一个的好处吗?
  • 抱歉,我忘了提到这 9 个表,每个表都有大约 200 万行,我记得我在连接以前项目中的表时遇到了麻烦。
  • 您使用的是什么数据库?您最好使用 sqlplus 或 mysql 之类的命令行实用程序。此外,如果性能是连接时的问题,请考虑为连接列编制索引。
  • 请澄清:您是否在这 9 个较小的表的 fbID 列中从“2 亿行表”中搜索 userID 列的值?您的目标是只返回大表中的这些行,在 9 个小表中的任何一个中都有匹配的行吗?

标签: php sql database performance


【解决方案1】:

我还有 9 个其他表,其中包含 userID、fbID 列

这 9 个表各有大约 200 万行

仅使用巧妙的代码无法轻松克服这种数据结构的低效率。由于需要处理大量冗余数据,因此最有效的算法将在此架构下运行缓慢。

您需要的是normalization。您应该更改表的结构以删除冗余数据。这将消除对九个单独的表进行 2 亿次搜索的需要,从而显着提高效率。

【讨论】:

    【解决方案2】:

    现在这可以工作了,尽管就像其他人在 cmets 中所说的那样,最好知道您是否有适当的索引。

    SELECT
      u.fbID, t.B, t.C, t.d
    FROM
      veryLargeTable AS t
    CROSS JOIN (
      SELECT userId, fbID FROM
        smallerTable1 
      UNION SELECT userId, fbID FROM
        smallerTable2 
      ...
      UNION SELECT userId, fbID FROM
        smallerTable9 
    ) AS u USING (userId)
    

    您可能想先在较小的数据集上运行它,看看它的表现如何。

    【讨论】:

    • 嘿,抱歉,我试图简化问题,但这个非常大的表实际上是 ~15 个表,每个表的行数从 10 到 5000 万行不等,它们总计约 2 亿行。你认为这个解决方案还能用吗?
    • 呃......好吧......你确实简化了一点......但是你可以像这样运行15个查询并合并结果,或者像我一样使用UNION来maerge这15个表上面有9张桌子。这肯定会很慢......
    【解决方案3】:

    请理解,由于行数的原因,最有效的方法可能仍需要一些时间。

    第一个真正的问题是您需要在 PHP 中使用它。那是多么的绝对?如果完全有可能在数据库本身上工作,您希望这样做:

    -- 
    -- Index all 9 tables on userid,fbId
    
    select UserId,fbId
      into WorkingTable_UserId_to_fbId
      from table1Of9
    union all
    select UserId,fbId
      from table_2_of_9
    --
    -- repeat the UNION all clause up to:
    UNION ALL
    select UserId,fbId
      from table_9_of_9
    GROUP BY 1,2
    
    -- Index resulting table on userId,fbId 
    

    这为您提供了一个工作表,使基本查询如下:

    select Linker.Fbid,main.b,main.c.,main.d
      from mainTable main
      JOIN WorkingTable_UserId_to_FbId linker on main.userId = linker.userId
    

    如果绝对不可能创建该表,那么您必须使用相同的代码并将其插入到上面的查询中,它不会那么快。应该是:

    select Linker.Fbid,main.b,main.c.,main.d
      from mainTable main
      JOIN (  select UserId,fbId
               from table1Of9
              union all
             select UserId,fbId
               from table_2_of_9
             -- etc, etc.
    
           ) Linker on main.userId = linker.userId
    

    但是,当服务器尝试收集 2 亿行以准备返回 PHP 时,这可能会停止。所以你需要把它分成几块,一次提取可能 10000 行。将 OFFSET...LIMIT 添加到上述查询可能很诱人,但这仍然会给服务器带来沉重的负担。最好在 PHP 中处理,例如:

    # Very sloppy code off the top of my head,
    # modify this loop based on what you know of the
    # userId values
    $id = 1;
    while($id <= 200000000) {
        $topId = $id + 9999;
        $sql="select Linker.Fbid,main.b,main.c.,main.d
                from mainTable main
                JOIN WorkingTable_UserId_to_FbId linker on main.userId = linker.userId
               WHERE main.userId between $id and $topId";
    
        # Note: don't freak out about SQL injection in the above code,
        #       you are hardcoding the values of ID, not getting them from a user
    
        #
        # Execute query, retrieve rows, output
        # then up the counter:
        $id+=1000;
    }
    

    【讨论】:

    • 是的,我可以在数据库中创建该表。你能评论一下这个解决方案的速度吗?一旦我制作了这张表,这是否很有可能会显着降低速度?
    • 表会加快速度,尤其是添加索引后。原因是您现在实际上是在使用索引而不是表本身,并且由于索引包含这两个值,因此一旦找到 UserId,fbId 就会与它一起存储。另外,既然你提到有骗子,这张表已经消除了他们,效率更高。
    猜你喜欢
    • 2015-10-13
    • 1970-01-01
    • 1970-01-01
    • 2013-08-24
    • 2013-12-27
    • 1970-01-01
    • 2017-04-08
    • 2022-06-12
    • 2019-12-10
    相关资源
    最近更新 更多