最近发现一个分页查询存储过程中的的一个SQL语句,当聚集索引列的排序方式不同的时候,效率差别达到数十倍,让我感到非常吃惊
由此引发出来分页查询的情况下对大表做Clustered Scan的时候,
不同情况下会选择FORWARD 或者 BACKWARD差别,以及建立聚集索引时,选择索引列的排序方式的一些思考
废话不多,上代码
先建立一张测试表,在Col1上建立聚集索引,写入100W条数据

  1. create table ClusteredIndexScanDirection
  2. (
  3. Col1 int identity(1,1),
  4. Col2 varchar(50),
  5. Col3 varchar(50),
  6. Col4 Datetime
  7. )
  8.  
  9. create unique clustered index idx_Col1 on ClusteredIndexScanDirection(Col1 ASC)
  10.  
  11. DECLARE @date datetime,@i int=0
  12. set @date=GETDATE()
  13. while @i<1000000
  14. begin
  15. insert into ClusteredIndexScanDirection values (NEWID(),NEWID(),DATEADD(MI,@i,GETDATE()-200))
  16. set @i=@i+1
  17. end

先直观地看一下聚集索引扫描时候的FORWARD 和 BACKWARD

 BACKWARD

  执行如下分页查询,当按照Col4符合2017-7-18和2017-7-23,并且Col1 倒序排序的时候
  从执行计划看,Clustered Index Scan的Scan Direction的方式是BACKWARD

  

 

FORWARD

  执行如下分页查询,当按照Col4符合2017-7-18和2017-7-23,并且Col1 正序排序的时候
  从执行计划看,Clustered Index Scan的Scan Direction的方式是FORWARD

  查询条件一样,分页情况下,排序方式不一样,性能上有么有差别?肯定有,太明显了,如果没有,本文也就没有什么意义了
  如图是上述两种查询方式在我本机的测试结果,同样是前100条数据,因为排序方式不同,其代价也是不同的
  逻辑读,一个是2327,一个是9978次,差别不小吧,在实际场景中,这个差别是非常非常大的,大到足以超乎你想想

 对FORWARD和BACKWARD有一个直观的感受之后,来说说这两者的区别

  如果了解B树索引结构的话,应该知道聚集索引是以类似于B树结构的方式来组织的,既然是B树结构,
  那么下面这个图就不难理解了,
  在索引列按照某事方式排序的情况下,比如

  1.   create unique clustered index idx_Col1 on ClusteredIndexScanDirection(Col1 ASC)
      或者是
      create unique clustered index idx_Col1 on ClusteredIndexScanDirection(Col1 DESC)

  下面这张图分别是FORWARD和BACKWARD两种Scan direction的实现方式

  

                FORWARD

                    BACKWARD

    Sql Server究竟选中哪种方式,是FORWARD还是BACKWARD,是依赖于你的索引情况和查询结果集排序情况的
    以我上面的查询为例
    如果是按照查询结果正序排序的方式查询

  1. SELECT *
  2. FROM ClusteredIndexScanDirection WITH (NOLOCK)
  3. WHERE Col4 >= '2017-7-18'
  4. AND Col4 <= '2017-7-23'
  5. ORDER BY 1 ASC
  6. OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY

    也就是要求查询结果的排序方式与聚集索引的排序方式一致,聚集索引是ASC的,Sql Server就会采用FORWARD的方式,
    也即是从左到右的Scan方式,找到满足1000条的数据后返回,查询终止
  

    

    如果是按照查询结果的倒序排序的方式查询

  1. SELECT *
  2. FROM ClusteredIndexScanDirection WITH (NOLOCK)
  3. WHERE Col4 >= '2017-7-18'
  4. AND Col4 <= '2017-7-23'
  5. ORDER BY 1 DESC
  6. OFFSET 0 ROWS FETCH NEXT 1000 ROWS ONLY

    也就是要求查询结果的排序方式与聚集索引的排序方式不一致,聚集索引是ASC的,Sql Server就会采用BACKWARD的方式,
    也即是从右到左的Scan方式,找到满足100条的数据后返回,查询终止

    现在就存在一个问题,如果聚集索引是按照ASC正序排列的,也就是说在聚集索引排序一定的情况下,
    聚集索引列和查询条件(CreateDate)上的时候都是递增的,也就是说,查询目标数据分布在B树的右边,
    (当然这么说不严谨,物理存储中并没有左右的概念,这些都是逻辑上的,并不是完全物理上的概念),
    实际业务中,差不多的意思就是查询最近N天的数据
    如果查询结果是按照聚集索引正序排序,
    Sql Server 采用FORWARD的方式,也即从左至右,那么这个查询就要经历B树种从左到右很大一部分数据扫描之后,才能找到所需要的数据
    如果查询结果是按照聚集索引倒叙排序,
    Sql Server 采用BACKWARD的方式,也即从右至左,那么这个查询直接从最右边开始Scan,很快就能找到符合条件的100条数据。
    聚集索引是ASC或者DESC的方式,也会影响到这个查询,这些概念都是相对的,当然实际场景中,索引情况和查询条件可能更复杂,
    可见,一个查询的实现,是通过FORWARD还是BACKWARD,跟聚集索引的排序方式和查询结果的排序方式,以及查询条件都有关。
    Sql Server 选择FORWARD或者BACKWARD,本身都没有错,如果出现不同排序方式下性能差别非常大的时候,
    就要注意到是不是,聚集索引的方式与查询排序方式之间存在类似上述的问题。
    不管是FORWARD或者BACKWARD,避免让Scan整个表的大部分数据才找到符合条件的数据
      当然实际情况也比例子中复杂很多,还是那句话,具体情况具体分析。
    比如业务系统查询数据时,排序方式是固定的(比如你网购的订单信息,总是按照时间倒叙排列的),当然也不排除其他情况
    这就要求我们在创建聚集索引的时候,要考虑到查询的方式以及排序的方式,慎重地作出选择。

 总结:
    SQLServer在对查询结果排序的查询中,如果扫描的方向与查询结果不一致,需要再次在内存中排序,
    因此,大多数情况下,会根据查询结果的排序来执行FORWARD或者BACKWARD操作(当然也不一定百分百)。
    本文通过聚集索引Scan的两种方式,FORWARD和BACKWARD,粗浅第分析了表上的聚集索引的排序对查询时的影响,
    当然非聚集索引上也会出现FORWARD和BACKWARD扫描的请,
      我们在选择聚集索引排序方式的时候,可以考虑到是不是因为FORWARD和BACKWARD的因素,以便进一步的排查确认。

    

  补充:

