优化大表上的联接


10

我试图从正在访问具有约2.5亿条记录的表的查询中获得更多性能。从我对实际(未估计)执行计划的阅读中,第一个瓶颈是如下查询:

select
    b.stuff,
    a.added,
    a.value
from
    dbo.hugetable a
    inner join
    #smalltable b on a.fk = b.pk
where
    a.added between @start and @end;

有关所涉及的表和索引的定义,请参见下文。

执行计划表明在#smalltable上使用了一个嵌套循环,并且对largetable的索引扫描已执行480次(对于#smalltable中的每一行)。这对我来说似乎是倒退的,因此我尝试强制使用合并联接:

select
    b.stuff,
    a.added,
    a.value
from
    dbo.hugetable a with(index = ix_hugetable)
    inner merge join
    #smalltable b with(index(1)) on a.fk = b.pk
where
    a.added between @start and @end;

有问题的索引(有关完整定义,请参见下文)涵盖fk列(连接谓词),按升序添加(在where子句中使用)和id(无用),并包括value

但是,当我这样做时,查询从2 1/2分钟扩展到超过9分钟。我希望这些提示能够强制执行更高效的联接,该联接仅对每个表进行一次传递,但显然不会。

欢迎任何指导。如果需要,提供其他信息。

更新(2011/06/02)

重新组织了表上的索引之后,我取得了显着的性能进步,但是在汇总大表中的数据时遇到了新的障碍。结果是按月汇总,当前看起来如下所示:

select
    b.stuff,
    datediff(month, 0, a.added),
    count(a.value),
    sum(case when a.value > 0 else 1 end) -- this triples the running time!
from
    dbo.hugetable a
    inner join
    #smalltable b on a.fk = b.pk
group by
    b.stuff,
    datediff(month, 0, a.added);

目前,hugetable具有一个聚集索引pk_hugetable (added, fk)(主键)和一个非聚集索引ix_hugetable (fk, added)

如果没有上面的第4列,那么优化器将像以前一样使用嵌套循环联接,使用#smalltable作为外部输入,并使用非聚集索引搜索作为内部循环(再次执行480次)。我担心的是估计行数(12,958.4)与实际行数(74,668,468)之间的差异。这些搜索的相对成本为45%。但是,运行时间不到一分钟。

在第4列中,运行时间飙升至4分钟。这次它以相同的相对成本(45%)在聚簇索引上进行搜索(2次执行),通过哈希匹配(30%)进行聚合,然后在#smalltable(0%)上进行哈希联接。

我不确定我下一步的行动。我担心的是,日期范围搜索或连接谓词都无法保证,甚至无法保证所有结果都将大大减少结果集。在大多数情况下,日期范围只会减少记录的10-15%,而fk上的内部联接可能会过滤掉20-30%。


根据遗嘱A的要求,以下结果sp_spaceused

name      | rows      | reserved    | data        | index_size  | unused
hugetable | 261774373 | 93552920 KB | 18373816 KB | 75167432 KB | 11672 KB

#smalltable定义为:

create table #endpoints (
    pk uniqueidentifier primary key clustered,
    stuff varchar(6) null
);

虽然dbo.hugetable定义为:

create table dbo.hugetable (
    id uniqueidentifier not null,
    fk uniqueidentifier not null,
    added datetime not null,
    value decimal(13, 3) not null,

    constraint pk_hugetable primary key clustered (
        fk asc,
        added asc,
        id asc
    )
    with (
        pad_index = off, statistics_norecompute = off,
        ignore_dup_key = off, allow_row_locks = on,
        allow_page_locks = on
    )
    on [primary]
)
on [primary];

定义以下索引:

create nonclustered index ix_hugetable on dbo.hugetable (
    fk asc, added asc, id asc
) include(value) with (
    pad_index = off, statistics_norecompute = off,
    sort_in_tempdb = off, ignore_dup_key = off,
    drop_existing = off, online = off,
    allow_row_locks = on, allow_page_locks = on
)
on [primary];

ID字段是多余的,从以前的DBA谁坚持认为,制造品的所有表无处不应该有一个GUID,没有例外。


您能否包含sp_spaceused'dbo.hugetable'的结果?
将在

完成,添加在表定义开头的上方。
快速乔·史密斯,

确实是。它可笑的大小是我研究此问​​题的原因。
快速乔·史密斯,

Answers:


5

您的ix_hugetable外观非常无用,因为:

  • 聚集索引(PK)
  • INCLUDE没什么区别,因为聚集索引包含所有非键列(最低叶的非键值= INCLUDEd =聚集索引是什么)

另外:-添加或fk应该是第一个-ID是第一个=用途不大

尝试将集群键更改为(added, fk, id)ix_hugetable。您已经尝试过了(fk, added, id)。如果没有其他问题,您将节省大量磁盘空间和索引维护

