3

首先抱歉,如果问题已经得到解答,我在这里和谷歌都搜索了,但找不到我的答案。这个问题不可能没有被问到,但它隐藏在所有“只需使用 LEFT JOIN”和“将其存储在数组中”的答案之下。

我需要加载分布在多个表中的大量数据(然后将其插入另一个数据库引擎,但这并不重要,我需要优化我的 SELECT)。

表格布局如下所示:

带有 a_id 字段的表 A 带有 a_id 和 b_id 字段的表 B 带有 b_id 和 c_id 字段的表 C ...(像这样再进行 3-4 级)。

我目前以这种方式访问​​数据(伪代码):

query1 = SELECT ... FROM TableA WHERE something=$something

foreach query1 as result1:

    query2 = SELECT ... FROM TableB WHERE b_id=result1.a_id

    foreach query2 as result2:
        query3 = SELECT ... FROM TableC WHERE bc_id=result2.b_id

            foreach query3 as result3:
                // Another few levels of this, see the millions of SELECTs coming?

到目前为止,我发现的唯一解决方案是:

  1. 使用慢速方式并发送多个查询(当前解决方案,完成我的小测试集需要很长时间)
  2. 使用大量的 LEFT JOIN 在一个查询中获取所有数据。涉及数千次传输大量数据,因此在客户端有一些奇特的逻辑将其再次拆分为适当的表,因为每一行都将包含其父表的内容。(我使用 OOP,每个表都映射到一个对象,每个对象都包含彼此)。
  3. 将表 A 中的每个对象存储在一个数组中,然后加载所有表 B,存储到一个数组中,然后在表 C 上继续。适用于小型集合,但我的是几 GB,根本不适合 ram。

有没有办法避免在这样的循环中每秒执行 10k 次查询?

(我正在使用 PHP,从 MySQL 转换为 MongoDB,这样可以更好地处理嵌套对象,如果这有帮助的话)

编辑:似乎对我要做什么以及为什么有些困惑。我将尝试更好地解释:我需要批量转换为新结构。新结构效果很好,甚至不用费心去看。我正在从头开始改造一个非常古老的网站,并选择 MongoDB 作为我的存储引擎,因为我们有大量这样的嵌套数据,而且它对我来说效果很好。切换回 MySQL 对我来说甚至不是一个选择,新的结构和代码已经很成熟,我已经为此工作了大约一年。我不是在寻找优化当前架构的方法,我做不到。数据是这样的,我需要读取整个数据库。一次。然后我就完成了。

我需要做的就是从旧网站导入数据,处理并转换它,以便我可以将其插入到我们的新网站中。MySQL 出现了:旧站点是一个非常普通的 PHP/MySQL 站点。我们有很多桌子(实际上大约有 70 张左右)。我们的用户不多,但每个用户都有大量数据,分布在 7 个表上。

我目前所做的是循环每个用户(1 个查询)。对于这些用户中的每一个(70k),我加载表 A,其中每个用户包含 10-80 行。然后,我在 A 的每个循环上查询表 B(因此,10-80 乘以 70k),其中每个 A 包含 1-16 行。然后是表 C,每个 B 包含 1-4 行。我们现在是 4 *80*70k 查询要做。然后我有 D,每个 C 有 1-32 行。E 每个 D 有 1-16 行。F 每个 E 有 1-16 行。表 F 有几百万行。

问题

  • 我最终对 MySQL 服务器进行了数千甚至数百万次查询,其中生产数据库甚至不在我的本地计算机上,而是在 5-10 毫秒之外。即使在 0.01 毫秒,我也有几个小时的网络延迟。我创建了一个本地副本,因此我的受限测试集运行得更快,但下载这样的几 GB 数据仍然需要很长时间。

  • 我可以将成员表保存在 RAM 中,也可以保存在表 A 中,这样我就可以一次下载每个数据库,而不是进行数千次查询,但是一旦在表 B 中,在内存中跟踪它就会变得一团糟,尤其是因为我使用 PHP(至少是命令行),与我可以严格控制 RAM 的 C++ 程序相比,它使用的内存要多一些。所以这个解决方案也不起作用。

  • 我可以将所有表连接在一起,但如果它适用于 2-3 个表,则对 7 个表执行此操作会导致额外的巨大带宽损失,从服务器传输相同数据数百万次而不使用(同时也使代码以适当的顺序将它们拆分回来真的很复杂)。

