SQLSERVER 的复合索引和包含索引到底有啥区别?

news2025/1/6 20:30:20

一:背景

1. 讲故事

在 SQLSERVER 中有非常多的索引,比如:聚集索引,非聚集索引,唯一索引,复合索引,Include索引,交叉索引,连接索引,奇葩索引等等,当索引多了之后很容易傻傻的分不清,比如:复合索引Include索引,但又在真实场景中用的特别多,本篇我们就从底层数据页层面厘清一下。

二:到底有什么区别

1. 这些索引解决了什么问题

说区别之前,一定要知道它们大概解决了什么问题?这里我就从 索引覆盖 角度来展开吧,为了方便讲述,先上一个测试 sql:


IF(OBJECT_ID('t') IS NOT NULL) DROP TABLE t;

CREATE TABLE t(a INT IDENTITY, b CHAR(6), c CHAR(10) DEFAULT 'aaaaaaaaaa')

SET NOCOUNT ON
DECLARE @num INT 
SET @num =10000
WHILE (@num <90000)
BEGIN
	INSERT INTO t(b) VALUES ('b'+CAST(@num AS CHAR(5)))
	SET @num=@num+1
END

CREATE CLUSTERED INDEX idx_a ON t(a)
CREATE INDEX idx_b ON t(b)

SELECT * FROM t;

代码非常简单,在 t 表中创建三个列,插入 8w 条数据,然后创建两个索引,接下来做一个查询获取 b,c 列。


SET STATISTICS IO ON
SET STATISTICS TIME ON
SELECT b,c FROM t WHERE b IN  ('b10000','b20000','b30000','b40000','b50000','b70000','b80000','b90000')
SET STATISTICS IO OFF
SET STATISTICS TIME OFF

输出如下:


表“t”。扫描计数 8,逻辑读取次数 30,物理读取次数 0,页面服务器读取次数 0,预读读取次数 0,页面服务器预读读取次数 0,LOb 逻辑读取次数 0,LOB 逻辑读取次数 0,LOB 页面服务器读取次数 0,LOB 预读读取次数 0,LOB 页面服务器预读读取次数 0。

 SQL Server 执行时间:
   CPU 时间 = 0 毫秒,占用时间 = 134 毫秒。

 SQL Server 执行时间:
   CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。

Completion time: 2023-01-06T08:47:45.2364473+08:00

从执行计划看,这是一个经典的 书签查找,这种查找返回的行数越多性能越差,在索引优化时一般都会规避掉这种情况,我们也看到了逻辑读取次数有 30 次,那能不能再小一点呢?

为了解决这个问题,干脆把 c 列也放到索引中去达到索引覆盖的效果,这就需要用到 复合索引 了,参考sql如下:


CREATE INDEX idx_complex ON t (b,c)

再次查询输出如下:


SQL Server 分析和编译时间: 
   CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
表“t”。扫描计数 8,逻辑读取次数 24,物理读取次数 0,页面服务器读取次数 0,预读读取次数 0,页面服务器预读读取次数 0,LOb 逻辑读取次数 0,LOB 逻辑读取次数 0,LOB 页面服务器读取次数 0,LOB 预读读取次数 0,LOB 页面服务器预读读取次数 0。

 SQL Server 执行时间:
   CPU 时间 = 0 毫秒,占用时间 = 96 毫秒。

 SQL Server 执行时间:
   CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。

Completion time: 2023-01-06T08:53:56.9688921+08:00

从执行计划来看,这次没有走 书签查找 而是 索引查找,并且逻辑读也降到了 24 次,这是一个好的优化。

相信有些朋友也知道用 Include索引 也能达到这个效果,接下来试着把复合索引给删了增加一个 Include索引,代码如下:


DROP INDEX idx_complex ON dbo.t;
CREATE INDEX idx_include ON  t(b) INCLUDE (c)

再次查询输出如下:


表“t”。扫描计数 8,逻辑读取次数 16,物理读取次数 0,页面服务器读取次数 0,预读读取次数 0,页面服务器预读读取次数 0,LOb 逻辑读取次数 0,LOB 逻辑读取次数 0,LOB 页面服务器读取次数 0,LOB 预读读取次数 0,LOB 页面服务器预读读取次数 0。

 SQL Server 执行时间:
   CPU 时间 = 0 毫秒,占用时间 = 73 毫秒。

 SQL Server 执行时间:
   CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。

Completion time: 2023-01-06T08:58:18.1122561+08:00

从执行计划来看也是走的 非聚集索引,而且逻辑读再次降到了 16 次,相比原始的书签查找已经优化了 50%,这是一个巨大的性能提升不是。

到这里其实有一个问题,两种优化走的都是 非聚集索引,从逻辑读次数看貌似 Include索引 更好一些,为什么会这样呢?这就涉及到了底层存储,接下来一起扒一下。

