【问题标题】:Why are joins bad when considering scalability?为什么在考虑可扩展性时连接不好?
【发布时间】:2011-02-07 02:34:13
【问题描述】:

为什么连接不好或“慢”。我知道我不止一次听到过这个。我找到了这句话

问题是连接是相对的 慢,尤其是在非常大的数据上 套,如果他们很慢你 网站很慢。需要很长时间 得到所有这些单独的位 磁盘上的信息并将它们全部放入 再次在一起。

source

我一直认为他们很快,尤其是在查找 PK 时。为什么他们“慢”?

【问题讨论】:

    标签: sql join


    【解决方案1】:

    可扩展性是关于预计算(缓存)、扩展或将重复的工作缩减为最基本的工作,以最大限度地减少每个工作单元的资源使用。为了实现良好的扩展,您不需要在数量上做任何您不需要做的事情,并且您要确保尽可能高效地完成您实际做的事情。

    在这种情况下,加入两个单独的数据源当然相对较慢,至少与不加入它们相比是这样,因为这是您需要在用户请求时实时完成的工作。

    但请记住,替代方案不再有两个单独的数据;您必须将两个不同的数据点放在同一记录中。您无法在某处没有结果的情况下组合两个不同的数据,因此请确保您了解权衡取舍。

    好消息是现代关系数据库擅长连接。您真的不应该认为使用良好的数据库连接会很慢。有许多可扩展性友好的方法来获取原始连接并使它们大大更快:

    • 加入代理键(自动编号/标识列)而不是自然键。这意味着连接操作期间的比较更小(因此更快)
    • 索引
    • 物化/索引视图(将其视为预先计算的联接或托管去规范化)
    • 计算列。您可以使用它来散列或以其他方式预先计算连接的键列,这样对于连接的复杂比较现在要小得多并且可能预先编制索引。
    • 表分区(通过将负载分散到多个磁盘或将可能的表扫描限制为分区扫描来帮助处理大型数据集)
    • OLAP(预先计算某些类型的查询/连接的结果。这并不完全正确,但您可以将其视为通用非规范化)
    • 复制、可用性组、日志传送或其他机制让多台服务器回答同一数据库的读取查询,从而在多台服务器之间扩展您的工作负载。
    • 使用像 Redis 这样的缓存层来避免重新运行需要复杂连接的查询。

    我想说存在关系数据库的主要原因是允许您有效地进行联接*。当然不仅仅是存储结构化数据(您可以使用 csv 或 xml 等平面文件结构来做到这一点)。我列出的一些选项甚至可以让您提前完全构建连接,因此在您发出查询之前结果已经完成 - 就像您对数据进行了非规范化(诚然以较慢的写入操作为代价)。

    如果您的连接速度较慢,您可能没有正确使用您的数据库。

    只有在这些其他技术失败后才能进行反规范化。真正判断“失败”的唯一方法是设定有意义的绩效目标并根据这些目标进行衡量。 如果你还没有测量,那么现在考虑去规范化还为时过早。

    * 即,作为与单纯的表集合不同的实体存在。真正的 rdbms 的另一个原因是安全的并发访问。

    【讨论】:

    • 索引应该在列表的顶部。许多(咳嗽)开发人员在对小型数据集进行测试时似乎忘记了它们,然后在生产中使数据库陷入瘫痪。我已经看到,只需添加索引,查询的运行速度就会快 100,000 倍。这是任意索引,甚至没有进行任何深入的数据分析来确定最左边前缀匹配的最佳组合。
    • 我认为我的顺序是正确的——只是大多数开发人员已经做了第一项,因此索引是他们需要进行更改的第一项。
    • 在您的第三项中,您提到了“物化/索引视图”。您是在谈论常规 SQL 视图还是其他?
    • @slolife 常规 sql 视图就像当您使用引用视图的查询时在后台运行额外的查询一样。但是你也可以告诉 sql server “物化”一些视图。执行此操作时,sql server 将保留视图数据的额外副本,就像常规表一样,这样当您在查询中引用视图时,它不再需要在后台运行此查询,因为数据已经存在.您还可以在视图上放置与源表不同的索引,以进一步帮助您调整性能。
    • 谢谢乔尔。我得调查一下。
    【解决方案2】:

    也来自您引用的文章:

    许多拥有数十亿美元的超大型网站 记录,PB 级数据,许多 数以千计的同时用户,以及 每天数百万的查询是 使用分片方案,有些是 甚至提倡非规范化 架构的最佳策略 数据层。

    除非你真的很大 您可能不需要的网站 担心这种复杂程度。

    这比拥有 数据库完成所有这些工作,但你是 能够超越甚至 最高端的数据库可以处理。

    本文讨论的是 Ebay 等大型网站。在这种使用级别上,您可能不得不考虑普通关系数据库管理以外的其他东西。但是在“正常”的业务过程中(具有数千个用户和数百万条记录的应用程序),那些更昂贵、更容易出错的方法是矫枉过正的。

    【讨论】:

      【解决方案3】:

      首先,关系数据库的存在理由(存在的理由)是能够对实体之间的关系进行建模。连接只是我们遍历这些关系的机制。当然,它们的成本确实很低,但是如果没有连接,就真的没有理由拥有一个关系数据库。

      在学术界,我们学习各种范式(1st、2nd、3rd、Boyce-Codd 等),我们了解不同类型的键(主键、外键、备用键、唯一键等)。 ) 以及这些东西如何组合在一起来设计数据库。我们学习了 SQL 的基础知识以及操作结构和数据(DDL 和 DML)。

      在企业界,许多学术结构的可行性远没有我们想象的那么大。一个完美的例子是主键的概念。从学术上讲,它是唯一标识表中一行的属性(或属性集合)。因此,在许多问题领域中,正确的学术主键是 3 或 4 个属性的组合。然而,现代企业界中的几乎每个人都使用自动生成的顺序整数作为表的主键。为什么?两个原因。首先是因为当您在各处迁移 FK 时,它使模型更加简洁。第二个也是与这个问题最密切相关的是,通过连接检索数据在单个整数上比在 4 个 varchar 列上更快、更有效(正如一些人已经提到的那样)。

      现在让我们更深入地研究一下现实世界数据库的两个特定子类型。第一种类型是事务数据库。这是驱动现代网站的许多电子商务或内容管理应用程序的基础。使用事务数据库,您正在对“事务吞吐量”进行大量优化。大多数商业或内容应用程序必须平衡查询性能(来自某些表)和插入性能(在其他表中),尽管每个应用程序都有自己独特的业务驱动问题需要解决。

      第二种真实世界数据库是报告数据库。这些几乎专门用于汇总业务数据并生成有意义的业务报告。它们的形状通常不同于生成数据的事务数据库,并且针对批量数据加载 (ETL) 的速度和大型或复杂数据集的查询性能进行了高度优化。

      在每种情况下,开发人员或 DBA 都需要仔细平衡功能和性能曲线,并且在等式的两边都有很多提高性能的技巧。在 Oracle 中,您可以执行所谓的“解释计划”,以便具体了解查询是如何被解析和执行的。您正在寻求最大化数据库对索引的正确使用。一个非常讨厌的禁忌是将函数放在查询的 where 子句中。每当您这样做时,您就可以保证 Oracle 不会对该特定列使用任何索引,并且您可能会在解释计划中看到完整或部分表扫描。这只是一个具体示例,说明如何编写最终会变慢的查询,并且它与联接没有任何关系。

      当我们谈论表扫描时,它们显然会影响查询速度与表的大小成正比。 100 行的全表扫描甚至都不明显。对具有 1 亿行的表运行相同的查询,您需要下周返回。

      让我们先谈谈规范化。这是另一个可能被过度强调的积极的学术话题。大多数时候,当我们谈论规范化时,我们实际上是指通过将重复数据放入自己的表中并迁移 FK 来消除重复数据。人们通常会跳过 2NF 和 3NF 描述的整个依赖关系。然而在极端情况下,当然有可能拥有一个完美的 BCNF 数据库,该数据库非常庞大,并且是编写代码的完整野兽,因为它是如此规范化。

      那么我们在哪里平衡呢?没有单一的最佳答案。所有更好的答案往往是在易于结构维护、易于数据维护和易于代码创建/维护之间做出某种折衷。一般来说,数据重复越少越好。

      那么为什么连接有时很慢?有时它是糟糕的关系设计。有时它是无效的索引。有时这是数据量问题。有时这是一个可怕的书面查询。

      很抱歉这么冗长的回答,但我觉得有必要在我的 cmets 周围提供一个更丰富的背景,而不是仅仅喋喋不休地回答 4 子弹。

      【讨论】:

        【解决方案4】:

        与通过反规范化避免它们相比,连接可能,但如果使用正确(在具有适当索引的列上连接等等)它们本身并不慢

        如果您精心设计的数据库架构出现性能问题,您可以考虑使用反规范化技术之一。

        【讨论】:

        • ...除了在 MySQL 中,无论您的索引看起来如何,它似乎都存在大量连接的性能问题。或者至少在过去是这样的。
        • 要点,如果特定 DBMS(甚至可能是版本)存在已知问题,那么这个建议可能是有道理的,但作为一般建议,如果您使用的是关系数据库,则会产生很大的误导。也就是说,非关系存储机制正变得越来越流行 Amazon 的 SimpleDB 和 CouchDB (couchdb.apache.org) 就是示例。如果将关系模型抛在脑后,您可能会得到更好的服务,那么您可能也应该将针对后面优化的产品也抛在脑后,并寻找其他工具。
        【解决方案5】:

        Joins are fast. 连接应被视为具有适当规范化数据库模式的标准做法。联接允许您以有意义的方式联接不同的数据组。不要害怕加入。

        需要注意的是,您必须了解规范化、连接和正确使用索引。

        提防过早的优化,因为所有开发项目的第一大失败就是赶不上最后期限。一旦你完成了项目,并且了解了权衡,如果你能证明它是合理的,你就可以打破规则。

        确实,随着数据集大小的增加,连接性能会非线性下降。因此,它的扩展性不如单表查询,但它仍然可以扩展。

        没有翅膀的鸟飞得更快也是事实,但只能直下。

        【讨论】:

          【解决方案6】:

          嗯,是的,从一个非规范化表中选择行(假设您的查询有合适的索引)可能比选择通过连接多个表构建的行更快,特别是如果连接没有可用的有效索引。

          文章中引用的示例 - Flickr 和 eBay - 是 IMO 的例外情况,因此有(并且值得)例外响应。作者在文章中特别指出了RI的缺失和数据重复的程度。

          大多数应用程序 - 同样是 IMO - 受益于 RDBMS 提供的验证和减少重复。

          【讨论】:

            【解决方案7】:

            连接确实需要额外的处理,因为它们必须查看更多文件和更多索引才能将数据“连接”在一起。然而,“非常大的数据集”都是相对的。大的定义是什么?对于 JOIN,我认为它是对大型结果集的引用,而不是整个数据集。

            大多数数据库可以非常快速地处理从主表中选择 5 条记录并为每条记录连接相关表中的 5 条记录的查询(假设有正确的索引)。这些表每个可以有数亿条记录,甚至数十亿条记录。

            一旦您的结果集开始增长,事情就会放缓。使用相同的示例,如果主表产生 10 万条记录,那么将有 50 万条“连接”记录需要查找。只是从数据库中提取这么多数据并增加延迟。

            不要避免 JOIN,只要知道当数据集变得“非常大”时您可能需要优化/反规范化。

            【讨论】:

              【解决方案8】:

              拥有 TB 级数据库的人仍然使用联接,如果他们能让它们在性能方面发挥作用,那么您也可以。

              有很多理由不去规范化。首先,选择查询的速度并不是数据库的唯一甚至主要关注点。数据的完整性是首要关注的问题。如果您进行非规范化,那么您必须采用适当的技术来保持数据在父数据更改时进行非规范化。因此,假设您将客户端名称存储在所有表中,而不是在 client_Id 上加入客户端表。现在,当客户端的名称发生变化时(100% 的机会一些客户端的名称会随着时间而变化),现在您需要更新所有子记录以反映该变化。如果您这样做是为了进行级联更新并且您有一百万个子记录,那么您认为这将是多快以及有多少用户会遇到锁定问题和工作延迟?此外,由于“连接速度很慢”而进行非规范化的大多数人对数据库的了解不足,无法正确确保其数据完整性受到保护,并且由于完整性太差,通常最终会得到具有不可用数据的数据库。

              非规范化是一个复杂的过程,如果要正确完成,需要对数据库性能和完整性有透彻的了解。除非您对员工有这样的专业知识,否则不要尝试去规范化。

              如果你做几件事情,连接就足够快了。首先使用 suggorgate 键,int 连接几乎是最快的连接。其次总是索引外键。使用派生表或连接条件创建一个较小的数据集进行过滤。如果您有一个非常复杂的大型数据库,请聘请具有分区和管理大型数据库经验的专业数据库人员。有很多技术可以在不消除连接的情况下提高性能。

              如果您只需要查询功能,那么是的,您可以设计一个可以非规范化的数据仓库,并通过 ETL 工具(针对速度进行了优化)而不是用户数据输入来填充。

              【讨论】:

                【解决方案9】:

                虽然连接(可能是由于规范化设计)对于数据检索显然比从单个表中读取要慢,但非规范化数据库对于数据创建/更新操作可能会很慢,因为整个事务的足迹不会最小。

                在规范化数据库中,一条数据将只存在于一个位置,因此更新的占用空间将尽可能小。在非规范化数据库中,可能需要更新多行或跨表中的同一列,这意味着占用空间会更大,并且可能会增加锁定和死锁的机会。

                【讨论】:

                  【解决方案10】:

                  如果

                  ,连接会很慢
                  • 数据索引不正确
                  • 过滤结果不佳
                  • 加入查询写得不好
                  • 数据集非常庞大和复杂

                  确实,数据集越大,查询所需的处理就越多,但检查和处理上述前三个选项通常会产生很好的结果。

                  您的来源提供了非规范化作为选项。只要您已经用尽了更好的选择,这很好。

                  【讨论】:

                    【解决方案11】:

                    如果需要扫描来自每一方的大部分记录,则连接可能会很慢。

                    像这样:

                    SELECT  SUM(transaction)
                    FROM    customers
                    JOIN    accounts
                    ON      account_customer = customer_id
                    

                    即使在account_customer 上定义了索引,仍然需要扫描来自后者的所有记录。

                    对于这个查询列表,体面的优化器可能甚至不会考虑索引访问路径,而是使用HASH JOINMERGE JOIN

                    请注意,对于这样的查询:

                    SELECT  SUM(transaction)
                    FROM    customers
                    JOIN    accounts
                    ON      account_customer = customer_id
                    WHERE   customer_last_name = 'Stellphlug'
                    

                    连接很可能会很快:首先,customer_last_name 上的索引将用于过滤所有 Stellphlug(当然数量不多),然后将针对 account_customer 发出索引扫描每个 Stellphlug 都可以找到他的交易。

                    尽管accountscustomers 中可能有数十亿条记录,但实际上只有少数需要扫描。

                    【讨论】:

                    • 但很难避免。设计您的应用,使此类查询不会经常执行。
                    • 如果在 accounts(account_customer) 上定义了索引,大多数 RDBMS 将使用该索引来准确找出需要扫描 customers 数据库的哪些行。
                    • 是的,但无论如何它并不便宜。您可以将总和存储在某个字段中并在每笔交易中更新。
                    • @jemfinch:不,他们不会。这将需要扫描整个索引以过滤掉客户,然后在嵌套循环中扫描客户的索引。 HASH JOIN 会快得多,所以除了MySQL 之外的所有主要数据库都将使用它,这只会使customers 在嵌套循环中领先(因为它的大小更小)
                    【解决方案12】:

                    正确设计的表包含正确的索引和正确编写的查询并不总是很慢。你在哪里听说过:

                    为什么连接不好或“慢”

                    不知道他们在说什么!!!大多数连接将非常快。如果您必须一次连接许多行,与非规范化表相比,您可能会受到打击,但这可以追溯到正确设计的表,知道何时进行非规范化以及何时不进行非规范化。在繁重的报表系统中,将非规范化表格中的数据分解为报表,甚至创建数据仓库。在事务繁重的系统中规范化表。

                    【讨论】:

                      【解决方案13】:

                      根据连接,生成的临时数据量可能很大。

                      例如,这里的一个数据库具有通用搜索功能,其中所有字段都是可选的。搜索例程在搜索开始之前对每个表进行了连接。这在开始时运作良好。但是,现在主表有超过 1000 万行......不是那么多。现在搜索需要 30 分钟或更长时间。

                      我的任务是优化搜索存储过程。

                      我做的第一件事是,如果正在搜索主表的任何字段,我只对这些字段的临时表进行了选择。然后,在进行其余的搜索之前,我将所有表与该临时表连接起来。现在可以在不到 10 秒的时间内搜索其中一个主表字段。

                      如果没有开始搜索主表字段,我会对其他表进行类似的优化。完成后,搜索时间不会超过 30 秒,大多数都在 10 秒以下。

                      SQL 服务器的 CPU 利用率也下降了。

                      【讨论】:

                      • @BoltBait:在执行连接之前,您应该始终尝试减少行数吗?
                      • 它在我的情况下确实创造了奇迹。但是,除非有必要,否则我不会优化系统。
                      • 通常不会在连接上生成临时数据(当然取决于选择性、可用内存和连接缓冲区的大小),AFAIK;但是,如果没有可用于此类操作的索引,则临时数据通常是按 order by 和 distinct 创建的。
                      【解决方案14】:

                      联接被认为是与可扩展性相反的力量,因为它们通常是瓶颈,并且不能轻松分布或并行。

                      【讨论】:

                      • 我不确定这是不是真的。我知道 Teradata 肯定能够在 Amps 之间分配连接。显然,某些类型的连接可能比其他连接更棘手/难以处理。
                      • 索引可以在 RDBMS 中进行分区,范围从 mysql 到 oracle。可扩展的 AFAIK(分布式且可以并行)。
                      【解决方案15】:

                      如果做得草率,它们可能会很慢。例如,如果您在连接上执行“选择 *”,您可能需要一段时间才能恢复。但是,如果您仔细选择要从每个表返回的列,并使用适当的索引,应该没有问题。

                      【讨论】:

                        【解决方案16】:

                        文章说,与没有连接相比,它们很慢。这可以通过非规范化来实现。所以在速度和标准化之间有一个权衡。不要忘记过早的优化:)

                        【讨论】:

                        • 即使这不是一个硬性规则,如果你在一个表上连接,mysql 可能会使用一个索引来执行那个连接——那个索引连接可以修剪很多行,另一个索引用于任何 where 子句表。如果你不加入,mysql 通常只会使用一个索引(这可能不是最有效的一个),不管你的 where 子句是如何形成的。
                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 2012-10-07
                        • 1970-01-01
                        • 2015-08-09
                        • 2018-05-14
                        • 2019-02-24
                        • 1970-01-01
                        • 2018-05-20
                        相关资源
                        最近更新 更多