我应该使用JOIN函数还是在循环结构中运行多个查询?

时间:2013-08-16 13:09:15

标签: php mysql optimization

我有这2个mysql表:TableA和TableB

表A
* ColumnAId
* ColumnA1
* ColumnA2
表B
* ColumnBId
* ColumnAId
* ColumnB1
* ColumnB2

在PHP中,我希望拥有这种多维数组格式

$array = array(
    array(
        'ColumnAId' => value,
        'ColumnA1' => value,
        'ColumnA2' => value,
        'TableB' => array(
            array(
                'ColumnBId' => value,
                'ColumnAId' => value,
                'ColumnB1' => value,
                'ColumnB2' => value
            )
        )
    )
);

这样我就可以用这种方式循环

foreach($array as $i => $TableA) {
    echo 'ColumnAId' . $TableA['ColumnAId'];
    echo 'ColumnA1' . $TableA['ColumnA1'];
    echo 'ColumnA2' . $TableA['ColumnA2'];
    echo 'TableB\'s';
    foreach($value['TableB'] as $j => $TableB) {
        echo $TableB['...']...
        echo $TableB['...']...
    }
}

我的问题是,查询MySQL数据库的最佳方式或正确方法是什么,以便我可以实现这一目标?

解决方案1 ​​ ---我正在使用的

$array = array();
$rs = mysqli_query("SELECT * FROM TableA", $con);
while ($row = mysqli_fetch_assoc($rs)) {
    $rs2 = mysqli_query("SELECT * FROM Table2 WHERE ColumnAId=" . $row['ColumnAId'], $con);
    // $array = result in array
    $row['TableB'] = $array2;
}

我怀疑我的代码导致它总是在查询数据库。

溶液2

$rs = mysqli_query("SELECT * FROM TableA JOIN TableB ON TableA.ColumnAId=TableB.ColumnAId");
while ($row = mysqli_fet...) {
    // Code
}

第二个解决方案只查询一次,但是如果我在TableA中有数千行,而TableB中有数千行,每个TableB.ColumnAId(1 TableA.ColumnAId = 1000 TableB.ColumnAId),那么这个解决方案2需要花费很多时间解决方法1?

4 个答案:

答案 0 :(得分:5)

提出的两个解决方案都不是最佳的,但解决方案1是不可预测的,因此固有的呀!

在处理大型数据库时,您学到的第一件事就是“进行查询的最佳方式”通常取决于数据库中的因素(称为元数据):

  • 有多少行。
  • 您要查询的表数。
  • 每行的大小。

因此,不太可能有针对您的问题的银弹解决方案。您的数据库与我的数据库不同,如果您需要最佳性能,则需要对不同的优化进行基准测试。

您可能会发现数据库中的applying & building correct indexes(以及了解MySQL中索引的本机实现)为您做了更多的事情。

有些黄金规则的查询应该很少被破坏:

  • 不要在循环结构中执行。像往常一样诱人,创建连接,执行查询和获得响应的开销很高。
  • 除非必要,否则请{/ 1}} 。选择更多列将显着增加SQL操作的开销。
  • 了解您的指数。使用SELECT *功能,以便您可以查看正在使用的索引,优化查询以使用可用的内容并创建新索引。

正因为如此,在这两个问题中,我会选择第二个查询(仅使用您想要的列替换EXPLAIN),可能有更好的方法来构建查询如果你有时间进行优化。

但是,速度应该 NOT 是你唯一的考虑因素,有一个很好的理由不使用建议一:

可预测性:为什么读锁是一件好事

其他一个答案表明,将表锁定很长一段时间是件坏事,因此多查询解决方案很好。

我认为这不可能离真相更远。事实上,我认为在许多情况下,运行单个锁定SELECT *查询的可预测性是运行该查询的一个更大的参数,而不是优化&速度效益。

首先,当我们在MyISAM或InnoDB数据库(MySQL的默认系统)上运行SELECT(只读)查询时,会发生的事情是该表是读取锁定的。这可以防止任何WRITE操作在表上发生,直到读取锁定被放弃(我们的SELECT查询完成或失败)。其他SELECT查询不受影响,因此如果您运行的是多线程应用程序,它们将继续有效。

这种延迟是件好事。为什么,你可能会问?关系数据完整性。

我们举一个例子:我们正在运行一项操作来获取当前在游戏中一群用户的广告资源中的项目列表,所以我们这样做了:

SELECT

如果在此查询操作期间,用户将项目交易给其他用户,会发生什么?使用此查询,我们可以看到当我们启动查询时的游戏状态:项目存在一次,在我们运行查询之前拥有它的用户的清单中。

但是,如果我们在循环中运行它会发生什么?