好吧,算我没说清楚,这里是按照聚集索引排序,按照非索引字段查询,而不是直接按照聚集索引字段查询!!!
我的例子已经写的很清楚了
如果聚集索引建立在一个字段上,也即单字段作为聚集索引,在非聚集索引字段上查询,暂不论这个字段上有没有索引
如果查询结果的跟聚集索引的排序方式是相同的,那么就是FORWARD
如果查询结果的跟聚集索引的排序方式是相反的,那么就是BACKWARD
不管是FORWARD还是BACKWARD,究竟要扫描多大范围才能找到符合条件的数据,
取决于上面说的非聚集索引字段列的数据分布,岂能说“ 正序和倒序无差别”?

其实我更想表达的是,因为结果集的排序,会导致在做聚集索引Scan的时候选择FORWARD或者BACKWARD
FORWARD还是BACKWARD会对查询的效率有较大的影响,
实际应用中太复杂了,当然修改聚集索引的排序方式可以从一定程度上缓解这种问题,我当然测试过,不然也不会乱说
也有其他方法也可以实现,比如暴力地去修改聚集索引列,或者建立复合聚集索引,办法也不仅限于此
如果还有不明白的,可以试试下面这个脚本,可以直接在你机器上执行,看看最后两个查询的IO代价
当然这个例子也比较极端

  1. create table ClusteredIndexScanDirection
  2. (
  3. Col1 int identity(1,1),
  4. Col2 varchar(50),
  5. Col3 varchar(50),
  6. Col4 Datetime
  7. )
  8.  
  9. create unique clustered index idx_Col1 on ClusteredIndexScanDirection(Col1 ASC)
  10.  
  11. DECLARE @date datetime,@i int=0
  12. set @date=GETDATE()
  13. while @i<1000000
  14. begin
  15. insert into ClusteredIndexScanDirection values (NEWID(),NEWID(),DATEADD(MI,@i,GETDATE()))
  16. set @i=@i+1
  17. end
  18.  
  19. set statistics io on
  20.  
  21. SELECT *
  22. FROM ClusteredIndexScanDirection WITH (NOLOCK)
  23. WHERE Col4 >= '2016-6-1'
  24. AND Col4 <= '2016-6-15'
  25. ORDER BY Col1 ASC
  26. OFFSET 0 ROWS FETCH NEXT 1000 ROWS ONLY
  27.  
  28. SELECT *
  29. FROM ClusteredIndexScanDirection WITH (NOLOCK)
  30. WHERE Col4 >= '2016-6-1'
  31. AND Col4 <= '2016-6-15'
  32. ORDER BY Col1 DESC
  33. OFFSET 0 ROWS FETCH NEXT 1000 ROWS ONLY

 

20160606再次后记:

A表上的索引大概是这样的:create index idx_date on A(BusinessDate )
这两个大表join,因为结果集的排序与其中一个主表(也是最大的表)的聚集索引一致
一致的话,他就是Forward方式的了,
但是,在逻辑上,最近的数据分布在B树的右边,那就是几乎要遍历整个表才能查询出来符合条件数据
为了避免这个问题,那就先对A表进行查询,将结果放入临时表
select * into #A from A where A.BusinessDate>'2016-6-1' and A.BusinessDate<'2016-6-6'
然后再在#A上建立相关索引,在跟其他表join,绕开直接join时走index Forward的方式进行查询
当然实际问题没这么简单,原始查询20多秒,采用这种方式优化后2s,差不多有十几倍的提高,效果还是比较明显的。

    

