2009-11-17 190 views
19

我有两个大型表,每个大约有1亿条记录,恐怕我需要在两个表之间执行内部连接。现在,两张桌子都很简单。这里的介绍:SQL:内部连接两个大型表

生物实体表:

  • BioEntityId(INT)
  • 名称(nvarchar的4000,虽然这是一个矫枉过正)
  • TYPEID(INT)

临时股东大会表(一个附配表,实际上得到的本体导入操作的):

  • EMGId(INT)
  • PID(INT)
  • 名称(nvarchar的4000,虽然这是一个矫枉过正)
  • TYPEID(INT)
  • 上次更改时间(日期)

我需要获取一个匹配的名称,以便将BioEntityId与驻留在EGM表中的PId相关联。最初,我试图用单个内部连接完成所有事情,但查询似乎耗时过长,并且数据库的日志文件(以简单恢复模式)设法咀嚼所有可用磁盘空间(超过200 GB,当数据库占用18GB)并且等待两天后查询会失败,如果我没有弄错。我设法防止日志增长(现在只有33 MB),但查询已经连续运行了6天,看起来不会很快停止。我在一个相当不错的电脑上运行它(4GB内存,Core 2 Duo(E8400)3GHz,Windows Server 2008,SQL Server 2008),我注意到计算机每30秒偶尔会发生一次卡塞(给或采取)几秒钟。这使得它很难用于其他任何事情,这真的让我很紧张。

现在,这里的查询:

SELECT EGM.Name, BioEntity.BioEntityId INTO AUX 
FROM EGM INNER JOIN BioEntity 
ON EGM.name LIKE BioEntity.Name AND EGM.TypeId = BioEntity.TypeId 

我手动设置一些指标了; EGM和BioEntity都有一个包含TypeId和Name的非聚集覆盖索引。然而,查询运行了五天,也没有结束,所以我试着运行数据库优化顾问来让这件事情起作用。它建议删除我的旧索引并创建统计信息和两个聚集索引(每个表上只有一个,只包含TypeId,我觉得它很奇怪 - 或者只是简单的愚蠢 - 但我仍然放弃它)。

已现在正在运行6天,我仍然不知道该怎么办...... 任何想法的家伙?我怎样才能让这个更快(或者至少是有限的)?

更新: - 好吧,我已经取消了查询,并重新启动服务器,以获得OS重新启动和运行 - 我重新运行工作流程与您建议的修改,特别是裁剪为nvarchar领域的尺寸小得多,并为“=”交换“like”。这是要花至少两个小时,所以我会在

更新2晚些时候发布进一步的更新(格林尼治标准时间下午1:00时,18/11/09): - 估计执行计划揭示了67%的成本关于表扫描,然后是33%的散列匹配。接下来是0%的并行性(这不奇怪吗?这是我第一次使用估计的执行计划,但这个特殊的事实刚刚解除了我的眉毛),0%的散列匹配,0%的并行性,0%的顶级,0 %表插入,最后再选择0%。似乎索引是垃圾,正如预期的那样,所以我将制作手动索引并丢弃糟糕的建议索引。

+1

只是好奇...为什么你需要100+万行回来,你有什么打算用这些数据做? – 2009-11-17 16:22:56

+0

4k名称字段中存储的最大值是多少?如果它实质上小于4k,那么在每个表格中缩小尺寸。 – 2009-11-17 16:23:14

+0

生物信息学...:] – 2009-11-17 16:23:24

回答

6

对于庞大的加入,有时明确选择loop join速度东西:

SELECT EGM.Name, BioEntity.BioEntityId INTO AUX 
FROM EGM 
INNER LOOP JOIN BioEntity 
    ON EGM.name LIKE BioEntity.Name AND EGM.TypeId = BioEntity.TypeId 

与往常一样,发布您的估计的执行计划可以帮助我们提供更好的答案。

编辑:如果两个输入进行排序(他们应该是,与覆盖索引),你可以尝试MERGE JOIN

SELECT EGM.Name, BioEntity.BioEntityId INTO AUX 
FROM EGM 
INNER JOIN BioEntity 
    ON EGM.name LIKE BioEntity.Name AND EGM.TypeId = BioEntity.TypeId 
OPTION (MERGE JOIN) 
+1

