非聚集索引在堆上的性能与聚集索引的性能


39

该2007年白皮书比较了在作为聚簇索引组织的表上与在与CI相同的关键列具有非聚簇索引的组织为堆的表上的单个选择/插入/删除/更新和范围选择语句的性能表。

通常,聚簇索引选项在测试中表现更好,因为只需要维护一种结构,并且因为不需要书签查找。

本文未涉及的一个可能有趣的案例是,堆上的非聚簇索引与聚簇索引上的非聚簇索引之间的比较。在那种情况下,我希望堆甚至可以在NCI叶级别上表现更好,因为SQL Server具有直接遵循的RID,而不需要遍历聚集索引。

是否有人知道在这一领域已经进行过类似的正式测试,如果是的话,结果是什么?

Answers:


41

为了检查您的请求,我按照以下方案创建了2个表:

  • 790万记录代表余额信息。
  • 从1到790万的身份字段
  • 一个数字字段,将记录分为约50万个组。

第一个称为的表heap在该字段上获得了非聚集索引group。调用的第二个表在调用clust的顺序字段上获得了聚集索引,在该字段上获得key了非聚集索引group

测试在具有2个超线程内核,4Gb内存和64位Windows 7的I5 M540处理器上运行。

Microsoft SQL Server 2008 R2 (RTM) - 10.50.1600.1 (X64) 
Apr  2 2010 15:48:46 
Developer Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1)  

2011年3月9日更新:通过运行以下.net代码并在Sql Server Profiler中记录持续时间,CPU,读取,写入和RowCounts,我进行了第二次更广泛的基准测试。(使用的CommandText将在结果中提及。)

注意: CPU和持续时间以毫秒表示

  • 1000个查询
  • 从结果中消除零个CPU查询
  • 从结果中消除了受影响的0行
int[] idList = new int[] { 6816588, 7086702, 6498815 ... }; // 1000 values here.
using (var conn = new SqlConnection(@"Data Source=myserver;Initial Catalog=mydb;Integrated Security=SSPI;"))
            {
                conn.Open();
                using (var cmd = new SqlCommand())
                {
                    cmd.Connection = conn;
                    cmd.CommandType = CommandType.Text;
                    cmd.CommandText = "select * from heap where common_key between @id and @id+1000"; 
                    cmd.Parameters.Add("@id", SqlDbType.Int);
                    cmd.Prepare();
                    foreach (int id in idList)
                    {
                        cmd.Parameters[0].Value = id;

                        using (var reader = cmd.ExecuteReader())
                        {
                            int count = 0;
                            while (reader.Read())
                            {
                                count++;
                            }
                            Console.WriteLine(String.Format("key: {0} => {1} rows", id, count));
                        }
                    }
                }
            }

更新于2011年3月9日结束

SELECT表现

为了检查性能编号,我在堆表上一次在集群表上执行了以下查询:

select * from heap/clust where group between 5678910 and 5679410
select * from heap/clust where group between 6234567 and 6234967
select * from heap/clust where group between 6455429 and 6455729
select * from heap/clust where group between 6655429 and 6655729
select * from heap/clust where group between 6955429 and 6955729
select * from heap/clust where group between 7195542 and 7155729

该基准测试的结果适用于heap

rows  reads CPU   Elapsed 
----- ----- ----- --------
1503  1510  31ms  309ms
401   405   15ms  283ms
2700  2709  0ms   472ms
0     3     0ms   30ms
2953  2962  32ms  257ms
0     0     0ms   0ms

2011年3月9日更新cmd.CommandText = "select * from heap where group between @id and @id+1000";

  • 721行的CPU> 0,并且影响超过0行
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    6368         -         
Cpu            15        374      37   0.00754
Reads        1069      91459    7682   1.20155
Writes          0          0       0   0.00000
Duration   0.3716   282.4850 10.3672   0.00180

更新于2011年3月9日结束


该表clust的结果是:

rows  reads CPU   Elapsed 
----- ----- ----- --------
1503  4827  31ms  327ms
401   1241  0ms   242ms
2700  8372  0ms   410ms
0     3     0ms   0ms
2953  9060  47ms  213ms
0     0     0ms   0ms

2011年3月9日更新cmd.CommandText = "select * from clust where group between @id and @id+1000";

  • 721行的CPU> 0,并且影响超过0行
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    6056         -
Cpu            15        468      38   0.00782
Reads        3194     227018   20457   3.37618
Writes          0          0       0       0.0
Duration   0.3949   159.6223 11.5699   0.00214

更新于2011年3月9日结束


SELECT JOIN性能

cmd.CommandText = "select * from heap/clust h join keys k on h.group = k.group where h.group between @id and @id+1000";


该基准测试的结果适用于heap

873行的CPU> 0,并且影响超过0行

Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1009       4170    1683         -
Cpu            15         47      18   0.01175
Reads        2145       5518    2867   1.79246
Writes          0          0       0   0.00000
Duration   0.8215   131.9583  1.9095   0.00123

该基准测试的结果适用于clust

