【问题标题】:Should I use a JOIN function or run several queries in a loop structure?我应该使用 JOIN 函数还是在循环结构中运行多个查询?
【发布时间】:2013-08-18 21:44:57
【问题描述】:

我有这 2 个 mysql 表:TableA 和 TableB

表A
* 列 ID
* A1栏
* ColumnA2
TableB
* 列BId
* 列 ID
* 列 B1
* B2栏

在 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 数据库的最佳方式或正确方式是什么?

Solution1 --- 我正在使用的那个

$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?

【问题讨论】:

  • 我假设在解决方案 1 中,您确实在第二个查询中使用 WHERE 子句仅从 TableB 中获取与 TableA 中特定行相关的行?在这种情况下,我个人更喜欢解决方案 1,除非其他人以更好的想法/分析做出回应。
  • @TomasCreemers - 不,这是一个非常糟糕的做法,使用循环查询(请参阅下面的答案)
  • @TomasCreemers 在循环中进行查询基本上是“不要使用 SQL”的第 2 条规则,在查询中插入未经处理的用户输入。
  • @SweetieBelle ...这将是糟糕的规则之一。使用多个查询比使用单个查询更好/更快的原因有很多。锁粒度是一个,MySQL 优化器不好是另一个,临时表开销仍然是另一个,我可能还可以举出一些其他的。
  • @griffin 锁定粒度在执行SELECT 语句时不太可能成为问题,原因我们已经讨论过。很可能,您不希望在读取数据时更改数据库(出于关系完整性原因),因此最好等待任何写入操作。 极不可能任何其他因素加起来的性能开销与数百或数千个连接、执行和响应的成本相同,即使是在 UNIX 套接字上也是如此。

标签: php mysql optimization


【解决方案1】:

提出的两种解决方案都可能不是最优的,但解决方案 1 是不可预测的,因此存在固有缺陷!

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

  • 有多少行。
  • 您正在查询多少个表。
  • 每行的大小。

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

您可能会发现数据库中的applying & building correct indexes(以及了解 MySQL 中索引的本机实现)对您有更多帮助。

查询有一些黄金法则很少被打破:

  • 不要在循环结构中执行它们。尽管通常很诱人,但创建连接、执行查询和获得响应的开销很高。
  • 除非需要,否则请避免使用SELECT *。选择更多列将显着增加 SQL 操作的开销。
  • 了解您的索引。使用 EXPLAIN 功能,您可以查看正在使用的索引、优化查询以使用可用的索引并创建新索引。

因此,在这两个查询中,我会选择第二个查询(仅将 SELECT * 替换为您想要的列),但是如果您愿意,可能有更好的方法来构建查询有时间进行优化。

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

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

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

我认为事实并非如此。事实上,我认为在许多情况下,运行单个锁定 SELECT 查询的可预测性比优化和速度优势更适合运行该查询。

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

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

举个例子:我们正在运行一个操作来获取当前在游戏中的一群用户的库存中的物品列表,所以我们这样做了:

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

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

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

根据用户是在我们阅读他的详细信息之前还是之后进行交易,以及我们阅读两个玩家的库存的顺序,有四种可能性:

  1. 该项目可能会显示在第一个用户的库存中(扫描用户 B -> 扫描用户 A -> 已交易的项目或扫描用户 B -> 扫描用户 A -> 已交易的项目)。
  2. 该项目可以显示在第二个用户的库存中(项目交易 -> 扫描用户 A -> 扫描用户 B 或项目交易 -> 扫描用户 B -> 扫描用户 A)。
  3. 该物品可能会显示在两个库存中(扫描用户 A -> 交易的物品 -> 扫描用户 B)。
  4. 该物品可能会显示在两个用户的库存中(扫描用户 B -> 交易的物品 -> 扫描用户 A)。

这意味着我们将无法预测查询结果或确保关系完整性

如果您打算在周二午夜向物品 ID 为 1000000 的人赠送 5,000 美元,我希望您手头有 10,000 美元。如果您的程序依赖于在拍摄快照时唯一的项目是唯一的,那么您可能会在此类查询中引发异常。

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

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

哦,最后,使用准备好的语句!

你应该永远不要有这样的陈述:

mysqli_query("SELECT * FROM Table2 WHERE ColumnAId=" . $row['ColumnAId'], $con);

mysqlisupport for prepared statements。阅读并使用它们,它们将帮助您避免something terrible happening to your database