另一个选择可能是尝试使用表顺序的方式执行FORCE ORDER提示,而没有JOIN / INDEX提示。我尽量不亲自使用JOIN / INDEX提示,因为您删除了优化程序的选项。许多年前,有人告诉我(与SQL Guru研讨会),当您拥有巨大的表JOIN小表时,FORCE ORDER提示会有所帮助:7年后,YMMV ...

哦,让我们知道DBA住在哪里,以便我们安排一些打击乐调整

编辑,6月2日更新后

第4列不是非聚集索引的一部分,因此它使用聚集索引。

尝试将NC索引更改为包含值列,这样就不必访问聚集索引的值列

create nonclustered index ix_hugetable on dbo.hugetable (
    fk asc, added asc
) include(value)

注意:如果值不可为空,则它在COUNT(*)语义上是相同的。但是对于SUM,它需要实际值,而不是存在

例如,如果更改COUNT(value)COUNT(DISTINCT value) 而不更改索引,则应该再次中断查询,因为它必须将值作为值而不是存在来处理。

该查询需要3列:add,fk,value。前2个被过滤/联接,关键列也被过滤/联接。仅使用值,因此可以将其包括在内。覆盖索引的经典用法。


哈哈,我头脑中的是,聚集索引和非聚集索引具有fk并以不同顺序添加。我不敢相信我没有注意到这一点,几乎就像我不敢相信它是这样设置的。我明天将更改聚簇索引,然后在重建时在街上喝咖啡。
快速乔·史密斯,

我更改了索引并使用FORCE ORDER进行了重击,以尝试减少大表上的查找次数,但无济于事。我的问题已更新。
Quick Joe Smith

@Quick张三:更新了我的答案
GBN

是的,不久之后我尝试了。因为索引重建需要很长时间,所以我忘记了它,最初以为我会加快它的速度来完成完全无关的事情。
Quick Joe Smith

2

hugetable在仅added列上定义一个索引。

DB将仅在列列表的最右边使用多部分(多列)索引,因为它具有从左边开始计数的值。您的查询未fk在第一个查询的where子句中指定,因此它会忽略索引。


执行计划表明指数(ix_hugetable)被seeked。还是您说此索引不适用于查询?
快速乔·史密斯,

索引不合适。谁知道它是如何“使用索引”的。经验告诉我这是你的问题。尝试一下,告诉我们如何进行。
波西米亚

@Quick Joe Smith-您尝试过@Bohemian的建议吗?结果在哪里?
Lieven Keersmaekers,

2
我不同意:ON子句在逻辑上首先被处理,实际上实际上是WHERE,因此OP必须先尝试两列。fk完全没有索引=聚集索引扫描或键查找以获取JOIN的fk值。您能否也对您描述的行为添加一些参考?特别是对于SQL Server,因为您几乎没有有关该RDBMS的历史记录。实际上,回想起来,当-1键入aI时,此注释
gbn

2

执行计划表明在#smalltable上使用了一个嵌套循环,并且对largetable的索引扫描已执行480次(对于#smalltable中的每一行)。

这是我期望查询优化器使用的顺序,假设循环连接是正确的选择。另一种方法是循环250M次并每次都在#temp表中执行查找-这可能需要花费数小时/天。

您要在MERGE联接中使用的索引几乎是 2.5亿行*“每行的大小”-不小,至少为GB。从sp_spaceused输出“几个GB” 来看,可能是一个轻描淡写的说法-MERGE联接要求您遍历索引,而索引将是非常I / O密集的。


我的理解是,共有3种连接算法,并且当两个输入均由连接谓词排序时,合并连接的性能最佳。对或错,这就是我想要得到的结果。
快速乔·史密斯,

2
除此之外,还有更多。如果#smalltable具有大量行,则合并联接可能是合适的。顾名思义,如果行数很少,那么循环连接可能是正确的选择。想象一下#smalltable有一两行,并且与另一张表中的少数几行匹配-在这里很难证明合并联接是合理的。
将在

我认为还有更多的东西。我只是不知道那会是什么。正如您可能已经猜到的那样,数据库优化并不是我的强项。
快速乔·史密斯,

@Quick Joe Smith-感谢sp_spaceused。75GB的索引和18GB的数据-ix_hugetable不是表上唯一的索引吗?
将在

1
+1意志。计划者当前正在做正确的事情。问题在于表的群集方式导致随机磁盘查找。
丹尼斯·德伯纳迪

1

您的索引不正确。请参阅索引dos和donts

就目前情况而言,您唯一有用的索引是小表主键上的索引。因此,唯一合理的计划是对小的表进行顺序扫描,并与大表嵌套循环。

尝试在上添加聚簇索引hugetable(added, fk)。这应该使计划者从大表中找出适用的行,然后嵌套循环或将它们与小表合并在一起。


感谢您的链接。我明天上班时会尝试这个。
快速乔·史密斯,
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.