SQL查询:大表之间的内连接优化

时间:2009-02-04 13:54:09

标签: sql mysql optimization inner-join bigtable

我在MySQL 4.x DB中有以下3个表:

  • hosts:(300.000条记录)
    • id(UNSIGNED INT)PRIMARY KEY
    • name(VARCHAR 100)
  • 路径:(6.000.000条记录)
    • id(UNSIGNED INT)PRIMARY KEY
    • name(VARCHAR 100)
  • urls:(7.000.000条记录)
    • host(UNSIGNED INT)PRIMARY KEY< ---链接到hosts.id
    • path(UNSIGNED INT)PRIMARY KEY< ---链接到paths.id

正如您所看到的,架构非常简单,但问题是这些表中的数据量。

这是我正在运行的查询:

SELECT CONCAT(H.name, P.name)
FROM hosts AS H
INNER JOIN urls as U ON H.id = U.host
INNER JOIN paths AS P ON U.path = P.id;

此查询工作正常,但需要50分钟才能运行。有没有人知道如何加快查询速度?

提前致谢。 尼古拉斯

14 个答案:

答案 0 :(得分:6)

也许您应该包含WHERE子句?或者您真的需要所有数据吗?

答案 1 :(得分:4)

在我看来,过度热衷于使用代理键会减慢你的速度。如果表格是:

  • 主持人:

    • name(VARCHAR 100)PRIMARY KEY
  • 路径:

    • name(VARCHAR 100)PRIMARY KEY
  • 网址:

    • host(VARCHAR 100)PRIMARY KEY< --- links to hosts.name
    • path(VARCHAR 100)PRIMARY KEY< --- links to paths.name

然后你的查询根本不需要加入:

SELECT CONCAT(U.host, U.path) FROM urls U;

是的,表格URL会占用更多磁盘空间 - 但这有关系吗?

编辑:再想一想,PATHS表的重点是什么?不同主机多长时间共享相同的路径?

为什么不:

  • 主持人:

    • name(VARCHAR 100)PRIMARY KEY
  • 网址:

    • host(VARCHAR 100)PRIMARY KEY< --- links to hosts.name
    • path(VARCHAR 100)PRIMARY KEY< ---没有链接到任何地方

EDIT2 或者,如果你真的需要主机的代理键:

  • 主持人:

    • id integer PRIMARY KEY
    • name(VARCHAR 100)
  • 网址:

    • host integer PRIMARY KEY< --- links to hosts.name
    • path(VARCHAR 100)PRIMARY KEY< ---没有链接到任何地方

    SELECT CONCAT(H.name,U.path)FROM urls U. JOIN主持人H ON H.id = U.host;

答案 2 :(得分:2)

首先,我不会在查询中执行CONCAT。在外面做。

但实际上你的查询运行缓慢,因为你正在检索数百万行。

答案 3 :(得分:2)

总的来说,最好的建议是跟踪和分析,看看真正占用时间的是什么。但这是我对具体事情的看法。

(1)我想说你要确保在执行这个查询时不使用索引。由于您没有过滤条件,因此完全扫描所有表并使用排序合并或散列操作将它们连接在一起应该更有效。

(2)字符串连接肯定需要一些时间,但我不明白为什么人们建议删除它。那么你可能需要在另一段代码中进行连接,它仍然需要大约相同的时间(除非MySQL的字符串连接由于某种原因特别慢)。

(3)从服务器到客户端的数据传输可能花费大量时间,可能比服务器需要获取数据的时间长。如果您有工具来跟踪此类事物,请使用它们。如果您可以在客户端增加获取数组大小,请尝试不同的大小(例如在JDBC中使用Statement.setFetchSize())。即使客户端和服务器位于同一主机上,这也可能很重要。

答案 4 :(得分:1)

您是否已在join-attributes上声明了一些索引?

PS:有关MySQL 4.x的索引,请参阅 here [已损坏的链接]

答案 5 :(得分:1)

在运行查询之前尝试优化表:

optimize table hosts, paths, urls;