我现在取消了查询,让我们看看SQL Server是否可以从死里复活并给我们计划... – 2009-11-17 16:29:46

+0

好吧,服务器死了,重新启动,在夜间重新配置工作流程;现在发布结果 – 2009-11-18 12:48:34

+0

'内循环连接'使用更少的内存和更多的CPU? – seyed 2013-02-03 20:38:48

1

1亿条记录是巨大的。我会说与一个数据库一起工作,你需要一个专用的测试服务器。在执行这样的查询时使用同一台机器做其他工作是不现实的。

你的硬件是相当能干,但加入该大执行体面你需要更多的权力。拥有8GB的四核系统将是一个好的开始。除此之外,你必须确保你的索引设置得恰到好处。

+1

我会向我的老板推特;)谢谢 – 2009-11-17 16:53:43

+1

大声笑是啊告诉他StackOverflow说你还需要一个新的AlienWare笔记本电脑! – 2009-11-17 17:17:05

+0

和两个30英寸的显示器。这是很多数据看 – 2009-11-17 22:39:53

4

我想尝试删除'LIKE'操作符;因为你似乎没有做任何通配符匹配。

+0

不是真的不,我也尝试了等号(“=”),但它看起来没有前途。我会换掉它,谢谢! – 2009-11-17 16:29:10

+4

没有通配符,LIKE应该优化成“=”。 – 2009-11-17 16:43:07

14

我不是一个SQL调优专家,但在一个VARCHAR字段加入数亿行的听起来并不像在任何数据库系统,我知道一个好主意。

你可以尝试在姓名字段应该得到的可能匹配到一个合理的号码前发动机必须要看实际VARCHAR数据添加一个整数列,每个表和计算哈希值。

+0

有趣的想法。我稍后再试,谢谢。 – 2009-11-17 16:28:09

+0

CHECKSUM对此很有帮助。 – 2009-11-17 21:44:11

+0

校验和会起作用,但根据NAME中数据的性质,您可能可以使用更快的散列算法(也许NAME在前10个字符中是唯一的,或者类似的东西)。 – 2009-11-17 22:32:35

3

按照建议,我会散列这个名字来使连接更合理。如果可能的话,我会强烈考虑在通过查找导入批次的过程中调查id的分配情况,因为这样可以避免以后需要进行连接(并且可能反复不得不执行如此低效的连接)。

我看你对这个typeid的指标 - 这将极大地帮助,如果这是在所有的选择。此外,随着名称的哈希添加列以同一指数:

SELECT EGM.Name 
     ,BioEntity.BioEntityId 
INTO AUX 
FROM EGM 
INNER JOIN BioEntity 
    ON EGM.TypeId = BioEntity.TypeId -- Hopefully a good index 
    AND EGM.NameHash = BioEntity.NameHash -- Should be a very selective index now 
    AND EGM.name LIKE BioEntity.Name 
+0

我会沿着这条路进一步尝试,我现在需要探索估算计划。谢谢:) – 2009-11-18 12:57:33

2

另一个建议我可能会提供被尝试一次获取数据的一个子集,而不是处理所有100 M行来调整你的查询。这样,您不必花费太多时间等待查看您的查询何时完成。然后你可以考虑检查查询执行计划,这也可以提供一些洞察手头的问题。

+1

这个和正确的,最小的索引可能(可能是另一个预处理步骤)是易处理性的关键。 – Don 2009-11-17 17:05:55

0

既然你不问DB做任何花哨的关系操作,你可以很容易的脚本这一点。不要用大量简单的查询来杀死数据库,而是尝试导出这两个表(可以从备份中获得脱机副本吗?)。

一旦你导出的表,编写一个脚本来执行这个简单的加入为你。这需要大致相同的时间来执行,但不会杀死数据库。

由于数据的大小和查询运行的时间长度,您不会经常这样做,因此脱机批处理过程很有意义。

对于脚本,您需要索引较大的数据集,然后遍历较小的数据集并查找大数据集索引。它将运行O(n * m)。

1

你有没有主键或索引?你可以分阶段选择它吗?即名称如'A%',其中名称如'B%'等。

+0

我有PK的(EMGId和BioEntityId),并且索引发布在问题 – 2009-11-18 12:50:37

