在 MySQL 中,对于连接谓词之间具有多对多关系的大型表,最有效的查询设计是啥?

Posted

技术标签:

【中文标题】在 MySQL 中,对于连接谓词之间具有多对多关系的大型表,最有效的查询设计是啥?【英文标题】:In MySQL, what is the most effective query design for joining large tables with many to many relationships between the join predicates?在 MySQL 中,对于连接谓词之间具有多对多关系的大型表,最有效的查询设计是什么? 【发布时间】:2011-01-27 05:41:03 【问题描述】:

在我们的应用程序中,我们收集有关汽车发动机性能的数据——基本上是基于发动机类型、运行发动机的车辆和发动机设计的发动机性能源数据。目前,新行插入的基础是引擎开关周期;我们根据发动机状态从活动到非活动的变化来监控性能变量,反之亦然。相关的engineState 表如下所示:

+---------+-----------+---------------+---------------------+---------------------+-----------------+
| vehicle | engine    | engine_state  | state_start_time    | state_end_time      | engine_variable |
+---------+-----------+---------------+---------------------+---------------------+-----------------+
| 080025  | E01       | active        | 2008-01-24 16:19:15 | 2008-01-24 16:24:45 |             720 | 
| 080028  | E02       | inactive      | 2008-01-24 16:19:25 | 2008-01-24 16:22:17 |             304 |
+---------+-----------+---------------+---------------------+---------------------+-----------------+ 

对于具体的分析,我们希望基于分钟的行粒度来分析表格内容,而不是基于当前的活动/非活动引擎状态。为此,我们正在考虑创建一个简单的productionMinute 表,在我们分析的时间段内每分钟有一行,并在每个表的日期时间列上加入productionMinuteengineEvent 表。因此,如果我们的分析时间段是从 2009 年 12 月 1 日到 2010 年 2 月 28 日,我们将创建一个包含 129,600 行的新表,在这三个月的每一天每一分钟都有一个。 productionMinute 表的前几行:

+---------------------+ 
| production_minute   |
+---------------------+
| 2009-12-01 00:00    |
| 2009-12-01 00:01    |
| 2009-12-01 00:02    |     
| 2009-12-01 00:03    |
+---------------------+

表之间的连接将是:

     FROM engineState AS es 
LEFT JOIN productionMinute AS pm ON pm.production_minute >= es.state_start_time 
                                AND pm.production_minute <= es.event_end_time 

然而,这种加入带来了多个环境问题:

    engineState 表有 500 万行,productionMinute 表有 130,000 行 当engineState行跨度超过一分钟(即es.state_start_timees.state_end_time之间的差异大于一分钟),如上例中的情况,有多个productionMinute表行连接到单个 engineState 表行 如果在任何给定分钟内有多个引擎在运行,同样按照上面的示例,多个 engineState 表行将连接到单个 productionMinute

在测试我们的逻辑并仅使用一个小表提取(对于productionMinute 表,一天而不是 3 个月)查询需要一个多小时才能生成。在研究这个项目时为了提高性能以便查询三个月的数据是可行的,我们的想法是从engineEvent创建一个临时表,消除任何对分析不重要的表数据,并且将临时表加入productionMinute 表。我们还计划尝试不同的连接(特别是内部连接),看看这是否会提高性能。

连接谓词之间具有多:多关系的表的最佳查询设计是什么?什么是最好的连接类型(左/右,内)?

【问题讨论】:

您尝试生成哪种报告的具体示例会有所帮助。您很有可能不需要扩展为每分钟的观察结果,并且可以直接构建您的结果。另外,您的 engineState 表上有哪些索引? 您的第 2 和第 3 项投诉不是环境问题,而是设计问题。我的意思是,我看不出它们中的任何一个有什么问题——它们是真的,因为你已经以这种方式布置了你的数据。您需要描述为什么您认为这是一个问题,并清楚说明您对所编写的连接有什么期望(您想为其分配什么语义含义:D)。 【参考方案1】:

使用 LEFT JOIN、INNER JOIN 或 RIGHT JOIN 是语义上的差异 - 使用不同的连接以获得 性能 不仅仅是一个坏主意,它意味着表之间的关系没有完全理解 - 因为不同的 JOIN 类型可以返回不同的信息,因为它们的含义不同。

通常,INNER JOIN 对优化器非常友好,因为这允许将 WHERE 子句中的不同过滤条件和 JOIN 条件推到更多地方,以改进索引扫描或表扫描。参照完整性约束还可以为优化器提供有关数据保证在双方都存在的信息。

您应该查看您的执行计划并查看您的索引策略。理想情况下,您希望缩小覆盖范围的索引,并且希望在您的计划中看到索引搜索、索引扫描、表扫描(按优先顺序)。

通常,您希望您的模型针对事务处理进行规范化并针对报告进行非规范化,但是一开始处理两个模型很烦人,因此您首先尝试对规范化数据进行报告和分析,这可以用于一段时间有更好的索引并查看执行计划。

当您的报告在索引良好的范式上变得太差时,我会考虑将数据转换为可能具有星型模式的维度模型(看看 Kimball 的方法),该模式具有非常简单的报告模式(通常是所有 INNER JOIN 和一个简单的星号),并且可以在传统数据库系统上得到很好的优化。

【讨论】:

【参考方案2】:

如果我理解正确,您正在调查 BI 问题。 BI 布局将操作数据与合并数据分开。

要做到这一点(快速而肮脏),您需要三个元素。

您的手术数据 ETL 作业,只需要执行您显示的查询并将结果集插入另一个非规范化表中 您将在其中保存整合数据的非规范化表。

