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

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
简介:

当我们进行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就会忽略我们的非聚集索引。


本文转自Woodytu博客园博客,原文链接:http://www.cnblogs.com/woodytu/p/4546520.html,如需转载请自行联系原作者

相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS&nbsp;SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/sqlserver
相关文章
|
20天前
|
存储 SQL 关系型数据库
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
MySQL调优主要分为三个步骤:监控报警、排查慢SQL、MySQL调优。 排查慢SQL:开启慢查询日志 、找出最慢的几条SQL、分析查询计划 。 MySQL调优: 基础优化:缓存优化、硬件优化、参数优化、定期清理垃圾、使用合适的存储引擎、读写分离、分库分表; 表设计优化:数据类型优化、冷热数据分表等。 索引优化:考虑索引失效的11个场景、遵循索引设计原则、连接查询优化、排序优化、深分页查询优化、覆盖索引、索引下推、用普通索引等。 SQL优化。
167 15
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
|
20天前
|
存储 关系型数据库 MySQL
MySQL高级篇——覆盖索引、前缀索引、索引下推、SQL优化、主键设计
覆盖索引、前缀索引、索引下推、SQL优化、EXISTS 和 IN 的区分、建议COUNT(*)或COUNT(1)、建议SELECT(字段)而不是SELECT(*)、LIMIT 1 对优化的影响、多使用COMMIT、主键设计、自增主键的缺点、淘宝订单号的主键设计、MySQL 8.0改造UUID为有序
MySQL高级篇——覆盖索引、前缀索引、索引下推、SQL优化、主键设计
|
11天前
|
SQL 存储 索引
SQL Server的Descending Indexes降序索引
【9月更文挑战第21天】在SQL Server中,降序索引允许指定列的排序顺序为降序,可显著优化涉及降序排序的查询性能,特别是在复合索引中。通过创建降序索引,可以更高效地满足特定业务需求,如按交易时间降序获取最新记录。然而,使用时需考虑查询频率、数据分布及维护成本,以确保最佳性能。
|
7天前
|
关系型数据库 MySQL 网络安全
5-10Can't connect to MySQL server on 'sh-cynosl-grp-fcs50xoa.sql.tencentcdb.com' (110)")
5-10Can't connect to MySQL server on 'sh-cynosl-grp-fcs50xoa.sql.tencentcdb.com' (110)")
|
2月前
|
SQL 分布式计算 MaxCompute
一种基于ODPS SQL的全局字典索引分布式计算思路
本文提供一种能充分利用分布式计算资源来计算全局字典索引的方法,以解决在大数据量下使用上诉方式导致所有数据被分发到单个reducer进行单机排序带来的性能瓶颈。
|
3月前
|
SQL 关系型数据库 分布式数据库
PolarDB产品使用问题之相同的SQL语句在不同时间执行EXPLAIN计划显示出不同的索引类型,是什么原因
PolarDB产品使用合集涵盖了从创建与管理、数据管理、性能优化与诊断、安全与合规到生态与集成、运维与支持等全方位的功能和服务,旨在帮助企业轻松构建高可用、高性能且易于管理的数据库环境,满足不同业务场景的需求。用户可以通过阿里云控制台、API、SDK等方式便捷地使用这些功能,实现数据库的高效运维与持续优化。
PolarDB产品使用问题之相同的SQL语句在不同时间执行EXPLAIN计划显示出不同的索引类型,是什么原因
|
2月前
|
SQL 存储 数据库
|
2月前
|
存储 SQL 数据库
|
2月前
|
SQL 关系型数据库 MySQL
SQL Server、MySQL、PostgreSQL:主流数据库SQL语法异同比较——深入探讨数据类型、分页查询、表创建与数据插入、函数和索引等关键语法差异,为跨数据库开发提供实用指导
【8月更文挑战第31天】SQL Server、MySQL和PostgreSQL是当今最流行的关系型数据库管理系统,均使用SQL作为查询语言,但在语法和功能实现上存在差异。本文将比较它们在数据类型、分页查询、创建和插入数据以及函数和索引等方面的异同,帮助开发者更好地理解和使用这些数据库。尽管它们共用SQL语言,但每个系统都有独特的语法规则,了解这些差异有助于提升开发效率和项目成功率。
132 0
|
2月前
|
SQL 关系型数据库 MySQL
SQL索引构建与优化的神奇之处:如何用高效索引让你的数据检索飞起来?
【8月更文挑战第31天】在现代软件开发中,数据库索引对于提升查询性能至关重要。本文详细介绍了SQL索引的概念、构建方法及优化技巧,包括避免不必要的索引、使用复合索引等策略,并提供了实用的示例代码,如 `CREATE INDEX index_name ON table_name (column_name, another_column_name);`。通过遵循这些最佳实践,如了解查询模式和定期维护索引,可以大幅提高数据检索效率,从而增强应用程序的整体性能。
75 0
下一篇
无影云桌面