1

我手动设置了一些索引; EGM和BioEntity都有一个包含TypeId和Name的非聚集覆盖索引。然而,查询运行了五天,并且也没有结束,所以我尝试运行数据库优化顾问来让这件事情起作用。它建议删除我的旧索引并创建统计信息和两个聚集索引(每个表上只有一个,只包含TypeId,我觉得它很奇怪 - 或者只是简单的愚蠢 - 但我仍然放弃它)。

你说你在TYPEID两个表中做了一个聚集索引,虽然看起来你对每个表已经(BioEntityId & EGMId,分别)主键。您不要希望您的TypeId成为这些表上的聚集索引。您希望BioEntityId & EGMId在磁盘上的聚集索引的顺序进行聚类(将物理排序你的数据,你想上​​外键非聚集索引您将使用进行查找。即TYPEID。尝试制作主键集群,并且在两个仅包含TypeId的表上添加一个非聚集索引

在我们的环境中,我们有一个表大概每个记录10-20万条记录我们做了很多类似于你的查询,在这里我们将两个数据集合在一个或两个列上,添加一个索引,每个外键应该对你的性能有很大的帮助

请记住,有1亿条记录,这些索引将需要很多磁盘空间。不过,表现似乎是关键,所以应该值得。

K.斯科特有一篇不错的文章here,它更深入地解释了一些问题。

+0

我知道。我这样做了,但结果并不是我所期望的。 我放弃了,因为SQL Server数据库优化顾问建议它;仍然认为这是愚蠢的 – 2009-11-18 12:43:23

1

重申了几个之前的帖子在这里(我会投了)......

如何选择是TYPEID?如果您的100M +行中只有5,10甚至100个不同的值,那么索引对您不起任何作用 - 特别是因为您无论如何都选择了所有行。

我建议在两个表中的CHECKSUM(Name)上创建一个列似乎很好。也许使这个持续计算列:

CREATE TABLE BioEntity 
(
    BioEntityId int 
    ,Name   nvarchar(4000) 
    ,TypeId  int 
    ,NameLookup AS checksum(Name) persisted 
) 

,然后创建像这样的索引(我会使用群集,但即使非聚集会帮助):

CREATE clustered INDEX IX_BioEntity__Lookup on BioEntity (NameLookup, TypeId) 

(BOL检查,有规则以及在可能适用于您的环境的计算列上构建索引的限制。)

做了两个表,这会提供一个非常有选择性的指标来支持你的查询,如果它的修订是这样的:

SELECT EGM.Name, BioEntity.BioEntityId INTO AUX 
FROM EGM INNER JOIN BioEntity 
ON EGM.NameLookup = BioEntity.NameLookup 
    and EGM.name = BioEntity.Name 
    and EGM.TypeId = BioEntity.TypeId 

取决于许多因素,它仍然会运行长(不只是因为你是将多少数据复制到新表中?),但这应该少于几天。

+0

是的,只有一百个左右的TypeId项目。 感谢您的意见,我会尽快给您一个尝试......我的2岁MBP刚刚死了我,我去了商店:/ – 2009-11-18 15:02:48

6

也许有点偏离主题,但是: “我注意到计算机每隔30秒偶尔会发生几秒钟的阻塞(给与接受)。”

此行为对于廉价的RAID5阵列(或可能用于单个磁盘)在复制(以及您的查询主要复制数据)千兆字节的信息时具有特征。

关于问题的更多信息 - 你不能把你的查询分割成更小的块吗?像A,B等开头的名字或特定范围的ID?这可以大大减少事务/锁定开销。

+0

+1数据分区 – 2009-11-17 22:28:00

+0

所以这得到upvoted张贴相同我已经问过的事情? – DForck42 2009-11-18 14:42:39

+0

单个磁盘,是的。 – 2009-11-18 15:12:30

1

为什么nvarchar?最佳做法是,如果你不需要(或者期望需要)unicode支持,只需使用varchar。如果您认为最长的名字少于200个字符,我会将该列设置为varchar(255)。我可以看到已经推荐给你的散列的代价很高(看起来像这个数据库是插入密集型的)。然而,有了这么大的尺寸,以及名称的频率和随机性,在索引散列(取决于散列)或名称的大多数情况下,索引将快速碎片化。

我会改变名称列如上所述,并使聚簇索引TypeId,EGMId/BioentityId(任何表的代理键)。然后,您可以很好地加入TypeId,并且名称上的“粗略”连接将循环较少。要查看此查询可能运行多长时间,请尝试使用它作为TypeIds的一小部分,并且应该给您估计运行时间(尽管它可能会忽略缓存大小,内存大小,硬盘传输速率等因素)。

编辑:如果这是一个正在进行的过程,您应该在两个表之间强制执行外键约束以便将来导入/转储。如果不在进行中,哈希可能是您最好的。

+0

不能确定,虽然它可能就足够了 – 2009-11-18 12:49:45

5

首先,100M行连接并不是不合理或不常见。

但是,我怀疑你看到的性能差的原因可能与INTO子句有关。因此,您不仅要进行连接,还要将结果写入新表。 你对日志文件变得如此巨大的观察基本上证实了这一点。

有一件事要尝试:删除INTO并查看它是如何执行的。如果性能是合理的,那么为了解决缓慢的写入问题,您应该确保您的数据库日志文件位于与数据不同的物理卷上。如果不是这样,磁头在读取数据并写入日志时会打碎(大量搜索),并且您的perf会崩溃(可能会减少到原来的1/40到1/60) )。

