在考虑可伸缩性时,为什么联接不好?


92

为什么联接不好或“慢”。我知道我再听一次。我找到了这句话

问题是联接相对较慢,尤其是在非常大的数据集上,如果联接较慢,则您的网站也很慢。从磁盘上获取所有这些单独的信​​息位并将它们重新组合在一起需要很长时间。

资源

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

sql  join 

Answers:


98

可伸缩性是关于预先计算,分散或将重复的工作缩减为基本要素的全部目的,以最大程度地减少每个工作单元的资源使用。为了实现良好的伸缩性,您无需做任何不需要做的事情,而您实际上要确保所做的事情能尽可能高效地完成。

在这种情况下,连接两个单独的数据源当然相对较慢,至少与不将它们连接起来相比,这是因为您需要在用户要求的时候进行工作。

但是请记住,替代方案是根本不再拥有两个单独的数据。您必须将两个不同的数据点放在同一记录中。您不能在没有任何结果的情况下将两个不同的数据合并在一起,因此请确保您了解了取舍。

好消息是现代关系数据库擅长联接。您不应该真的认为使用良好的数据库会导致连接缓慢。有许多的可扩展性,友好的方式吃生的加入,让他们快:

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

我会说关系数据库根本存在的主要原因是允许您高效地进行联接*。当然,不仅仅是存储结构化数据(您可以使用csv或xml等平面文件结构来实现此目的)。我列出的一些选项甚至可以让您预先完全建立连接,因此在发出查询之前结果已经完成-就像您对数据进行了非规范化一样(以降低写操作速度为代价)。

如果加入速度较慢,则可能是您未正确使用数据库。

只有在这些其他技术都失败之后,才能进行非规范化。真正判断“失败”的唯一方法是设定有意义的性能目标并根据这些目标进行衡量。如果您还没有衡量的话,甚至考虑去规范化还为时过早。

*即,作为实体存在于表的单独集合之外。真正的rdbms的另一个原因是安全的并发访问。


14
索引应该在列表的顶部。许多(咳嗽)的开发人员在对小型数据集进行测试时似乎忘记了它们,然后将数据库投入生产。我发现仅通过添加索引,查询运行速度就快了100,000倍。而且这是任意索引,甚至不需要进行任何深入的数据分析即可确定最左边的前缀匹配的最佳组合。
邓肯

我认为我的命令正确无误-只是大多数开发人员已经在做第一件事,因此索引是他们需要进行更改的第一件事。
Joel Coehoorn

在您的第三项中,提到“材料化/索引视图”。您是在谈论常规SQL视图还是其他?
slolife 2010年

@slolife常规sql视图就像在使用引用视图的查询时在后台运行额外的查询一样。但是您也可以告诉sql server“实现”一些视图。当您执行此操作时,SQL Server将像常规表一样保留视图数据的额外副本,这样,当您在查询中引用该视图时,由于数据已经存在,因此不再需要在后台运行此查询。 。您还可以在视图上放置与源表不同的索引,以进一步帮助您调整性能。
Joel Coehoorn

谢谢乔尔。我必须调查一下。
slolife 2010年

29

连接可能比通过反规范化避免连接要慢,但是如果正确使用(在具有适当索引的列上连接等等),它们并不是天生就不会慢

如果设计良好的数据库架构出现性能问题,则可以考虑使用非规范化许多优化技术之一。


2
...在MySQL中除外,在MySQL中,无论您的索引如何,似乎都有大量连接的性能问题。或者至少是过去。
Powerlord

2
要点是,如果特定DBMS(甚至可能是版本)存在已知问题,则此建议可能有意义,但作为一般建议,如果您使用关系数据库,则很容易引起误解。也就是说,非关系存储机制正变得越来越流行,例如Amazon的SimpleDB和CouchDB(ouchdb.apache.org)。如果将关系模型抛在后面能为您提供更好的服务,那么您可能应该也将针对这种模型进行优化的产品抛在脑后,并寻找其他工具。
Tendayi Mawushe

13

文章说,与没有联接相比,它们速度较慢。这可以通过非规范化来实现。因此在速度和规范化之间需要权衡。也不要忘记过早的优化:)