这样您将加快查询速度,因为它现在是一个简单的选择。

与任何 BI 解决方案一样,您需要每天运行 ETL(取决于您的业务需求)以更新您的非规范化信息。

另一方面,您可以拒绝 BI 方式并处理您当前的架构/查询。您可以添加索引、统计信息、更改表,但我认为这不是一个可扩展的解决方案。您可以解决三个月大的数据库的性能问题,但如果您有一个三年大的数据库怎么办?

【讨论】:

【参考方案3】:

我同意 vy32。您只需执行一次此查询,即可以适合分析的格式获取数据。您应该使用适当的 ETL 工具(或者,只是 perl 或其他简单工具)从 engineState 表中获取数据,计算生产分钟数,然后将其加载到另一个为分析类型查询正确建模的数据库中。

如果您认为您的问题只是对数据进行非规范化并将分钟数分配为代理键。这是一个相对简单(且常见)的 ETL 问题,在直接 SQL 中性能不高,但在其他语言和工具中很简单。

真正的 ETL 流程可以轻松处理您的生产量。

【讨论】:

【参考方案4】:

我的经验是 mysql 查询优化器非常糟糕。 PostgreSQL中的那个要好得多。

您的问题是您的数据是为了便于记录而不是为了便于分析而构建的。我的建议是你继续创建临时表,但不是你想象的那样。我认为您最好的选择是在每天结束时进行后处理步骤,该步骤获取当天的所有数据,并在具有 production_minute 索引的新表(理想情况下在不同的主轴上)中创建每分钟的条目。这个新数据库可以更快地进行分析查询,并且查询不会显着减慢数据收集速度。

【讨论】:

【参考方案5】:

数据检索性能是函数

访问磁盘上数据的速度(取决于 在索引的存在,大小 表、缓存大小、原始 I/O 速度) 需要的记录数 返回(一些连接减少 返回的行数,一些 增加,一些条件可以 应用于一些必须去的索引 记录) 的列数 你需要返回

所有这些你都可以优化

添加索引 通过垂直分区来减小表的大小(将表拆分为两个或多个语义不同的表 - 例如,如果从您的 5m 表中,您实际上在 99.5% 的时间只处理 100k 条记录,也许您可​​以将表拆分为活动/非活动或类似) 如果您不能垂直拆分,您可能会水平拆分表格 - 表格的列数也会影响检索速度(但影响不大) 最终提高原始 I/O 速度可以通过在多个硬盘之间透明地拆分表来实现(但要了解您的底层文件系统属性)

索引对性能的影响最大,因为它们可以将磁盘访问时间和内存操作的速度减少几个数量级(它们以维护索引结构为代价将 O(n) 变为 log O(n);所以他们确实会减慢更新速度)

为了获得最大的检索速度,索引应涵盖所有连接以及条件和查询的编写方式,以便查询优化器可以确定如果首先执行这些将产生最大收益(最高选择性)。

对于您的特定示例,尝试对不同的索引组合进行基准测试

    pm.production_minute 肯定应该被编入索引 使用 es.state_start_time 和 es.state_end_time 您有 4 个可能的索引选项(可以组合): es.state_start_time 上的索引 es.state_end_time 上的索引 索引 (es.state_start_time, es.state_end_time) 索引 (es.state_end_time, es.state_start_time)

了解您的数据可以让您确定哪一个是最佳的。如果您发现最后两个两列索引的性能最好,我不会感到惊讶。或者有一个单列和另外两列索引(但列的顺序相反)。

在这两种情况下,体面的优化器将能够仅通过读取索引而不查看实际记录来确定结果集,从而大大减少了您的磁盘访问。

【讨论】:

【参考方案6】:

性能取决于表中数据的结构。

仅当您希望左表或右表中的所有值都用于所选投影并且这些值可能在要连接的表中没有某些内容时,左或右外连接才有用。

相信您的查询优化器会为您的数据找到最有效的连接算法...它的构建是为了知道如何做好它的工作。如果您遇到性能问题,请查看数据的结构和存储方式。

【讨论】:

谢谢杰里米;但这正是我要问的问题——在处理连接谓词之间的多:多关系和处理大型数据集时,我们应该如何(重新)构造和存储表中的数据以优化查询性能?请记住,我们与当前的设计无关,因为我们可以使用临时表来重构数据并将索引放在连接谓词上……但这种方法是否适用于面临类似性能挑战的其他人?如果没有,有哪些有效的方法? 但这不是你问的问题。您专门询问了联接。如果您有一个非常大的数据集并且您有多个要索引的字段,最好使用 B+ 树来索引您的字段。在执行查询时,几乎在所有情况下都需要更少的 IO。我不确定 MySQL 对您可以采用的索引技术有多少控制,但如果您有选择,请选择它。如果您别无选择,那么我怀疑它已经使用 B+ 树进行索引,并且指定要索引的字段应该涵盖您。 感谢杰里米的转发。我相信 MySQL 确实允许我们指定要使用的索引类型。我们将进一步研究此选项,然后我会回复我们的发现。

以上是关于在 MySQL 中,对于连接谓词之间具有多对多关系的大型表,最有效的查询设计是啥?的主要内容,如果未能解决你的问题,请参考以下文章

具有多对多关系的 NSPredicate

JPA CriteriaQuery 多对多谓词

如何在 MySQL 的多对多关系中以逗号分隔的列表中的一个字段连接数据?

Hibernate Annotation - 如何连接三个具有多对多关系的表

CoreData 谓词 ANY + AND 与多对多关系

使用 PHP/CodeIgniter 从具有多对多关系的两个表中显示数据