0

我想知道,执行时间是由加入还是通过数据传输。

假设您的Name列中的平均数据大小为150个字符,实际上您将有300个字节加上每个记录的其他列。乘以1亿条记录,您将获得大约30GB的数据传输到您的客户端。你是否运行远程客户端或服务器本身? 也许您会等待30GB的数据传输到您的客户端...

编辑:好的,我看你插入到辅助表。数据库恢复模式的设置是什么?

探讨在硬件方面的瓶颈,它可能会限制资源是否被读取或写入数据很有意思。例如,您可以开始运行Windows性能监视器并捕获队列的长度以读取和写入磁盘。

理想,你应该把数据库日志文件,输入表和输出表上单独的物理卷来提高速度。

+0

恢复模式设置为简单;了解到困难的方法:) 关于单独的物理卷的简单而合乎逻辑的建议,我只使用单个硬盘。谢谢! 我发布执行计划的估计现在的方式 – 2009-11-18 12:48:01

0

如果哈希比赛消耗了太多的资源,然后做的,比方说,10000行批量查询的时间,“行走”的TYPEID列。你没有说TypeID的选择性,但是大概它足够有选择性地能够做到这么小的批次,并且一次完全覆盖一个或多个TypeID。您还在寻找批处理中的循环连接,因此如果仍然使用散列连接,则强制循环连接或减少批处理大小。

使用批次也将在简单恢复模式,让您的TRAN日志变得非常大。即使在简单恢复模式下,像您一样的巨大连接也会占用大量空间,因为它必须保持整个事务处于打开状态,而在进行批处理时,它可以为每个批处理重用日志文件,将其大小限制为所需的最大空间一批操作。

如果你真的需要加入的名称,那么你可以考虑名称转换为ID的一些辅助表,基本上暂时修复非规范化的设计(如果您不能永久修复)。

约校验的想法可以很好了,但是我还没有与打的非常多,我自己。

在任何情况下,这样一个巨大的哈希匹配是不会作为配料的循环连接来进行为好。如果你能得到一个合并连接这将是真棒......

1

我会尽力解决外箱的问题,也许有一些其他的算法,可以做的工作比数据库更好更快。当然,这一切都取决于数据的性质,但也有一些字符串搜索算法是非常快(博耶 - 穆尔,ZBox中等等),或其他数据挖掘算法(MapReduce的?)通过精心制作的数据导出它可能是可能的弯曲问题以适应更优雅更快的解决方案。此外,可以更好地并行处理问题,并且通过简单的客户端利用您周围的系统的空闲周期,有框架可以帮助解决这个问题。

的这个输出可能是因为你可以用它来更快地从数据库中获取完整的数据REFID元组的列表。

这并不妨碍你从指数试验,但如果你要等待6天的结果我想证明的资源花在探索其他可能的选择。

我2%的

+0

嗯好主意,谢谢! – 2009-11-19 11:57:09