即使这不是硬性规定,如果在表上进行连接,mysql可能会使用索引来执行该连接-该索引连接可能会删减许多行,并为表上的任何where子句提供另一个索引。如果您不加入,则无论您的where子句如何形成,mysql通常只会使用一个索引(可能不是最有效的索引)。
leeeroy

11

首先,关系数据库的存在理由(存在的理由)将能够为实体之间的关系建模。联接只是我们遍历这些关系的机制。当然,它们确实需要象征性的代价,但是如果没有联接,则实际上没有理由拥有关系数据库。

在学术界,我们学习诸如各种正常形式(第一,第二,第三,博伊斯-科德等)之类的东西,并且了解不同类型的键(主键,外键,交替键,唯一键等)以及如何使用这些东西适合设计数据库。而且,我们学习了SQL的基本知识以及结构和数据(DDL和DML)。

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

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

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

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

而且,当我们谈论表扫描时,它们显然会与表的大小成比例地影响查询速度。对100行的全表扫描甚至没有引起注意。在具有1亿行的表上运行相同的查询,您需要在下周返回以获得收益。

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

那么我们在哪里平衡呢?没有一个最佳答案。所有更好的答案往往是在结构维护的便利性,数据维护的便利性和代码创建/维护的便利性之间做出了一些折衷。通常,重复数据越少越好。

那么,为什么有时加入速度慢?有时这是糟糕的关系设计。有时这是无效的索引。有时是数据量问题。有时这是一个可怕的书面查询。

抱歉,这么冗长的回答,但是我感到不得不为我的评论提供一个更加具体的背景,而不是仅仅听从四子弹的回应。


10

具有terrabyte大小的数据库的人仍在使用联接,如果他们可以使它们按性能运行,那么您也可以。

有很多原因不进行降级处理。首先,选择查询的速度并不是数据库的唯一或什至主要关注的问题。数据的完整性是首要考虑的问题。如果您进行非规范化,则必须采用适当的技术来使数据在父数据更改时保持非规范化。因此,假设您要在所有表中存储客户端名称,而不是在client_Id上加入客户端表。现在,当客户端名称更改时(某些客户端名称随时间变化的可能性为100%),现在您需要更新所有子记录以反映该更改。如果您进行级联更新并拥有一百万条子记录,那么您认为这种记录会以多快的速度发生?有多少用户在发生这种情况时会遇到锁定问题和工作延迟?此外,由于“

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

如果您做几件事,联接就足够快了。首先使用Suggorgate键,一个int联接几乎是最快的联接。第二,总是索引外键。使用派生表或联接条件创建一个较小的数据集进行过滤。如果您有一个非常复杂的大型数据库,请雇用一个具有丰富的分区和管理经验的专业数据库人员。有很多技术可以提高性能而又不脱离联接。

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


8

如果连接缓慢

  • 数据索引不正确
  • 结果过滤不良
  • 连接查询写得不好
  • 数据集非常大而复杂

因此,的确,您的数据集越大,查询所需要的处理就越多,但是检查并使用上述前三个选项通常会产生很好的结果。

您的源代码提供了非规范化选项。仅当您已经用尽更好的替代方法时,这才是好的。


7

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

像这样:

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

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

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

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

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

连接很可能会很快:首先,customer_last_name将使用索引on 过滤所有Stellphlug(当然不是很多),然后account_customer将为每个Stellphlug发出索引扫描以查找他的交易。

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


但是很难避免。设计您的应用程序,以使此类查询不会经常执行。
安德烈(Andrey)2010年

1
如果在accounts(account_customer)大多数RDBMS 上定义了索引,则该索引将使用该索引来准确找出customers需要扫描数据库的哪些行。
jemfinch

是的,但是这绝对不是便宜的操作。您可以将总和存储在某个字段中,并在每次交易中进行更新。
Andrey 2010年

@jemfinch:不,他们不会。这将需要扫描整个索引只是为了过滤掉客户,然后在嵌套循环中扫描客户的索引。A HASH JOIN会快得多,因此将在所有主要数据库中使用,除了MySQL,它将除外,这只会customers导致嵌套循环(因为它的大小更小)
Quassnoi

