当我们进行SQL Server问题处理的时候,有时候会发现一个很有意思的现象:SQL Server完全忽略现有定义好的非聚集索引,直接使用表扫描来获取数据。我们来看看下面的表和索引定义:

 1 CREATE TABLE Customers
 2 (
 3    CustomerID INT NOT NULL,
 4    CustomerName CHAR(100) NOT NULL,
 5    CustomerAddress CHAR(100) NOT NULL,
 6    Comments CHAR(185) NOT NULL,
 7    Value INT NOT NULL
 8 )
 9 GO
10
11 CREATE UNIQUE CLUSTERED INDEX idx_Customers ON Customers(CustomerID)
12 GO
13
14 CREATE UNIQUE NONCLUSTERED INDEX idx_Test ON Customers(Value)
15 GO

我们往表里插入80000条记录:

 1 DECLARE @i INT = 1
 2 WHILE (@i <= 80000)
 3 BEGIN
 4 INSERT INTO Customers VALUES
 5 (
 6    @i,
 7    'CustomerName' + CAST(@i AS CHAR),
 8    'CustomerAddress' + CAST(@i AS CHAR),
 9    'Comments' + CAST(@i AS CHAR),
10    @i
11 )
12
13 SET @i += 1
14 END
15 GO

执行下列查询,就会发现SQL Server完全忽略非聚集索引,而使用表扫描来获取数据,点击工具栏的显示包含实际的执行计划:

1 SELECT * FROM Customers
2 WHERE Value < 1267
3 GO

而当我们把查询条件修改为1266时,我们惊奇的发现,SQL Server又重新使用非聚集索引来获取数据了:

1 SELECT * FROM Customers
2 WHERE Value < 1266
3 GO

很多人估计会很兴奋,因为他们认为它们找到了SQL Server里的一个BUG,用指定索引来查询就可以避免这个问题:

1 SELECT * FROM Customers
2 WITH (INDEX(idx_Test))
3 WHERE Value < 1267
4 GO

从执行计划里我们可以看到,SQL Server需要进行书签查找,因为针对这个查询,我们并没有定义对应的覆盖非聚集索引。当你进行全表聚集索引扫描时,SQL Server这里帮了你一个大忙:用书签查找获取每条记录成本太高,因此SQL Server使用了全表扫描,这样就只需要较少的IO和CPU占用,因为书签查找都要通过内循环运算符完成。

在SQL Server里,这个行为被称为临界点(Tipping Point) 。我们再详细解释下这个概念。简单来说,临界点定义了SQL Server是使用书签查找还是全表/索引扫描。这也意味着临界点只与非覆盖非聚集索引有关。一个对指定查询扮演覆盖非聚集索引的角色的话,不会有临界点,也就不会有刚才介绍的问题。

在有书签查找的查询时,SQL Server使用书签查找还是全表扫描取决于获取的页数。是的,你没看错!获取的页数决定了书签查找是好的还是不好的!这与查询返回的记录条数完全无关,唯一有关就是页数。临界点出现在查询需要读取的24%-33%页数之间。

在这范围之前,查询优化器会选择书签查找,在这范围之后,查询优化器会选择全表扫描(在全表扫描运算符里会有谓语定义)。

这也意味着你记录的大小决定了临界点的位置。在查询越过临界点进行全表扫描时,小记录,你就只能从表获取小数量的记录,大记录,你就能够获得大量的记录。下图就是对临界点的一个图示。

在我们刚才的例子里,每条记录是400 bytes长,因此8kb的页面里可以保存20条记录,当我们进行全表扫描时,SQL Server会产生4016个逻辑读。

1 SET STATISTICS IO ON
2 SELECT * FROM Customers

刚才的例子里,我们的表在聚集索引的叶子层有4000个数据页,也就是说临界点在1000与1333页之间的某个地方。在优化器选择进行全表扫描前,你只能读取0.25%-0.67%(1000* 20/80000,1333*20/80000)的表数据。

下面这个查询会用到书签查找:

1 SET STATISTICS IO ON
2 SELECT * FROM Customers
3 WHERE Value < 1266
4 GO

可以看到,这个查询需要3887个IO操作,而全表扫描只需要4016个IO,这里的书签查找成本(IO和CPU消耗)越来越昂贵了。超过了这个点,SQL Server就决定不使用书签查找,改用全表扫描了。

1 SET STATISTICS IO ON
2 SELECT * FROM Customers
3 WHERE Value < 1267
4 GO

