欢迎来到SQL Server性能调优培训的第2个月。这个月将会是最有趣和最有挑战性的一个月,这个月我们专一只谈SQL Server中的索引,索引,还是索引。相信我,值得一个月去学习索引。
今天我会谈下堆表(Heap Tables),在接下来的3周,我们会探讨下聚集索引,非聚集索引,还有SQL Server的索引战略。先来看下堆表。堆表就是没有聚集索引的表。如果SQL Server中的表能有一个聚集索引,那这个表被称为聚集表(Clustered Table),没有聚集索引(/非聚集索引)的表,我们称它为堆表。
在堆表里,数据没有任何方式的排序,它就是一个无序堆,无结构关联的记录。当你使用SELECT语句访问堆表时,SQL Server在执行计划里会使用表扫描(Table Scan)运算符,因为你没有定义合适的聚集索引。(堆表)没有表查找(Table Seek)这个运算符。这点非常重要。
下周你会学到,当我们提到聚集索引时,你是通过聚集索引扫描(Clustered Index Scan)、聚集索引查找(Clustered Index Seek)运算符来访问聚集索引的。在堆表你只有一个表扫描(Table Scan)运算符。表扫描意味着你必须扫描整张表,不以你表拥有的数据量来衡量。你的数据量越多,操作花费(时间)越长。
表扫描始终是个线性O(n)操作(更多关于大O介绍),它不以你越来越大的表来衡量。现在让我们一起详细看下在SQL Server中堆表的优点和缺点。
优点
堆表插入数据非常,非常,非常快。如我们刚才说的堆表就是一个数据堆。当你从传统电话本(拆开装订)把每页扯出来并把各页放在你面前的桌上时,你就拥有了一个堆表。在堆表的电话本里插入一个新的电话记录非常快:你分配一个8kb 的新页,在那页写上新的纪录,最后把那页与面前的其他页放一起,搞定。不需要保证任何的排序。
在SQL Server里是一样的:分配一个新页,在新页存新的纪录,把这页分配给堆表,搞定。这是一个非常快的方法,因为SQL Server不需要保证任何的排序。把新纪录存放在哪里完全由SQL Server自己决定。
因此在数据库架构里,这样的表设计有些时候是非常好的主意:这些表只有海量(huge) ,并行(parallel)的INSERT活动。考虑下你的登陆/审计表。当我绝不推荐在任何地方使用堆表。这里只是一些特定有意义的使用案例。但不是任何地方。
缺点
堆表除了插入数据非常快的优点外,也有很多缺点,当你决定创建堆表时若不考虑这些就不合适了。
第一个缺点,堆表在你访问表数据时会在存储子系统引发随机存取(random I/O) 。想象下对你的堆表执行简单的SELECT语句。如果数据没有缓冲在缓冲池,SQL Server会发起从你的存储系统进行物理读操作。这些读操作会是随机存取(random I/O),因为堆表的页是存在数据文件里某些地方的,它们并不相邻。
如果你使用传统的旋转存储(现在大多数情况还是这样的机械硬盘),在你的存储级别就有性能上的问题,因为随机存取是非常,非常慢的。SSD硬盘在这方面是大的游戏规则改变者,因为如果你在SSD硬盘执行随机存取(random I/O) 或循序存取(sqquential I/O) ,这些都不是问题。因为两种操作不管哪样速度基本都是一样(随机存取(random I/O) 比循序存取(sqquential I/O) 稍微慢一点)。
另外一个是明确只有在堆表里才有的被称为转发记录(Forwarding Records) 的问题。存在堆表里的记录在某些情况下(更新变长记录会移动记录的存储)可以从一页移动到另一页。如果这个发生的话,SQL Server会在原页存放一条指向记录存放新位置的转发记录(Forwarding Record) 。
当你访问你的数据的时候,SQL Server还是访问原页,并通过转发记录拿到在额外页你需要的记录。这会让你的读性能大幅度下降。如果你想了解更多转发记录(Forwarding Record)细节,并且如何避免它们,我建议你看下我的关于那个话题的第10个SQL Server Quickie。
小结
堆表在一些情况有它们的用处。一般我都建议创建聚集表(在上面定义一个聚集索引),但想下在一些特殊案例下,堆表可能是一个服务你业务需要更好方式(利用它的性能特性)。如果你想了解更多关于什么时候使用堆表更合适的细节,我也推荐Tomas Kejser's的博客帖子 聚集索引与堆表的对抗。Tomas会给你非常有争议却仍有用的洞察,(那就是)对你来说关于什么时候使用堆表是有意义的。
下周我会探讨更多关于SQL Server中的聚集索引。你会学到如何选择你的正确聚集主键,什么时候它们是好的,还有什么时候是坏的。请继续关注,下周见。