百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

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

bigegpt 2024-08-04 11:39 9 浏览

一:背景

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 列 保存在叶子节点,不参与索引计算,相对来说占用的索引页空间更小。

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

相关推荐

最全的MySQL总结,助你向阿里“开炮”(面试题+笔记+思维图)

前言作为一名编程人员,对MySQL一定不会陌生,尤其是互联网行业,对MySQL的使用是比较多的。对于求职者来说,MySQL又是面试中一定会问到的重点,很多人拥有大厂梦,却因为MySQL败下阵来。实际上...

Redis数据库从入门到精通(redis数据库设计)

目录一、常见的非关系型数据库NOSQL分类二、了解Redis三、Redis的单节点安装教程四、Redis的常用命令1、Help帮助命令2、SET命令3、过期命令4、查找键命令5、操作键命令6、GET命...

netcore 急速接入第三方登录,不看后悔

新年新气象,趁着新年的喜庆,肝了十来天,终于发了第一版,希望大家喜欢。如果有不喜欢看文字的童鞋,可以直接看下面的地址体验一下:https://oauthlogin.net/前言此次带来得这个小项目是...

精选 30 个 C++ 面试题(含解析)(c++面试题和答案汇总)

大家好,我是柠檬哥,专注编程知识分享。欢迎关注@程序员柠檬橙,编程路上不迷路,私信发送以下关键字获取编程资源:发送1024打包下载10个G编程资源学习资料发送001获取阿里大神LeetCode...

Oracle 12c系列(一)|多租户容器数据库

作者杨禹航出品沃趣技术Oracle12.1发布至今已有多年,但国内Oracle12C的用户并不多,随着12.2在去年的发布,选择安装Oracle12c的客户量明显增加,在接下来的几年中,Or...

flutter系列之:UI layout简介(flutter-ui-nice)

简介对于一个前端框架来说,除了各个组件之外,最重要的就是将这些组件进行连接的布局了。布局的英文名叫做layout,就是用来描述如何将组件进行摆放的一个约束。在flutter中,基本上所有的对象都是wi...

Flutter 分页功能表格控件(flutter 列表)

老孟导读:前2天有读者问到是否有带分页功能的表格控件,今天分页功能的表格控件详细解析来来。PaginatedDataTablePaginatedDataTable是一个带分页功能的DataTable,...

Flutter | 使用BottomNavigationBar快速构建底部导航

平时我们在使用app时经常会看到底部导航栏,而在flutter中它的实现也较为简单.需要用到的组件:BottomNavigationBar导航栏的主体BottomNavigationBarI...

Android中的数据库和本地存储在Flutter中是怎样实现的

如何使用SharedPreferences?在Android中,你可以使用SharedPreferencesAPI来存储少量的键值对。在Flutter中,使用Shared_Pref...

Flet,一个Flutter应用的实用Python库!

▼Flet:用Python轻松构建跨平台应用!在纷繁复杂的Python框架中,Flet宛如一缕清风,为开发者带来极致的跨平台应用开发体验。它用最简单的Python代码,帮你实现移动端、桌面端...

flutter系列之:做一个图像滤镜(flutter photo)

简介很多时候,我们需要一些特效功能,比如给图片做个滤镜什么的,如果是h5页面,那么我们可以很容易的通过css滤镜来实现这个功能。那么如果在flutter中,如果要实现这样的滤镜功能应该怎么处理呢?一起...

flutter软件开发笔记20-flutter web开发

flutterweb开发优势比较多,采用统一的语言,就能开发不同类型的软件,在web开发中,特别是后台式软件中,相比传统的html5开发,更高效,有点像c++编程的方式,把web设计出来了。一...

Flutter实战-请求封装(五)之设置抓包Proxy

用了两年的flutter,有了一些心得,不虚头巴脑,只求实战有用,以供学习或使用flutter的小伙伴参考,学习尚浅,如有不正确的地方还望各路大神指正,以免误人子弟,在此拜谢~(原创不易,转发请标注来...

为什么不在 Flutter 中使用全局变量来管理状态

我相信没有人用全局变量来管理Flutter应用程序的状态。毫无疑问,我们的Flutter应用程序需要状态管理包或Flutter的基本小部件(例如InheritedWidget或St...

Flutter 攻略(Dart基本数据类型,变量 整理 2)

代码运行从main方法开始voidmain(){print("hellodart");}变量与常量var声明变量未初始化变量为nullvarc;//未初始化print(c)...