2. 存储原理研究

研究它们的不同点,最彻底的方式就是从底层存储出发,首先我们观察下 复合索引 的底层存储是什么样的,可以用 DBCC 命令。


DBCC TRACEON(3604)
DBCC IND(MyTestDB,t,-1)

IndexLevel=2 来看这个复合索引构成的B树已经达到了二层,接下来我们查一下 368 号数据页内容。


DBCC PAGE(MyTestDB,1,368,2)

输出如下:


PAGE: (1:368)

Memory Dump @0x000000F555578000

000000F555578000:   01020002 00800001 00000000 00001b00 00000000  ....................
000000F555578014:   00000200 3e010000 601f9c00 70010000 01000000  ....>...`...p.......
000000F555578028:   f8000000 e0680000 f5010000 00000000 00000000  .....h..............
000000F55557803C:   00000000 01000000 00000000 00000000 00000000  ....................
000000F555578050:   00000000 00000000 00000000 00000000 16623130  .................b10
000000F555578064:   30303061 61616161 61616161 61010000 00380500  000aaaaaaaaaa....8..
000000F555578078:   00010004 00001662 38333631 36616161 61616161  .......b83616aaaaaaa
000000F55557808C:   61616191 1f010070 05000001 00040000 00006231  aaa....p..........b1

OFFSET TABLE:

Row - Offset                        
1 (0x1) - 126 (0x7e)                
0 (0x0) - 96 (0x60)                 


DBCC 执行完毕。如果 DBCC 输出了错误信息,请与系统管理员联系。

根据下面的 Slot 个数可以知道这个分支节点数据页只有 2 条记录,分别为:(b10000,aaaaaaaaaa,0x01) , (b83616,aaaaaaaaaa,0x011f91),这里说明一下最后的 01 和 0x011f91 是主键key,接下来找个叶子节点,比如:1632 号索引页。


PAGE: (1:1632)


Memory Dump @0x000000F555578000

...
000000F555578050:   00000000 00000000 00000000 00000000 16623135  .................b15
000000F555578064:   32383761 61616161 61616161 61a81400 00040000  287aaaaaaaaaa.......
000000F555578078:   16623135 32383861 61616161 61616161 61a91400  .b15288aaaaaaaaaa...
000000F55557808C:   00040000 16623135 32383961 61616161 61616161  .....b15289aaaaaaaaa
000000F5555780A0:   61aa1400 00040000 16623135 32393061 61616161  a........b15290aaaaa
000000F5555780B4:   61616161 61ab1400 00040000 16623135 32393161  aaaaa........b15291a
000000F5555780C8:   61616161 61616161 61ac1400 00040000 16623135  aaaaaaaaa........b15
000000F5555780DC:   32393261 61616161 61616161 61ad1400 00040000  292aaaaaaaaaa.......
000000F5555780F0:   16623135 32393361 61616161 61616161 61ae1400  .b15293aaaaaaaaaa...
000000F555578104:   00040000 16623135 32393461 61616161 61616161  .....b15294aaaaaaaaa
000000F555578118:   61af1400 00040000 16623135 32393561 61616161  a........b15295aaaaa
000000F55557812C:   61616161 61b01400 00040000 16623135 32393661  aaaaa........b15296a
000000F555578140:   61616161 61616161 61b11400 00040000 16623135  aaaaaaaaa........b15
...

从叶子节点上看,也是 (b,c,key) 的布局模式,这时候脑子里就有了一张图。

用同样的方式观察下 Include索引,发现 IndexLevel=1,说明只有一层。

再用 DBCC 观察下分支节点的布局。


PAGE: (1:1696)

Memory Dump @0x000000F554F78000

000000F554F78000:   01020001 00820001 00000000 00001100 00000000  ....................
000000F554F78014:   00000601 42010000 1c09d814 a0060000 01000000  ....B....	..........
000000F554F78028:   0f010000 78310000 39010000 00000000 00000000  ....x1..9...........
000000F554F7803C:   f01efa04 00000000 00000000 00000000 00000000  ....................
000000F554F78050:   00000000 00000000 00000000 00000000 16623130  .................b10
000000F554F78064:   30303001 00000088 03000001 00030000 16623130  000..............b10
000000F554F78078:   33313138 010000b0 03000001 00030000 16623130  3118.............b10
000000F554F7808C:   3632326f 020000b1 03000001 00030000 16623130  622o.............b10
000000F554F780A0:   393333a6 030000b2 03000001 00030000 16623131  933..............b11
...

从输出看并没有记录 列c 的值,就是那烦人的 aaaaaaaaaa,然后再抽个叶子节点看看,比如:1218号索引页。


PAGE: (1:1218)
Memory Dump @0x000000F554F78000

000000F554F78000:   01020000 04020001 c1040000 01001500 c3040000  ....................
000000F554F78014:   01003701 42010000 0a00881d c2040000 01000000  ..7.B...............
000000F554F78028:   0f010000 00310000 03000000 00000000 00000000  .....1..............
000000F554F7803C:   e7351886 00000000 00000000 00000000 00000000  .5..................
000000F554F78050:   00000000 00000000 00000000 00000000 16623833  .................b83
000000F554F78064:   313235a6 1d010061 61616161 61616161 61040000  125....aaaaaaaaaa...
000000F554F78078:   16623833 313236a7 1d010061 61616161 61616161  .b83126....aaaaaaaaa
000000F554F7808C:   61040000 16623833 313237a8 1d010061 61616161  a....b83127....aaaaa
000000F554F780A0:   61616161 61040000 16623833 313238a9 1d010061  aaaaa....b83128....a
000000F554F780B4:   61616161 61616161 61040000 16623833 313239aa  aaaaaaaaa....b83129.
000000F554F780C8:   1d010061 61616161 61616161 61040000 16623833  ...aaaaaaaaaa....b83
000000F554F780DC:   313330ab 1d010061 61616161 61616161 61040000  130....aaaaaaaaaa...
...

在叶子节点中我们终于看到了 aaaaaaaaaa ,其实想一想肯定是有的,不然怎么做索引覆盖呢?有了这些信息,脑子中又有了一张图。

从图中可以看出,Include索引 的分支节点是不包含 c 列的,这个列只会保存在 叶子节点 中,再结合树的高度来看就能解释为什么 Include索引 的逻辑读要少于 复合索引

三:总结

总的来说 复合索引Include索引 各有利弊吧,前者会让索引页的行数据更大,导致索引页更多,也就会占用更多的存储空间,更多的逻辑读,索引维护开销也更大,而后者只会将 Include 列 保存在叶子节点,不参与索引计算,相对来说占用的索引页空间更小。

在查询方面,复合索引能达到的索引覆盖场景远大于单列索引,而且在过滤,排序场景下也能发挥奇效,所以还是根据你的读写比例做一个取舍吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/144093.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

首发ML-30s+,一径打响CES激光雷达大战第一枪

作者 | 王博 编辑 | 于婷2023年1月&#xff0c;CES再度在美国拉斯维加斯拉开帷幕。随着车企在车载软硬件上不断加大投入&#xff0c;CES也越发变得像一个高规格的全球车展。 根据研究机构Counterpoint的测算&#xff0c;由于高阶ADAS和Robotaxi普及&#xff0c;到2030年全球激光…

Spring Cloud Netflix 全套组件入门到实战

文章目录简介注册中心核心功能高可用配置服务调用RestTemplateRestTemplateRibbonFegin负载策略超时&重试服务熔断降级机制隔离机制线程池隔离信号量隔离Hystrix仪表板服务路由启用Zuul路由配置参考文档代码仓库通过本文可以给你带来什么&#xff1f;熟悉掌握Spring Cloud&…

关于OPCUA的配套规范

OPC UA中的信息建模能力足够强大&#xff0c;使OPC UA成为定义从简单的数据&#xff08;如工程单位和传感器或设备生成的最大/最小范围&#xff09;到大型复杂关系的理想选择&#xff0c;其中包括涉及数据结构&#xff0c;方法和状态机的复杂对象类型的实例化。也就是说&#x…

mysql数据库的基础操作(一)

一、导入/导出sql脚本 1.1 导入sql脚本 1.1.1 终端导入sql脚本 在mysql中&#xff0c;执行source命令 mysql> source /chenshuai/cs.sql 1.1.2 可视化工具导入sql脚本 在Navicat Premium中&#xff0c;右键这个数据库&#xff0c;然后Execute SQL File 1.2 导出sql脚本…

Redis主从、哨兵、集群模式

众所周知,redis是目前非常流行的缓存中间件之一。在redis官网有这么一段话: redis有着丰富的数据结构&#xff0c;如 字符串&#xff08;strings&#xff09;&#xff0c; 散列&#xff08;hashes&#xff09;&#xff0c; 列表&#xff08;lists&#xff09;&#xff0c; 集合…

Please restart this script from an administrative PowerShell!

执行 npm install --global --vs2019 --production windows-build-tools报一下错误信息 Downloading python-2.7.15.amd64.msi Downloading Python failed. Error: Error: getaddrinfo ENOTFOUND cdn.npmmirror.com at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:…

养老院管理系统|基于JavaWeb开发实现养老院管理系统

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

ROS用pyqt实现GUI界面控制乌龟运动

#!/usr/bin/env python3 #codingutf-8 from PyQt5.QtWidgets import * import sys from window import MainWindow,TurtleWindow import rospy if __name__ __main__: rospy.init_node(turtle_ctrl_node) appQApplication(sys.argv) #窗口展示 windowTurtleWindow() …

问题排查 - DotNet 6 后台服务Docker部署后部分接口返回值乱码

问题描述 最近有一个DotNetWebApi服务需要使用Docker在服务器部署&#xff0c;部署后部分接口返回内容会出现部分乱码的情况。 一些关键点&#xff1a; 服务本机直接启动&#xff0c;此接口无异常&#xff1b;服务器直接启动&#xff0c;此接口无异常 服务器为Ubuntu20.04安…

rabbitmq加入linux开机自启动脚本

cd /etc/init.d编辑脚本 vi rabbitmq#! /bin/sh # chkconfig: 2345 20 60 # description: rabbitmq server export HOME/root export PATH/usr/local/rabbitmq/erlang/bin:$PATH case "$1" instart)/usr/local/rabbitmq/sbin/rabbitmq-server start;;stop)/usr/loc…

命令模式Command

1.意图&#xff1a;将一个请求封装为一个对象&#xff0c;从而使得可以用不同的请求对客户进行参数化&#xff1b;对请求排队或记录请求日志&#xff0c;以及支持可撤销的操作。 2.结构 Command声明执行操作的接口。 ConcreteCommand将一个接收者对象绑定于一个动作&#xff1…

利用nginx 反向代理解决跨域问题

说到nginx&#xff0c;不得不说真的很强大&#xff0c;也带来很多便利用于解决一些头疼的难题。 一般来说可以用来做&#xff1a;静态页面的服务器、静态文件缓存服务器、网站反向代理、负载均衡服务器等等&#xff0c;而且实现这一切&#xff0c;基本只需要改改那万能的配置…

前端插件的应用

像这种页面四个页面下面的展示格式都一样&#xff0c;这个时候就把公共部分代码抽取出来作为组件使用 直接把中间部分代码赋值过来 <template> <div> <div v-for"(items, index) in ford" :key"index"> <div v-if"items.shopC…

P3- 傅里叶变换1-通讯原理

前言&#xff1a;傅里叶变换是Modulation, OFDM 技术的理论基础这里主要介绍连续性随机变量的傅里叶变换,以及对应的性质。https://zhuanlan.zhihu.com/p/339281545https://wenku.baidu.com/view/ab338e55a16925c52cc58bd63186bceb19e8ede4.html?_wkts_1672887094135https://w…

pdf如何防止被他人编辑

好多人一直在寻找研究如何编辑或者修改PDF文件&#xff0c;有需求就会有市场&#xff0c;现在市场上或者网络上有非常多的PDF编辑软件&#xff0c;可以编辑修改PDF文件中的内容。即使PDF文件加了密码保护&#xff0c;我们大部分人依然可以轻易的通过在浏览器中虚拟打印的方式&a…

2023.1.6 学习总结

1.set容器的相关函数 set&#xff0c;顾名思义是“集合”的意思&#xff0c;在set中元素都是唯一的&#xff0c;而且默认情况下会对元素自动进行升序排列。 set容器 包含头文件&#xff1a; #include<set> set中只能用insert函数实现数据的输入。 set<int> s…

32.深度学习模型优化加速方法-1

32.1 模型优化加速方法 模型优化加速能够提升网络的计算效率,具体包括: Op-level的快速算法:FFT Conv2d (7x7, 9x9), Winograd Conv2d (3x3, 5x5) 等;Layer-level的快速算法:Sparse-block net [1] 等;优化工具与库:TensorRT (Nvidia), Tensor Comprehension (Facebook) …

【代码题】栈的应用

目录 1.有效的括号 2.逆波兰表达式求值 1.有效的括号 点击进入该题 https://leetcode.cn/problems/valid-parentheses/description/ 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有…

【黑马】瑞吉外卖-Day01、02笔记

瑞吉外卖 数据库搭建 表结构 Maven项目 创建Maven项目 编写pom文件 编写配置文件application.yml 创建启动类ReggieApplication.java 前端静态资源的配置 将两个前端静态资源包导入到resource目录下方&#xff0c;由于Spring-MVC默认只能访问static和templete下面的文件…

04-GC的常见收集方法:标记清除、标记复制、标记整理算法、分代收集的原理与特点?

1.标记清除算法(Mark-Sweep) 最基础的垃圾回收算法&#xff0c;分为两个阶段&#xff0c;标记和清除。 1.标记阶段标记出所有需要回收的对象 2.清除阶段回收被标记的对象所占用的空间 缺点: 1.容易产生大量的内存碎片,后续可能发生大对象不能找到可利用空间的问题 2.标记和清…