865行的CPU> 0,并且影响超过0行

Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1000       4143    1685         -
Cpu            15         47      18   0.01193
Reads        5320      18690    8237   4.97813
Writes          0          0       0   0.00000
Duration   0.9699    20.3217  1.7934   0.00109

更新性能

第二批查询是更新语句:

update heap/clust set amount = amount + 0 where group between 5678910 and 5679410
update heap/clust set amount = amount + 0 where group between 6234567 and 6234967
update heap/clust set amount = amount + 0 where group between 6455429 and 6455729
update heap/clust set amount = amount + 0 where group between 6655429 and 6655729
update heap/clust set amount = amount + 0 where group between 6955429 and 6955729
update heap/clust set amount = amount + 0 where group between 7195542 and 7155729

此基准的结果为heap

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  3013  31ms  175ms
401   806   0ms   22ms
2700  5409  47ms  100ms
0     3     0ms   0ms
2953  5915  31ms  88ms
0     0     0ms   0ms

2011年3月9日更新cmd.CommandText = "update heap set amount = amount + @id where group between @id and @id+1000";

  • 811行的CPU> 0,并且影响超过0行
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    5598       811         
Cpu            15        873      56   0.01199
Reads        2080     167593   11809   2.11217
Writes          0       1687     121   0.02170
Duration   0.6705   514.5347 17.2041   0.00344

更新于2011年3月9日结束


此基准的结果为clust

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  9126  16ms  35ms
401   2444  0ms   4ms
2700  16385 31ms  54ms
0     3     0ms   0ms 
2953  17919 31ms  35ms
0     0     0ms   0ms

2011年3月9日更新cmd.CommandText = "update clust set amount = amount + @id where group between @id and @id+1000";

  • 853行的CPU> 0,并且影响超过0行
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1001      69788    5420         -
Cpu            15        594      50   0.01073
Reads        6226     432237   33597   6.20450
Writes          0       1730     110   0.01971
Duration   0.9134   193.7685  8.2919   0.00155

更新于2011年3月9日结束


删除基准

我运行的第三批查询是delete语句

delete heap/clust where group between 5678910 and 5679410
delete heap/clust where group between 6234567 and 6234967
delete heap/clust where group between 6455429 and 6455729
delete heap/clust where group between 6655429 and 6655729
delete heap/clust where group between 6955429 and 6955729
delete heap/clust where group between 7195542 and 7155729

此基准测试结果heap

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  10630 62ms  179ms
401   2838  0ms   26ms
2700  19077 47ms  87ms
0     4     0ms   0ms
2953  20865 62ms  196ms
0     4     0ms   9ms

2011年3月9日更新cmd.CommandText = "delete heap where group between @id and @id+1000";

  • 724行的CPU> 0,并且影响超过0行
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts     192      69788    4781         -
Cpu            15        499      45   0.01247
Reads         841     307958   20987   4.37880
Writes          2       1819     127   0.02648
Duration   0.3775  1534.3383 17.2412   0.00349

更新于2011年3月9日结束


此基准测试结果clust

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
1503  9228  16ms  55ms
401   3681  0ms   50ms
2700  24644 46ms  79ms
0     3     0ms   0ms
2953  26955 47ms  92ms
0     3     0ms   0ms

2011年3月9日更新

cmd.CommandText = "delete clust where group between @id and @id+1000";

  • 751行的CPU> 0,并且影响超过0行
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts     144      69788    4648         -
Cpu            15        764      56   0.01538
Reads         989     458467   30207   6.48490
Writes          2       1830     127   0.02694
Duration   0.2938  2512.1968 24.3714   0.00555

更新于2011年3月9日结束


插入基准

基准测试的最后一部分是执行插入语句。

插入堆/簇(...)值(...),(...),(...),(...),(...),(...)


此基准测试结果heap

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
6     38    0ms   31ms

2011年3月9日更新

string str = @"insert into heap (group, currency, year, period, domain_id, mtdAmount, mtdAmount, ytdAmount, amount, ytd_restated, restated, auditDate, auditUser)
                    values";

                    for (int x = 0; x < 999; x++)
                    {
                        str += string.Format(@"(@id + {0}, 'EUR', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test'),  ", x);
                    }
                    str += string.Format(@"(@id, 'CAD', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test') ", 1000);

                    cmd.CommandText = str;
  • 912个语句的CPU> 0
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1000       1000    1000         -
Cpu            15       2138      25   0.02500
Reads        5212       7069    6328   6.32837
Writes         16         34      22   0.02222
Duration   1.6336   293.2132  4.4009   0.00440

更新于2011年3月9日结束


此基准测试结果clust

rows  reads CPU   Elapsed 
----- ----- ----- -------- 
6     50    0ms   18ms

2011年3月9日更新

string str = @"insert into clust (group, currency, year, period, domain_id, mtdAmount, mtdAmount, ytdAmount, amount, ytd_restated, restated, auditDate, auditUser)
                    values";

                    for (int x = 0; x < 999; x++)
                    {
                        str += string.Format(@"(@id + {0}, 'EUR', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test'),  ", x);
                    }
                    str += string.Format(@"(@id, 'CAD', 2012, 2, 0, 100, 100, 1000 + @id,1000, 1000,1000, current_timestamp, 'test') ", 1000);

                    cmd.CommandText = str;
  • 946条语句的CPU> 0