根据用户在阅读其详细信息之前或之后进行交易,以及我们阅读两个玩家的广告资源的顺序,有四种可能性:

  1. 该项目可以显示在第一个用户的库存中(扫描用户B - >扫描用户A - >项目交易或扫描用户B - >扫描用户A - >项目交易)。
  2. 该项目可以显示在第二个用户的库存中(交易项目 - >扫描用户A - >扫描用户B或交易项目 - >扫描用户B - >扫描用户A)。
  3. 该项目可以在两个库存中显示(扫描用户A - >商品交易 - >扫描用户B)。
  4. 该项目可以显示在用户库存的既不中(扫描用户B - >商品交易 - >扫描用户A)。
  5. 这意味着我们无法预测查询结果或确保关系完整性

    如果你计划在星期二午夜向物品ID为1000000的人提供5,000美元,我希望你手头有10万美元。如果您的程序依赖于在拍摄快照时唯一的唯一项目,则可能会使用此类查询引发异常。

    锁定很好,因为它增加了可预测性并保护了结果的完整性

    注意:您可以强制循环使用transaction锁定,但仍然会变慢。

    哦,最后,使用准备好的声明!

    你应该从不有一个如下所示的声明:

    SELECT * FROM `users` JOIN `items` ON `users`.`id`=`items`.`inventory_id` WHERE `users`.`logged_in` = 1;
    

    mysqli_query("SELECT * FROM Table2 WHERE ColumnAId=" . $row['ColumnAId'], $con); support for prepared statements。阅读并使用它们,它们将帮助您避免something terrible happening to your database

答案 1 :(得分:2)

绝对是第二种方式。嵌套查询是一个丑陋的事情,因为每次为每个嵌套查询获取所有查询开销(执行,网络e tc),而单JOIN查询将执行一次 - 即所有开销将只执行一次。

简单的规则是来循环使用查询 - 通常。可能有例外情况,如果一个查询过于复杂,那么由于性能应该被拆分,但在某种情况下只能通过基准和度量来显示。

答案 2 :(得分:2)

如果您想对应用程序代码中的数据进行算法评估(我认为这是正确的做法),您根本不应该使用SQL。 SQL是一种人类可读的方式,可以从关系数据库中获取计算实现的数据,这意味着,如果您只是使用它来存储数据,并在代码中进行计算,那么您就是无论如何都做错了。

在这种情况下,您应该更喜欢使用不同的存储/检索可能性,例如键值存储(存在持久存在的,并且较新版本的MySQL将键值接口暴露为很好的InnoDB,但它仍然使用关系数据库进行键值存储,也就是工作的错误工具。

如果您仍想使用您的解决方案:

基准。

我经常发现发出多个查询比单个查询更快,因为MySQL必须解析较少的查询,优化器的工作量较少,而且MySQL的工作量往往较少。 optimzer刚刚失败(这就是STRAIGHT JOIN和索引提示之类的东西存在的原因)。即使它没有失败,多个查询可能仍然会更快,具体取决于底层存储引擎以及有多少线程尝试一次访问数据(锁粒度 - 这仅适用于混合更新查询但< / strong> - 默认情况下,MyISAM和InnoDB都没有为SELECT查询锁定整个表。然后,如果不使用事务,您甚至可能会使用这两种解决方案得到不同的结果,因为如果您使用多个查询而不是单个查询,则查询之间的数据可能会发生变化。

简而言之:您的问题比您发布/要求的更多,以及通用答案可以提供的内容。

关于您的解决方案:如果您的环境中a)数据更改很常见和/或b)您有许多并发运行线程(请求)访问和更新表,我更喜欢第一个解决方案(锁定)分割查询的粒度更好,查询的可缓存性也是如此);如果您的数据库位于不同的网络上,例如网络延迟是一个问题,你可能更好的第一个解决方案(但我认识的大多数人在同一台服务器上使用MySQL,使用套接字连接,本地套接字连接通常没有太多的延迟)。

情况也可能会发生变化,具体取决于for循环的实际执行频率。

再次:基准


要考虑的另一件事是内存效率和算法效率。后两者在两种情况下都是O(n),但是根据你用来加入的数据类型,两者中的任何一个都可能更糟。例如。如果你使用字符串加入(你真的不应该,但你不说),更多php依赖解决方案的性能还取决于PHP哈希映射算法(php中的数组是有效的哈希映射)和碰撞的可能性,而mysql字符串索引通常是固定长度的,因此,根据您的数据,可能不适用。

对于内存效率,多查询版本肯定更好,因为你在两个解决方案中都有php数组(在内存方面效率非常低!),但是连接可能会根据几种情况使用临时表(通常它不应该,但有些情况下 - 你可以使用EXPLAIN查询你的查询)

答案 3 :(得分:0)

在某些情况下,您应该使用限制以获得最佳效果

如果你想要显示1000行 和一些单一查询(主数据)

你应该运行1000,限制在10-100之间

然后在查询中使用WHERE IN获取使用单个查询来掌握数据的外键。然后计算您的唯一数据以限制主数据。

实施例

从transaction_product限制100

中选择productID,日期

获取所有产品ID并使其独一无二

然后 从master_product中选择价格WHERE IN(1,2 3 4)限制4(从唯一总数中计算)

的foreach(事务)    master_poduct的[ProductID]