减少 MySQL 中具有许多一对多关系 (ORM) 的查询

Posted

技术标签:

【中文标题】减少 MySQL 中具有许多一对多关系 (ORM) 的查询【英文标题】:Decreasing queries in MySQL with many one-to-many relationships (ORM) 【发布时间】:2010-11-04 00:03:16 【问题描述】:

我目前正在使用 phpmysql 设计一个基于 Kohana 框架的应用程序。我正在使用内置的 ORM,事实证明它非常有用。一切正常,但我非常关心某些页面上运行的查询数量。

设置 例如,有一个页面,您可以在该页面上查看一个充满部分的类别,而这些部分又是产品。这以表格形式列出。每个产品(可能)都有许多属性、标志、层级定价中断。这些都必须在表格中表示出来。

多少查询? 就查询而言:类别必须查询其中的所有部分,并且这些部分必须查询它们包含的所有产品。还不错,但是每个产品都必须查询它的所有产品属性、层级定价和标志。因此,向一个类别添加更多产品会多次增加查询(因为我目前主要使用 ORM)。一个部分中有几百个产品将导致几百个查询。小查询,但这仍然不好。

到目前为止... 所有的键都被索引。我可以通过一个查询提取所有信息(参见下面的编辑),但是,正如您想象的那样,这将导致大量冗余数据分布在每个产品的多行中,每个额外的(例如)属性,标志等

我不反对放弃 ORM 以用于应用程序的显示部分并使用查询构建甚至原始 SQL。

