SQL Server临界点游戏——为什么非聚集索引被忽略!
当我们进行SQL Server问题处理的时候,有时候会发现一个很有意思的现象:SQL Server完全忽略现有定义好的非聚集索引,直接使用表扫描来获取数据。我们来看看下面的表和索引定义:
CREATE TABLE Customers
(
CustomerID INT NOT NULL,
CustomerName CHAR(100) NOT NULL,
CustomerAddress CHAR(100) NOT NULL,
Comments CHAR(185) NOT NULL,
Value INT NOT NULL
)
GO CREATE UNIQUE CLUSTERED INDEX idx_Customers ON Customers(CustomerID)
GO CREATE UNIQUE NONCLUSTERED INDEX idx_Test ON Customers(Value)
GO
我们往表里插入80000条记录:
DECLARE @i INT = 1
WHILE (@i <= 80000)
BEGIN
INSERT INTO Customers VALUES
(
@i,
'CustomerName' + CAST(@i AS CHAR),
'CustomerAddress' + CAST(@i AS CHAR),
'Comments' + CAST(@i AS CHAR),
@i
) SET @i += 1
END
GO
执行下列查询,就会发现SQL Server完全忽略非聚集索引,而使用表扫描来获取数据,点击工具栏的显示包含实际的执行计划:
SELECT * FROM Customers
WHERE Value < 1267
GO
而当我们把查询条件修改为1266时,我们惊奇的发现,SQL Server又重新使用非聚集索引来获取数据了:
SELECT * FROM Customers
WHERE Value < 1266
GO
很多人估计会很兴奋,因为他们认为它们找到了SQL Server里的一个BUG,用指定索引来查询就可以避免这个问题:
SELECT * FROM Customers
WITH (INDEX(idx_Test))
WHERE Value < 1267
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个逻辑读。
SET STATISTICS IO ON
SELECT * FROM Customers
刚才的例子里,我们的表在聚集索引的叶子层有4000个数据页,也就是说临界点在1000与1333页之间的某个地方。在优化器选择进行全表扫描前,你只能读取0.25%-0.67%(1000* 20/80000,1333*20/80000)的表数据。
下面这个查询会用到书签查找:
SET STATISTICS IO ON
SELECT * FROM Customers
WHERE Value < 1266
GO
可以看到,这个查询需要3887个IO操作,而全表扫描只需要4016个IO,这里的书签查找成本(IO和CPU消耗)越来越昂贵了。超过了这个点,SQL Server就决定不使用书签查找,改用全表扫描了。
SET STATISTICS IO ON
SELECT * FROM Customers
WHERE Value < 1267
GO
我们一起执行看下:
SELECT * FROM Customers
WHERE Value < 1266
GO
SELECT * FROM Customers
WHERE Value < 1267
GO
2个近乎一样的查询,却有完全不同的执行计划,这在性能调优的时候是个大问题,因为你的执行计划失去了稳定性。
针对输入参数的不同,却有完全不同的计划!这也是书签查找的重大缺陷!用了书签查找,你就不能获得稳定的执行计划。如果这个执行计划被缓存(或你的统计信息过期了),你用它获取大量数据的时候就会有性能上的问题,因为低效的书签查找被SQL Server盲目重用了!这会造成原先只要几秒的查询,要花好几分钟才能完成!
我们说过,临界点取决于查询的读取页数。我们对刚才的表做下一点改动,每条记录40 bytes长,8k的页里能存储200条的记录,同样我们也插入80000条记录(记得关掉IO统计:SET STATISTICS IO OFF和执行计划显示,否则电脑蜗牛了-_-)。
CREATE TABLE Customers3
(
CustomerID INT NOT NULL,
CustomerName CHAR(10) NOT NULL,
CustomerAddress CHAR(10) NOT NULL,
Comments CHAR(5) NOT NULL,
Value INT NOT NULL
)
GO CREATE UNIQUE CLUSTERED INDEX idx_Customers ON Customers3(CustomerID)
GO CREATE UNIQUE NONCLUSTERED INDEX idx_Test ON Customers3(Value)
GO DECLARE @i INT = 1
WHILE (@i <= 80000)
BEGIN
INSERT INTO Customers3 VALUES
(
@i,
'C2',
'C3',
'C4',
@i
) SET @i += 1
END
GO
这样的话,我们需要400页来存储这些数据。我们来看下临界点位置:临界点在100-133页读取的位置,也就是说通过非聚集索引,你只能读取0.125%-0.167%的数据,对于80000条数据的表来说,这几乎就是没数据!你的非聚集索引毫无用处!
我们来看下临界点的2个不同查询,这里我们可以打开执行计划显示。
SET STATISTICS IO ON
-- 书签查找会产生332个逻辑读。
SELECT * FROM Customers3
WHERE Value < 157
GO -- 聚集索引扫描会产生419个逻辑读。
-- The query produces 419 I/Os.
SELECT * FROM Customers3
WHERE Value < 158
GO
我们来看第2个查询,我们只选择80000条记录的157条,我们只选择了很少的数据,但是SQL Server在这里就非常聪明,完全忽略你的的非聚集索引,使用表扫描来获取数据。但对于整个查询来说,这个非聚集索引设计并不完美,因为不是覆盖的非聚集索引,如果有人用指定索引来查找数据,就会非常恐怖:
SELECT * FROM Customers3 WITH(INDEX(idx_Test))
WHERE Value < 80001
GO
这个查询产生了165120个逻辑读,把聚集索引全表扫描需要的IO数直接秒杀!从这个例子我们可以看出,临界点是SQL Server里的性能保障,它阻止着使用书签查找,造成占用昂贵资源的查询发生。但这些和记录数完全无关。这2个例子里的表记录数都是80000。我们只修改了表记录的大小,因此我们就改变了表的大小,最后临界点也跟着改变,SQL Server就会忽略我们的非聚集索引。
寓意:非聚集索引,不是覆盖非聚集索引的话,在SQL Server里是非常,非常,非常,非常有选择性的用例!下次当你碰到这个情况的时候,想下你要怎么处理这个问题!
参考文章:
SQL Server临界点游戏——为什么非聚集索引被忽略!的更多相关文章
- SQL SERVER 读书笔记:非聚集索引
对于有聚集索引的表,数据存储在聚集索引的叶子节点,而非聚集索引则存储 索引键值 和 聚集索引键值.对于非聚集索引,如果查找的字段没有包含在索引键值,则还要根据聚集索引键值来查找详细数据,此谓 Book ...
- sql server临时删除/禁用非聚集索引并重新创建加回/启用的简便编程方法研究对比
前言: 由于新型冠状病毒影响,博主(zhang502219048)在2020年1月份从广东广州工作地回到广东揭阳产业转移工业园磐东街道(镇里有阳美亚洲玉都.五金之乡,素以“金玉”闻名)老家后,还没过去 ...
- SQL Server中通过设置非聚集索引(Non-Clustered index)来达到性能优化的目的
首先我们一下,在SQL Server 2014 Management Studio中,如何为一张表设置Non-Clustered index 具体可以参考 https://docs.microsof ...
- SQL Server 性能调优2 之索引(Index)的建立
前言 索引是关系数据库中最重要的对象之中的一个,他能显著降低磁盘I/O及逻辑读取的消耗,并以此来提升 SELECT 语句的查找性能.但它是一把双刃剑.使用不当反而会影响性能:他须要额外的空间来存放这些 ...
- SQL Server索引进阶:第二级,深入非聚集索引
原文地址: Stairway to SQL Server Indexes: Level 2, Deeper into Nonclustered Indexes 本文是SQL Server索引进阶系列( ...
- SQL Server 索引和表体系结构(非聚集索引)
非聚集索引 概述 对于非聚集索引,涉及的信息要比聚集索引更多一些,由于整个篇幅比较大涉及接下来的要写的“包含列的索引”,“索引碎片”等一些知识点,可能要结合起来阅读理解起来要更容易一些.非聚集索引和聚 ...
- SQL Server 解读【已分区索引的特殊指导原则】(3) - 非聚集索引分区
一.前言 在MSDN上看到一篇关于SQL Server 表分区的文档:已分区索引的特殊指导原则,如果你对表分区没有实战经验的话是比较难理解文档里面描述的意思.这里我就里面的一些概念进行讲解,方便大家的 ...
- SQL SERVER 索引之聚集索引和非聚集索引的描述
索引是与表或视图关联的磁盘上结构,可以加快从表或视图中检索行的速度. 索引包含由表或视图中的一列或多列生成的键. 这些键存储在一个结构(B 树)中,使 SQL Server 可以快速有效地查找与键值关 ...
- SQL Server 2014,表变量上的非聚集索引
从Paul White的推特上看到,在SQL Server 2014里,对于表变量(Table Variables),它是支持非唯一聚集索引(Non-Unique Clustered Indexes) ...
随机推荐
- ubuntu安装gitlab
#安装所有的依赖包 sudo apt-get install -y build-essential git-core sudo apt-get install -y zlib1g-dev libyam ...
- 【Android】Android 移动应用数据到SD
[Android]Android 移动应用数据到SD 在应用的menifest文件中指定就可以了,在 <manifest> 元素中包含android:installLocation 属性, ...
- Microsoft.VisualBasic.DateAndTime.Timer 与 DateTime.Now.TimeOfDay.TotalSeconds 相当
如题,示例如下: Console.WriteLine(DateTime.Now.TimeOfDay.TotalSeconds); Console.WriteLine(Microsoft.VisualB ...
- vs2010 使用vs online账号 需要安装的插件
VS10SP1-KB2662296.exe http://pan.baidu.com/s/1qWDpEG0 vs2010 需要先升级到SP1
- CentOS 伪装安装TSA for DB2
DB2 HADR需要额外安装TSA,正常情况下CentOS无法通过安装前验证.会报一个说发行版不支持的错误. 可以通过伪装成RHEL的方式使得正常安装. 修改方式如下 修改/etc/system-re ...
- 软件包管理 之 Fedora/Redhat 在线安装更新软件包,yum 篇 ── 给新手指南
在本文中,我们主要解介绍 Fedora core 4.0 通过软件包管理工具yum来在线安装更新软件:关于apt工具应用,我们会在另外一篇中介绍: 一. yum 的使用:有些初学Linux的弟兄可能问 ...
- 构造函数和:this()的应用
一.构造函数和:this()的应用 //本实例演示构造函数和:this()的应用 public class ClsA { public string A{set;get;} public string ...
- Flash Builder中“Error: #2036 加载未完成”错误的解决方法
复制了一个名称为A的widget包,重命名为B,包含B.mxml和B.xml(配置文件),编译后无法加载B包创建的widget,报错为: 解决办法: 1.在工程的根目录下找到.actionScript ...
- eclipse新建一个Android项目,就会报错android.support.v7.app.ActionBarActivity
解决方法: 今天被这个问题折腾了一下,最后终于找到了解决办法. 产生这个问题,是因为你升级了ADT到version 22,但是还需要升级SDK Tools,Platform Tools,Build T ...
- 【特别推荐】8个富有创意的jQuery/CSS3插件
现在的互联网上什么都有,但是真正好的创意却非常稀缺,包括WEB界面也是如此,今天我们要特别推荐8个富有创意的jQuery/CSS3插件,也许这几个插件能让你的WEB界面更加富有创意和人性化. 1.jQ ...