我们一起执行看下:

1 SELECT * FROM Customers
2 WHERE Value < 1266
3 GO
4 SELECT * FROM Customers
5 WHERE Value < 1267
6 GO

2个近乎一样的查询,却有完全不同的执行计划,这在性能调优的时候是个大问题,因为你的执行计划失去了稳定性。

针对输入参数的不同,却有完全不同的计划!这也是书签查找的重大缺陷!用了书签查找,你就不能获得稳定的执行计划。如果这个执行计划被缓存(或你的统计信息过期了),你用它获取大量数据的时候就会有性能上的问题,因为低效的书签查找被SQL Server盲目重用了!这会造成原先只要几秒的查询,要花好几分钟才能完成!

我们说过,临界点取决于查询的读取页数。我们对刚才的表做下一点改动,每条记录40 bytes长,8k的页里能存储200条的记录,同样我们也插入80000条记录(记得关掉IO统计:SET STATISTICS IO OFF和执行计划显示,否则电脑蜗牛了-_-)。

 1 CREATE TABLE Customers3
 2 (
 3    CustomerID INT NOT NULL,
 4    CustomerName CHAR(10) NOT NULL,
 5    CustomerAddress CHAR(10) NOT NULL,
 6    Comments CHAR(5) NOT NULL,
 7    Value INT NOT NULL
 8 )
 9 GO
10
11 CREATE UNIQUE CLUSTERED INDEX idx_Customers ON Customers3(CustomerID)
12 GO
13
14 CREATE UNIQUE NONCLUSTERED INDEX idx_Test ON Customers3(Value)
15 GO
16
17
18 DECLARE @i INT = 1
19 WHILE (@i <= 80000)
20 BEGIN
21 INSERT INTO Customers3 VALUES
22 (
23    @i,
24    'C2',
25    'C3',
26    'C4',
27    @i
28 )
29
30 SET @i += 1
31 END
32 GO

这样的话,我们需要400页来存储这些数据。我们来看下临界点位置:临界点在100-133页读取的位置,也就是说通过非聚集索引,你只能读取0.125%-0.167%的数据,对于80000条数据的表来说,这几乎就是没数据你的非聚集索引毫无用处!

我们来看下临界点的2个不同查询,这里我们可以打开执行计划显示。

 1 SET STATISTICS IO ON
 2 -- 书签查找会产生332个逻辑读。
 3 SELECT * FROM Customers3
 4 WHERE Value < 157
 5 GO
 6
 7 -- 聚集索引扫描会产生419个逻辑读。
 8 -- The query produces 419 I/Os.
 9 SELECT * FROM Customers3
10 WHERE Value < 158
11 GO

我们来看第2个查询,我们只选择80000条记录的157条,我们只选择了很少的数据,但是SQL Server在这里就非常聪明,完全忽略你的的非聚集索引,使用表扫描来获取数据。但对于整个查询来说,这个非聚集索引设计并不完美,因为不是覆盖的非聚集索引,如果有人用指定索引来查找数据,就会非常恐怖:

1 SELECT * FROM Customers3 WITH(INDEX(idx_Test))
2 WHERE Value < 80001
3 GO

这个查询产生了165120个逻辑读,把聚集索引全表扫描需要的IO数直接秒杀!从这个例子我们可以看出,临界点是SQL Server里的性能保障,它阻止着使用书签查找,造成占用昂贵资源的查询发生。但这些和记录数完全无关。这2个例子里的表记录数都是80000。我们只修改了表记录的大小,因此我们就改变了表的大小,最后临界点也跟着改变,SQL Server就会忽略我们的非聚集索引。

寓意:非聚集索引,不是覆盖非聚集索引的话,在SQL Server里是非常,非常,非常,非常有选择性的用例!下次当你碰到这个情况的时候,想下你要怎么处理这个问题!

参考文章:

https://www.sqlpassion.at/archive/2013/06/12/sql-server-tipping-games-why-non-clustered-indexes-are-just-ignored/

转载于:https://www.cnblogs.com/woodytu/p/4546520.html