4

Joins are fast.联接应被视为具有适当规范化数据库架构的标准实践。联接使您能够以有意义的方式联接不同的数据组。不要害怕加入。

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

提防过早的优化,因为所有开发项目中的第一大失败就是在最后期限之前完成。一旦完成了项目,并且了解了折衷方案,只要可以证明其合理性,就可以违反规则。

的确,随着数据集大小的增加,联接性能会非线性下降。因此,它不能像单表查询那样很好地扩展,但是仍然可以扩展。

确实,一只鸟没有任何翅膀就可以飞得更快,但只能直下。


3

联接确实需要额外的处理,因为它们必须查找更多文件和更多索引以将数据“联接”在一起。但是,“非常大的数据集”都是相对的。large的解释是什么?就JOIN而言,我认为它是对大型结果集的引用,而不是对整个数据集的引用。

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

一旦结果集开始增长,事情就会变慢。使用同一示例,如果主表产生100K条记录,则将需要找到500K条“联接”记录。只是将大量数据从数据库中拉出会增加延迟。

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


3

同样从您引用的文章中:

每天都有数十亿个记录,PB级数据,数千个并发用户以及数百万个查询的许多大型网站正在使用分片方案,甚至有人主张将非规范化作为构建数据层的最佳策略。

而且,除非您是一个非常大的网站,否则您可能不必担心这种复杂性。

比让数据库完成所有这些工作更容易出错,但是您可以扩展甚至超过最高端数据库可以处理的范围。

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


2

联接被认为是可伸缩性的反作用力,因为它们通常是瓶颈,并且不能轻易分布或并行化。


我不确定这是真的。我知道Teradata当然可以在Amps之间分发联接。显然,某些类型的连接可能比其他类型更棘手/更棘手。
Cade Roux

索引可以在RDBMS中进行分区,范围从mysql到oracle。可缩放的AFAIK(分布式且可以并行)。
Unreason

2

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

为什么联接不好或“慢”

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


1

基于联接,​​生成的临时数据量可能很大。

例如,这里工作的一个数据库具有通用搜索功能,其中所有字段都是可选的。搜索例程在搜索开始之前对每个表进行了联接。在一开始就很好。但是,现在主表已经超过一千万行了……还不行。现在,搜索需要30分钟或更长时间。

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

我做的第一件事是,如果要搜索主表中的任何字段,我只对那些字段上的临时表进行选择。然后,在进行其余搜索之前,我将所有表与该临时表连接在一起。现在搜索主表字段之一所需的时间少于10秒。

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

SQL Server的CPU使用率也下降了。


@BoltBait:是外卖消息,您应该在执行联接之前始终尝试减少行数吗?
unutbu

在我看来,这无疑创造了奇迹。但是,除非有必要,否则我不会优化系统。
BoltBait 2010年

通常,联接不会生成临时数据(当然取决于选择性,可用内存和联接缓冲区的大小),AFAIK;但是,临时数据通常是按顺序创建的,如果没有可用于此类操作的索引,则它们是不同的。
Unreason 2010年

1

尽管联接(可能归因于规范化的设计)对于数据检索而言显然比从单个表中读取要慢,但由于规范化数据库的总事务量不会最小,因此非规范化的数据库对于数据创建/更新操作可能较慢。

在规范化的数据库中,一条数据将仅存放在一个地方,因此更新的占用空间将尽可能少。在非规范化数据库中,可能必须更新多行或跨表的同一列,这意味着占用空间会更大,并且出现锁和死锁的机会会增加。


1

是的,是的,从一个非规范化表中选择行(假设查询要有不错的索引)可能比选择从多个表中联接而构造的行更快,尤其是在联接没有有效索引可用的情况下。

文章中引用的示例-Flickr和eBay-是IMO的例外情况,因此有(应得到)例外的回应。作者在文章中特别提到了RI的缺乏和数据重复的程度。

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


0

如果草率地做,它们可能会变慢。例如,如果对联接执行“选择*”,则可能需要一段时间才能将东西取回。但是,如果您仔细选择要从每个表中返回哪些列,并且具有适当的索引,则应该没有问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.