Counter   Minimum    Maximum Average  Weighted
--------- ------- ---------- ------- ---------
RowCounts    1000       1000    1000         -      
Cpu            15       2403      21   0.02157
Reads        6810       8997    8412   8.41223
Writes         16         25      19   0.01942
Duration   1.5375   268.2571  6.1463   0.00614

更新于2011年3月9日结束


结论

尽管在使用聚集索引和非聚集索引访问表时(使用非聚集索引时)进行的逻辑读取更多,但性能结果为:

  • SELECT语句具有可比性
  • 有了聚集索引,UPDATE语句会更快
  • 有了聚集索引,DELETE语句会更快
  • 有了聚集索引,INSERT语句会更快

当然,我的基准测试在特定类型的表上非常有限,并且查询集非常有限,但是我认为基于这些信息,我们已经可以开始说在表上创建聚簇索引实际上总是更好。

2011年3月9日更新

从增加的结果可以看出,有限测试的结论在每种情况下都不正确。

加权持续时间

现在的结果表明,从聚集索引中受益的唯一语句是更新语句。在具有聚集索引的表上,其他语句的速度要慢大约30%。

在一些其他图表中,我绘制了每个查询相对于堆还是集群的加权持续时间。 加权持续时间堆vs选择集群

加权持续时间堆vs集群的联接

加权持续时间堆vs集群以进行更新

加权持续时间堆vs集群以进行删除

如您所见,insert语句的性能配置文件非常有趣。尖峰是由一些数据点引起的,需要花费更长的时间才能完成。 加权持续时间堆vs集群以进行插入

更新于2011年3月9日结束


@Martin下周找到一些时间时,我将尝试在具有几张具有5亿条记录的表的服务器上运行此命令。
Filip De Vos

我怀疑这项测试的准确性。一些部分需要特别注意,例如INSERT性能声称聚集索引速度更快-CLUST版本中的读取次数更多,但是经过的时间更少。我个人将忽略经过的时间在10毫秒以内(时序变化)-这意味着少于读取计数。

查阅金伯利·特里普(Kimberly Tripp)的《聚簇索引辩论》,在那儿,她解释了为何对聚簇表进行的大多数(如果不是全部)操作要比对堆进行的操作更快-有些与您的结果相反...
marc_s

1
@ Martin,@ Richard,@ marc_s。我现在正在制定更严格的基准测试。我希望能够在今天晚些时候添加结果。
Filip De Vos

1
@Filip-哇!您肯定会为此悬而未决的努力付出一切。尽管正如您完全正确地指出的那样,这是针对特定类型表的一个基准,其查询集非常有限,并且里程数无疑会有所不同。
马丁·史密斯

12

正如金伯利·特里普(Kimberly Tripp)(索引之王)在她的博客文章《聚集索引辩论》中继续很好地解释...,在数据库表上具有聚集键几乎可以加快所有操作的速度,而不仅是SELECT

只要选择一个好的集群键(如),SELECT相对于集群表通常在堆上速度较慢INT IDENTITY。如果您使用非常糟糕的群集密钥(例如GUID或具有很多可变长度组件的复合密钥),那么(只有这样)堆可能会更快。但是在那种情况下,您真的首先需要清理数据库设计...

因此,总的来说,我认为堆中没有任何意义-选择一个好的,有用的群集密钥,您应该在各个方面都受益。


3
这是无答案的。Martin在SQL Server上非常扎实。这个问题的目的是从性能测试中获得经过真实世界测试的验证结果,而不是更多的理论。

金伯利·特里普(Kimberly Tripp)的文章有效地假设所有非聚簇索引都被覆盖。如果真是这样,那么就不会有查找,并且堆在查找中的优势也会被抵消。但是,这不是我们大多数人生活的世界。在我们的情况下,尝试设计全部或大部分非聚集索引来覆盖会产生其自身的问题。

@ dbaguy52:为什么您认为Kim Tripp假定所有NC索引都覆盖了?我在她的博客文章中看不到任何想法……..请更详细地说明是什么使您相信情况如此(或者这是她的假设)
marc_s 2013年

7

碰巧碰到Joe Chang的这篇文章,它解决了这个问题。在下面粘贴他的结论。

考虑一个表,索引的深度为4,因此有一个根级别,2个中间级别和叶级别。索引查找单个索引键(即不查找键)将生成4个逻辑IO(LIO)。现在考虑是否需要密钥查找。如果表具有深度也为4的聚簇索引,则每个键查找都会生成4个LIO。如果表是堆,则每个键查找都会生成1个LIO。实际上,对堆的键查询要比对聚簇索引的键查询便宜约20-30%,而不是接近4:1的LIO比率。


1
值得注意的是,Joe Chang的报价根据他的假设确定了20-30%的堆效率优势,这与3月9日对本文进行更新时确定的优势几乎相同。
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.