SQL Server临界点游戏——为什么非聚集索引被忽略!相关推荐

  1. sql查询初学者指南_面向初学者SQL Server查询执行计划–非聚集索引运算符

    sql查询初学者指南 Now that we understand what Clustered Index Scan and Clustered Index Seek are, how they o ...

  2. 面向初学者的 SQL Server 查询执行计划(1)——聚集索引运算符(Clustered Index)

    在本文中,我们将讨论与聚集索引相关的各种执行计划运算符,以及它们的作用.它们何时出现以及它们何时出现. 执行计划中的每一个运算符都会提供一些有关 SQL Server 运行方式的指标. 我们需要理解这 ...

  3. sql聚集索引和非聚集索引_SQL Server中非聚集索引概述

    sql聚集索引和非聚集索引 This article gives an introduction of the non-clustered index in SQL Server using exam ...

  4. SQL Server索引概要(2)-非聚集索引(Non-Clustered Index)

    介绍 在上一篇SQL Server 聚集索引概述 中,我们探讨了 SQL Server 中索引和聚集索引的要求. 在我们继续之前,让我们快速总结一下 SQL Server 聚集索引: 它根据聚集索引键 ...

  5. 唯一聚集索引上的唯一和非唯一非聚集索引

    在上篇文章里,我讨论了唯一和非唯一聚集索引的区别.我们已经知道,SQL Server内部使用4 bytes的uniquifier来保证非唯一聚集索引行唯一.今天我们来看下唯一聚集索引上,唯一和非唯一非 ...

  6. 索引键的唯一性(3/4):唯一聚集索引上的唯一和非唯一非聚集索引

    在上篇文章里,我讨论了唯一和非唯一聚集索引的区别.我们已经知道,SQL Server内部使用4 bytes的uniquifier来保证非唯一聚集索引行唯一.今天我们来看下唯一聚集索引上,唯一和非唯一非 ...

  7. 复合非聚集索引里列的顺序的重要性

    当我谈论索引时,大家经常会问我在复合非聚集索引里,列的顺序是否重要?简单来说:"看情况".我们来具体看下为啥"看情况"-- 单例查找(Singleton Loo ...

  8. 更深入到非聚集索引:通往SQL Server索引级别2的阶梯

    更深入到非聚集索引:通往SQL Server索引级别2的阶梯 大卫·杜兰特,2017/10/18(第一次出版:2014 /11/26) 该系列 本文是楼梯系列的一部分:SQL Server索引的阶梯 ...

  9. SQL Server 聚集索引 clustered index 非聚集索引Nonclustered Indexes键查找查找Key Lookup执行计划过程详解

    SQL Server 聚集索引非聚集索引键查找过程详解 索引的相关术语 1 堆(Heap)是一种没有指定排序的数据结构,通俗的理解堆就像是按照顺序排放的杂物.在数据库里也即是对应没有聚集索引. 2 聚 ...

最新文章

  1. 思科三层交换配置清单与案例
  2. K-means聚类自定义距离计算
  3. (0053)iOS开发之沙盒(sandbox)机制和文件操作(三)
  4. SmartForms 小技巧
  5. SAP Spartacus content page url的硬编码列表 - Page Label
  6. EF Core 2.0使用MsSql/Mysql实现DB First和Code First
  7. Spring Cloud 学习 (五) Zuul
  8. Lifewire文档阅读笔记-如何使用IP地址找对应的MAC地址
  9. dev sda2 linux lvm,VM下LINUX完美增加硬盘空间(LVM)
  10. 【BZOJ1057】【codevs1428】棋盘制作,悬线法
  11. ES 6理解 let
  12. Win11延迟高怎么办?Win11延迟高的解决方法
  13. struts2中常用Result类型(type)的用法
  14. matlab基于瑞利信道,一种基于MATLAB的瑞利信道仿真方法研究
  15. Java杨辉三角打印
  16. 学习python量化分析
  17. JavaScript之for循环、while循环 continue、break与代码规范
  18. 计算机程序专利实用新型,涉及计算机程序的实用新型专利保护的思考
  19. Linux 安装 git最新版
  20. 素描滤镜_先进的素描技术

热门文章

  1. python模拟内置函数all_python内置函数all和any
  2. java漂亮的模板设计代码_JAVA模板方法设计模式——Java设计模式,写漂亮的代码——...
  3. OpenCV-Python实战(17)——人脸识别详解
  4. primefaces教程_PrimeFaces教程
  5. 阵列函数 java_Java复制阵列– Java中的阵列复制
  6. c语言编程字符串_C编程中的字符串
  7. Python字符串index()
  8. python 字符串转大写_Python字符串大写()
  9. UITableView上的iOS UIRefreshControl
  10. Linux高可用负载均衡 集群理解