【讨论】:

  • 附注:如果您想将更多的交集等逻辑放入应用程序 (PHP) 代码中,您可能需要考虑使用不同的存储解决方案,例如键值存储 (@987654325 @、CouchDB 等)
  • @griffin 更新了我的答案,说明为什么锁定本质上是好的,因为我觉得你的回答让锁定看起来完全是一件坏事(有意或无意)。
  • 锁定本质上是不好的——原子性是好的。您将锁定“良好”的积极副作用与锁定本身良好混淆了。或者不是,但这就是我从你更新的答案中读到的。顺便说一句,这也是为什么无锁(甚至无等待)算法在大多数情况下胜过锁定算法的原因(取决于非锁定解决方案的开销,这当然可能不成立)
  • @griffin 不,锁定保护完整性并确保可预测性。它对 WRT 性能有负面影响,但(元组,而不是表)锁定很好。
  • 嗯,你认为哪些副作用取决于视角,这对我来说似乎不同,来自 C 中的多线程编程(我一直在努力寻求无锁无等待解决方案以提高性能) - 例如__sync_fetch_and_add大多数 情况下比互斥锁 init+lock+unlock+destroy “更好”(仅在可能的情况下)。无论如何,我至少会将其改写为“锁定 in MySQL 很好......”
【解决方案2】:

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

简单的规则是循环使用查询 - 一般而言。可能会有例外,如果一个查询过于复杂,因此由于性能原因应该拆分,但在某些情况下只能通过基准和度量来显示。

【讨论】:

  • 虽然我同意 SQL 肯定是为了执行更复杂的结构化查询,但现实世界的数据表明,有时拆分查询实际上更好(锁争用、可缓存性......)。由于您的答案是“普遍接受的事实”,因此我不会对其投反对票,但应记住,通用答案并不能代替对具体细节或基准测试的真正了解。
  • @griffin - 是的,可能是我的回答太常见了。你所说的都是真的(我知道这一点) - 所以我已经添加了这个来回答,谢谢。
【解决方案3】:

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

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

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

基准测试。

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

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

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

情况也可能会根据 for 循环的实际执行频率而改变。

再次:基准测试


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

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

【讨论】:

  • -1:评论太长,但您没有真正回答问题。
  • @SweetieBelle 这不是评论,它确实回答了这个问题。 “基准”就是答案,我还说明了多个查询与单个查询的不同优势,因此它比其他的答案更不熟练。
  • @SweetieBelle:如果(为了论证)这个例子中的 TableA 将包含一个 BLOB 列,那仍然是真的吗?或者如果您需要在 TableA 中有 许多 列并且在 TableA 中的每行在 TableB 中有 许多 行,那不意味着 很多 i> 重复数据是结果集的一部分吗?我已经对几个查询场景进行了基准测试,其中循环比单个更复杂的查询提供更好的性能。
  • @SweetieBelle 子查询与 JOINS 不同,并且大多数时候需要一个临时表,因此您的语句可能不适用于大量数据(性能通常也会随着内存使用量的增加而降低,因为用于缓存或将表保存在内存中的可用内存较少)。
  • @SweetieBelle:在我的示例中,重点当然是 BLOB 列 必需的,因此它需要成为 SELECT 的一部分。然而,问题是 MySQL 不能只发送一次 BLOB,它必须为连接表中的每一行发送它。如果 BLOB 是 0.5MByte,并且连接表中有 10000 行与父表中的每行匹配,则这几乎是 5GByte 的额外传输。此开销将大于每个父表行一个额外的查询。
【解决方案4】:

在某些情况下,您应该使用 limit 以获得最佳性能

如果你想显示 1000 行 还有一些单一的查询(主数据)

你应该跑 1000 次,限制在 10-100 之间

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

例子

从 transaction_product 限制 100 中选择 productID,日期

获取所有productID并使其唯一

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

foreach(事务) master_poduct[产品ID]

【讨论】:

  • 这个答案令人困惑。您能否将您的代码放入代码块中并更好地解释您的建议?
  • 我尝试使用 PHP
  • 我尝试使用 PHP 和 codeigniter 简单的方法 $this->db->limit(10); //尝试使用分页来优化结果 $query1 = $this->db->get("product_transaction"); foreach($query1->result() as $row) $uniq_ID [$row->ID]= $row->name; $this->db->limit(array_unique(array_keys($uniq_ID))); $this->db->where_in(array_unique(array_keys($uniq_ID))); $query2 = $this->db->get("product_master"); foreach($query2->result() as $row) echo $uniq_ID[$row->ID]
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-31
  • 1970-01-01
  • 2013-07-31
  • 2023-03-14
  • 1970-01-01
  • 2012-02-15
  • 2012-10-20
相关资源
最近更新 更多