它可能会节省您一些时间,特别是如果已从表中删除行。 (有关OPTIMIZE的更多信息,请参阅here

答案 6 :(得分:1)

我尝试使用您想要的数据创建一个新表。这样做意味着你丢失了一些真实的数据,但你的速度很快。这个想法可能类似于OLAP或类似的东西吗?

当然,您必须对此表进行更新(每日或其他)。

答案 7 :(得分:1)

我不是MySQL专家,但看起来MySQL主键是群集的 - 你要确保你的主键是这样的;聚簇索引肯定有助于加快速度。

但有一件事 - 我不相信你可以在任何桌子上有两个“主要”钥匙;因为这个原因,你的网址表看起来很可疑。最重要的是,你应该绝对确保urls表中的这两列被索引到hilt - 每个列上的单个数字索引应该没问题 - 因为你加入它们,所以DBMS需要知道如何快点找到它们;这可能是你的情况。如果你是全表扫描那么多行,那么是的,当服务器试图找到你要求的所有内容时,你可能会坐在那里很长一段时间。

我还建议从select语句中删除该CONCAT函数,并查看它对结果的影响。如果不是某种促成因素,我会感到惊讶。只需检索两列并在之后处理串联,看看情况如何。

最后,你知道瓶颈在哪里吗?只要表格被正确编入索引,只需加入三个数百万行的表就不会花费太多时间(我希望可能只需要一秒左右的时间,只需观察表和查询)。但是如果你把这些行推到一个缓慢的或已经挂掉的网卡,一个内存匮乏的应用服务器等等,那么缓慢可能与你的查询无关,而是与查询后发生的事情有关。无论这些行的发现需要多长时间,七百万行都是相当多的数据要组装和移动。尝试只选择一行,而不是全部七百万,并看看相比之下。如果这很快,那么问题不是查询,而是结果集。

答案 8 :(得分:1)

当您的结果集返回所有数据时,几乎没有任何优化可以完成。您正在扫描整个表,然后加入其他具有索引的表。

PrimaryKeys是否聚集?这可确保数据以索引顺序存储在磁盘上,从而避免在磁盘的不同部分周围弹跳。

此外,您可以将数据分布在多个磁盘上。如果在SECONDARY上有PRIMARY和PATHS / HOSTS的URL,那么你将从驱动器获得更好的吞吐量。

答案 9 :(得分:1)

您需要查看服务器配置。 MySQL的默认内存参数会削弱大小的表的性能。如果您使用默认值,则需要至少将key_buffer_sizejoin_buffer_size提高至少4倍,或许更多。查看文档;您可以调整其他内存参数。

MySQL有一个有趣的性能怪癖,如果你的表超过一定大小的查询将返回大部分数据,性能进入厕所。不幸的是,它没有办法告诉你什么时候达到这个阈值。不过,它看起来像你一样。

答案 10 :(得分:0)

concat肯定会让你失望。我们可以看一下mysql的结果吗? Documentation Link

要做的最重要的事情是尝试仅提取您需要的数据。如果你可以拉出更少的记录,那将会加速你的速度。但是mysql解释应该可以帮助我们看看是否有任何索引可以帮助。

答案 11 :(得分:0)

据我所知,您需要一个完整的网址列表 - 这是700万条记录。 也许as sugested by Mitch您应该考虑使用WHERE子句来过滤结果。 也许时机主要与显示记录的延迟有关

检查此查询的时间

select count(*)
FROM hosts AS H
INNER JOIN urls as U ON H.id = U.host
INNER JOIN paths AS P ON U.path = P.id

如果这仍然很慢,我会去检查时间     从网址中选择计数(*)

然后

select count(*) 
from urls u 
inner join hosts h on u.host = h.id

然后

select count(*) 
from urls u 
inner join hosts h on u.host = h.id
inner join paths p on u.path = p.id

只是为了找到减速的来源

有时,重新排序查询也可以提供帮助

SELECT CONCAT(u.host, u.path)
from urls u 
inner join hosts h on u.host = h.id
inner join paths p on u.path = p.id

答案 12 :(得分:0)

我无法确定mySQL,但我知道在SQL Server中主键会自动创建索引但外键不会。确保检查外键字段是否有索引。

答案 13 :(得分:0)

由于我不是MySQL的大粉丝,我会问你是否尝试过PostgreSQL。在该DB中,您需要确保您的work_mem设置非常高,但您可以使用SET work_mem = 64MB为每个数据库连接设置它。

另一个建议是研究使用重复的路径条目。 许多共享路径的网址。

可能或可能没有帮助的另一件事是使用固定长度的文本字段而不是varchars。它曾用于产生速度差异,但我不确定当前的数据库引擎。

如果您确实使用PostgreSQL,它将允许您使用JOIN USING,但即使在MySQL上我更喜欢它:在每个表中将您的id字段命名为相同。不是在主机中的id和在url中的主机,而是将它命名为host_id两个位置。

现在更多评论。 :) 当您选择一小组行(可能是来自同一域的每个URL)时,此处的此数据布局非常有用。它还可以帮助批次如果您的查询经常需要对其中存储的其他数据执行urls表的顺序扫描,因为扫描可以跳过大文本字段(除非它无关紧要,因为无论如何,你的数据库通过指向链接表的方式存储文本。

但是,如果您几乎总是选择所有域和路径数据,那么将它存储在一个表中会更有意义。