Sql Server 聚集索引扫描 Scan Direction的两种方式------FORWARD 和 BACKWARD的更多相关文章

  1. SQL SERVER 聚集索引 非聚集索引 区别

    转自http://blog.csdn.net/single_wolf_wolf/article/details/52915862 一.理解索引的结构 索引在数据库中的作用类似于目录在书籍中的作用,用来 ...

  2. SQL Server - 聚集索引 <第六篇>

    聚集索引的叶子页存储的就是表的数据.因此,表行物理上按照聚集索引列排序,因为表数据只能有一种物理顺序,所以一个表只能有一个聚集索引. 当我们创建主键约束时,如果不存在聚集索引并且该索引没有被明确指定为 ...

  3. 浅谈sql server聚集索引与非聚集索引

    今天同事的服务程序在执行批量插入数据操作时,会超时失败,代码debug了几遍一点问题都没有,SQL单条插入也可以正常录入数据,调试了一上午还是很迷茫,场面一度很尴尬,最后还是发现了问题的根本,原来是另 ...

  4. 从性能的角度谈SQL Server聚集索引键的选择

      简介 在SQL Server中,数据是按页进行存放的.而为表加上聚集索引后,SQL Server对于数据的查找就是按照聚集索引的列作为关键字进行了.因此对于聚集索引的选择对性能的影响就变得十分重要 ...

  5. SQL server 聚集索引与主键的区别

    主键是一个约束(constraint),他依附在一个索引上,这个索引可以是聚集索引,也可以是非聚集索引. 所以在一个(或一组)字段上有主键,只能说明他上面有个索引,但不一定就是聚集索引. 例如下面: ...

  6. C#连接sql server windows 和 sqlserver 身份验证的两种连接字符串

    //sql server 身份验证 连接字符串 private string ConnstrSqlServer = "server=服务器名称;uid=登录名称;pwd=登录密码;datab ...

  7. SQL Server 2008 R2占用内存越来越大两种解决方法

    SQL Server 2008 R2运行越久,占用内存会越来越大. 第一种:有了上边的分析结果,解决方法就简单了,定期重启下SQL Server 2008 R2数据库服务即可,使用任务计划定期执行下边 ...

  8. Sql Server聚集索引创建

    create CLUSTERED index IX_ZhuiZIDList_ZID on ZhuiZIDList (ZID)

  9. 【sql server】索引详解

    索引可以理解为一种特殊的目录结构. sql server提供两种索引形式: 聚集索引和非聚集索引. 怎么理解这两种形式. 拿我们常用的字典举例来说, 一个字典好比数据库中的一个表.那么当我们想从字典中 ...

随机推荐

  1. SQL Server 触发器

    触发器是一种特殊类型的存储过程,它不同于之前的我们介绍的存储过程.触发器主要是通过事件进行触发被自动调用执行的.而存储过程可以通过存储过程的名称被调用. Ø 什么是触发器 触发器对表进行插入.更新.删 ...

  2. web app iphone4 iphone5 iphone6 响应式布局 适配代码

    在网页中,pixel与point比值称为device-pixel-ratio,普通设备都是1,iPhone 4是2,有些Android机型是1.5.] 那么-webkit-min-device-pix ...

  3. 简单正则匹配QQ邮箱

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <script src ...

  4. jvm的内存分配总结

    最近看了周志明版本的<深入理解Java虚拟机>第一版和第二版,写的很好,收获很多,此处总结一下.   jvm中内存划分:   如上图,一共分为五块,其中: 线程共享区域为: 1.java堆 ...

  5. 参加MVP OpenDay 和2015 MVP Community Camp社区大课堂

    微软MVP Openday 1月30日在北京召开,到时全国上百位 MVP 专家将齐聚北京.当然还有亚太的其他国家地区的MVP 也会来北京,1月31日微软 MVP 项目组主办的年度微软技术社区分享大会- ...

  6. 解读ASP.NET 5 & MVC6系列

    本系列的大部分内容来自于微软源码的阅读和网络,大部分测试代码都是基于VS RC版本进行测试的. 解读ASP.NET 5 & MVC6系列(1):ASP.NET 5简介 解读ASP.NET 5 ...

  7. Async All the Way

    Asynchronous code reminds me of the story of a fellow who mentioned that the world was suspended in ...

  8. iOS开发系列--IOS程序开发概览

    概览 终于到了真正接触IOS应用程序的时刻了,之前我们花了很多时间去讨论C语言.ObjC等知识,对于很多朋友而言开发IOS第一天就想直接看到成果,看到可以运行的IOS程序.但是这里我想强调一下,前面的 ...

  9. 安装 Linux 时碰到的硬盘分区的陷阱及应对

    硬盘分区的陷阱及应对 之所以想到写这篇,是因为本人在折腾 Linux 系统的过程中,有多次掉入硬盘分区的陷阱的经历.最近几天,再一次掉入坑中,折腾了两天才从坑中爬出来.经过多方查询资料,终于弄明白了硬 ...

  10. 构建基于Chromium的应用程序

    chromium是google chrome浏览器所采用的内核,最开始由苹果的webkit发展而出,由于webkit在发展上存在分歧,而google希望在开发上有更大的自由度,2013年google决 ...