问题是:有没有办法不经常查询数据库?就像,告诉 MySQL 服务器一个过程或什么我需要按这个顺序所有这些数据集,这样我就不必重新查询每一行,所以数据库只是不断地为我吐出数据?当前的问题是我做了太多查询,以至于脚本和数据库几乎都处于空闲状态,因为一个总是在等待另一个。查询本身实际上是对索引 int 字段非常快速的基本准备 SELECT 查询。

这是我过去在使用 MySQL 时经常遇到的问题,直到现在才真正给我带来麻烦。在当前状态下,脚本需要几个小时甚至几天才能完成。这不是那么糟糕,但如果有办法我可以做得更好,我会很高兴知道。如果没有,那好吧,我就等它完成,至少它最多会运行 3-4 次(2-3 次测试运行,让用户检查他们的数据是否正确转换,修复错误,再试一次,然后最后运行最后的错误修正)。

提前致谢!

4

2 回答 2

0

假设您的 7 个表由 id 链接,请执行以下操作

第一次查询

'SELECT * FROM table_a WHERE a_id IN (12,233,4545,67676,898999)'
// store the result in $result_of_first_query

然后执行 foreach 并在逗号分隔变量 (csv) 中选择要在下一个查询中使用的 id

foreach($result_of_first_query as $a_row_from_first_table)
{
    $csv_for_second_query = $csv_for_second_query.$a_row_from_first_table['b_id'].",";
}

$csv_for_second_query = trim($csv_for_second_query,", "); // problem is we will have a lot of duplicate entries
$temp_arr = array(); // so lets remove the duplicates
$temp_arr = explode(",",$csv_for_second_query);  // explode values in array
$temp_arr = array_unique($temp_arr);  // remove duplicates
$csv_for_second_query = implode(",",$temp_arr);  // create csv string again. ready!

现在对于您的第二张表,您将获得,只需 1 次查询您需要加入的所有值(不是通过 mysql,我们将使用 php 执行此操作)

第二次查询

'SELECT * FROM table_b where a_id IN ('.$csv_for_second_query.')'
// store the result in $result_of_second_query;

然后我们只需要以编程方式连接两个数组。

$result_a_and_b = array(); // we will store the joined result of every row here

// lets scan every row from first table
foreach($result_of_first_query as $inc=> $a_row_from_first_table)
{
    // assign every row from frist table to result_a_and_b 
    $result_a_and_b[$inc]['a']=$a_row_from_first_table;

    $inc_b=0; // counter for the joins that will happen by data from second table

    // for every row from first table we will scan every row from second table
    // so we need this nested foreach
    foreach($result_of_second_query as $a_row_from_second_table)
    {
        // are data need to join? if yes then do so! :)
        if($a_row_from_first_table['a_id']==$a_row_from_second_table['a_id'])
        {
            $result_a_and_b[$inc]['b'][$inc_b]=$a_row_from_second_table; // "join" in our "own" way :)
            ++$inc_b; // needed for the next join
        }
    }
}

现在我们有了这个格式的数组 $result_a_and_b:

$result_a_and_b[INDEX]['a']
$result_a_and_b[INDEX]['b'][INDEX]

所以使用 2 个查询,我们得到类似于 TABLE_A_ROWS_NUMBER + 1 的结果(一个是第一个表的初始查询)

像这样继续做你想要的许多级别。

  1. 用链接表的id查询数据库
  2. 获取 CSV 字符串中的 id
  3. 使用 WHERE id IN(11,22,33,44,55,.....) 进行查询
  4. 以编程方式加入

提示:您可以使用unset()释放临时变量的内存。

我相信我在您的问题中回答了“有没有办法不经常查询数据库?”

注意:代码没有测试错别字,也许我错过了一两个逗号 - 或者可能没有

我相信你能明白这一点:)希望它有帮助!

于 2013-06-18T08:34:34.500 回答
0

谢谢大家的回答。我得出的结论是,我实际上无法以其他任何方式做到这一点。

我自己的解决方案是在 localhost 上设置一个副本数据库(或者如果快照足够,则只是一个副本)。这样,它减少了网络延迟并允许脚本和数据库达到 100% 的 CPU 使用率,这似乎是我可以在不完全重新组织我的脚本的情况下获得的最快速度。

当然,这只适用于一次性脚本。处理这个问题的正确方法是我现在得到的两个答案的混合:在线程中使用多个无缓冲连接,并按批处理(从表 A 加载 50 行,存储在 ram 中,从表 B,存储在 RAM 中,然后处理所有这些并从表 A 继续)。

无论如何,谢谢大家的答案!

于 2013-06-23T19:24:40.963 回答