解决方案实际上可能非常简单,而我现在对此一无所知,老实说,这将是一种解脱。或者也许不是。我不知道。如果我的任何解释不足以理解问题,请询问,我会尝试举一个更好的例子。 (编辑:给出了更好的例子,见下文

虽然,附注... 不过可能有一些相关性:虽然我总是希望以最有效的方式设计应用程序,但这并不是一个每天会被点击数十或数百次的网站。它更像是一个管理应用程序,可能不会同时被几个人使用。我无法预见太多的重新加载,因为页面上的大部分数据编辑都是通过 AJAX 完成的。那么,如果每次加载此特定页面时在此页面上运行几百个查询(随着当前查看的部分中有多少产品而波动),我是否应该如此关心?只是一个侧面的想法,即使可以解决上述主要问题,我也更愿意这样做。

非常感谢!

编辑 根据几个答案,我似乎没有充分解释自己。因此,让我发布一个示例,以便您了解发生了什么。 不过,在示例之前,我还应该澄清两点:(1) 还有几个多对多关系,(2) 您可能会将我正在寻找的内容比作交叉表查询。

让我们简化并假设我们有 3 个主表: 产品(product_id、product_name、product_date_added) product_attributes (product_attribute_id, product_id, value) 通知(notification_id、notification_label)

还有 1 个枢轴表: product_notifications (notification_id, product_id)

我们将在一个表格中列出所有产品。在 ORM 中调用所有产品很简单。 因此,对于每个“产品”,我们都会列出 product_name 和 product_date_added。但是,我们还需要列出所有产品属性。每个产品有 0 个或多个。我们还必须显示产品有哪些通知,其中也有 0 个或更多。 所以目前,它的工作原理基本上是:

foreach ($products->find_all() as $product) //given that $products is an ORM object

   echo $product->product_id; //lets just pretend these are surrounded by html
   echo $product->product_name;
   foreach ($products->product_attributes->find_all() as $attribute)
   
       echo $attribute->value;
   
   foreach ($products->notifications->find_all() as $notification)
   
       echo $notification->notification_label; 
   
 

这当然过于简单化了,但这就是我所说的原则。这工作已经很棒了。 然而,如您所见,对于每个产品,它必须查询其所有属性以获取适当的集合或行。 find_all() 函数将返回以下内容的查询结果: SELECT product_attributes.* FROM product_attributes WHERE product_id = '#',通知也是如此。它会针对每个产品进行这些查询。 因此,对于数据库中的每个产品,查询的数量是该数量的几倍。 因此,虽然这很有效,但它的扩展性并不好,因为它可能会导致数百个查询。

如果我执行一个查询以获取一个查询中的所有数据,如下所示:

SELECT p.*, pa.*, n.*
FROM products p
LEFT JOIN product_attributes pa ON pa.product_id = p.product_id
LEFT JOIN product_notifications pn ON pn.product_id = p.product_id
LEFT JOIN notifications n ON n.notification_id = pn.notification_id

(再次过于简单化)。这会获取数据本身,但是对于产品具有的每个属性和通知,将返回一个包含冗余信息的额外行。

例如,如果我在数据库中有两个产品;一个有1个属性和1个标志,另一个有3个属性和2个标志,它将返回:

product_id, product_name, product_date_added, product_attribute_id, value, notification_id, notification_label
1, My Product, 10/10/10, 1, Color: Red, 1, Add This Product
2, Busy Product, 10/11/10, 2, Color: Blue, 1, Add This Product
2, Busy Product, 10/11/10, 2, Color: Blue, 2, Update This Product
2, Busy Product, 10/11/10, 3, Style: New, 1, Add This Product
2, Busy Product, 10/11/10, 3, Style: New, 2, Update This Product

不用说,这是很多多余的信息。每个产品返回的行数将是它具有的属性数乘以它具有的通知数。

ORM(或者,通常只是在循环中创建新查询)将每一行中的所有信息合并到它自己的对象中,从而可以更合理地处理数据。就是那块石头。在一个查询中调用信息可以消除可能数百个查询的需要,但会在行中创建大量冗余数据,因此不会返回简洁集合中的(一对多)关系数据。那是困难的地方。

抱歉拖了这么久,求详细点,哈哈,谢谢!

【问题讨论】:

一个好的 ORM 应该让您在初始查询中指定哪些内容应该作为 JOIN 加载,而不是稍后通过单独的查询延迟加载它们...... 仅供参考,您可以运行Database::instance()->last_query 并向我们展示一些ORM 生成的查询吗?哦,对一个经过深思熟虑的问题 +1(现在越来越少有 1 个代表用户)。 我已经编辑了问题描述以包含正在生成的查询的简短示例。谢谢!就 JOINS 而言,我还进行了编辑,以显示当您尝试加入此数据时会发生什么。希望上面发布的示例数据能更清楚地说明问题是什么以及我正在努力克服的问题。非常感谢! 【参考方案1】:

一个有趣的替代方法是使用完全独立的模型来处理您的读取和写入。 (命令查询分离)。复杂的对象模型(和 ORMS)非常适合对复杂的业务行为进行建模,但作为向用户查询和显示信息的接口却很糟糕。您提到您并不反对放弃 ORM 来渲染显示——嗯,这正是当今许多软件架构师所建议的。编写一个完全不同的界面(带有自己的优化查询)来读取和报告数据。 “读取”模型可以查询您与 ORM 支持的“写入”模型一起使用的同一数据库,或者它可以是一个单独的数据库,已针对您需要生成的报告/屏幕进行了非规范化和优化。

查看这两个演示文稿。这听起来可能有点矫枉过正(如果您的性能要求非常低,可能会这样),但令人惊讶的是,这种技术如何让这么多问题消失。

Udi Dahan: "Command-Query Responsibility Segregation" Greg Young: "Unshackle Your Domain"

【讨论】:

非常感谢您的意见,我将查看这些演示文稿并了解它们如何提供帮助,并牢记您的第一段。非常感谢!【参考方案2】:

一个好的 ORM 应该为您处理这个问题。如果您觉得必须手动操作,您可以这样做。

在单个查询中获取您需要的所有类别,并将主键 ID 存储在 PHP 数组中。

运行类似这样的查询:

mysql_query('SELECT yourListOfFieldsHere FROM Products WHERE Product_id IN ('.implode(',', $categoryIDs).')');

这应该会在一个查询中为您提供所需的所有产品。然后使用 PHP 将它们映射到正确的类别并相应地显示。

【讨论】:

只是为了提供 Kohana 方式,那就是 Db::query(Database::SELECT, $query)->execute()->as_array() ORM 可以处理这个问题。产品选择不是问题;我想我在问题描述中没有很好地解释自己,对不起!

以上是关于减少 MySQL 中具有许多一对多关系 (ORM) 的查询的主要内容,如果未能解决你的问题,请参考以下文章

在 ORM(Eloquent)中,具有 Genre 的 Book 是一对多关系吗?

创建具有多个一对多关系的 DAO 查询?

从数据库中获取具有一对多关系的特定产品

在视图中显示一对多关系sails.js waterline orm的列表

SQLAlchemy ORM 一对多关系未从数据库加载所有记录

Hibernate入门—— 一对多多对多关系