利用 SQL Server 过滤索引提高查询语句的性能分析


Posted in SQL Server onJuly 15, 2021

利用 SQL Server 过滤索引提高查询语句的性能分析

大家好,我是只谈技术不剪发的 Tony 老师。

Microsoft SQL Server 过滤索引(筛选索引)是指基于满足特定条件的数据行进行索引。与全表索引(默认创建)相比,设计良好的筛选索引可以提高查询性能、减少索引维护开销并可降低索引存储开销。本文就给大家介绍一下 Microsoft SQL Server 中的过滤索引功能。

在创建过滤索引之前,我们需要了解它的适用场景。

  • 在某个字段中只有少量相关值需要查询时,可以针对值的子集创建过滤索引。 例如,当字段中的值大部分为 NULL 并且查询只从非 NULL 值中进行选择时,可以为非 NULL 数据行创建筛选索引。 由此得到的索引与对相同字段定义的全表非聚集索引相比,前者更小且维护开销更低。
  • 表中含有分类数据行时,可以为一种或多种类别的数据创建筛选索引。 通过将查询范围缩小为表的特定区域,这可以提高针对这些数据行的查询性能。此外,由此得到的索引与全表非聚集索引相比,前者更小且维护开销更低。

我们在创建索引时可以通过一个 WHERE 子句指定需要索引的数据行,从而创建一个过滤索引。例如,对于以下订单表 orders:

CREATE TABLE orders (
  id INTEGER PRIMARY KEY,
  customer_id INTEGER,
  status VARCHAR(10)
);

BEGIN	
  DECLARE @counter INT = 1
  WHILE @counter <= 1000000
  BEGIN
    INSERT INTO orders
    SELECT @counter, (rand() * 100000),
          CASE 
            WHEN (rand() * 100)<1 THEN 'pending'
            WHEN (rand() * 100)>99 THEN 'shipped'
            ELSE 'completed'
          END
    SET @counter = @counter + 1
  END  
END;

订单表中总共有 100 万个订单,通常绝大部分的订单都处于完成状态。一般情况下,我们只需要针对某个用户未完成的订单进行查询跟踪,因此可以创建一个基于用户编号和状态的部分索引:

CREATE INDEX full_idx ON orders (customer_id, status);

然后我们查看以下查询语句的执行计划:

SET STATISTICS PROFILE ON

SELECT * 
FROM orders
WHERE customer_id = 5043
AND status != 'completed';
id    |customer_id|status |
------+-----------+-------+
743436|       5043|pending|
947848|       5043|shipped|

Rows	Executes	StmtText	StmtId	NodeId	Parent	PhysicalOp	LogicalOp	Argument	DefinedValues	EstimateRows	EstimateIO	EstimateCPU	AvgRowSize	TotalSubtreeCost	OutputList	Warnings	Type	Parallel	EstimateExecutions
2	1	SELECT * FROM [orders] WHERE [customer_id]=@1 AND [status]<>@2	1	1	0	NULL	NULL	NULL	NULL	1.405213	NULL	NULL	NULL	0.003283546	NULL	NULL	SELECT	0	NULL
2	1	  |--Index Seek(OBJECT:([hrdb].[dbo].[orders].[full_idx]), SEEK:([hrdb].[dbo].[orders].[customer_id]=(5043) AND [hrdb].[dbo].[orders].[status] < 'completed' OR [hrdb].[dbo].[orders].[customer_id]=(5043) AND [hrdb].[dbo].[orders].[status] > 'completed') ORDERED FORWARD)	1	2	1	Index Seek	Index Seek	OBJECT:([hrdb].[dbo].[orders].[full_idx]), SEEK:([hrdb].[dbo].[orders].[customer_id]=(5043) AND [hrdb].[dbo].[orders].[status] < 'completed' OR [hrdb].[dbo].[orders].[customer_id]=(5043) AND [hrdb].[dbo].[orders].[status] > 'completed') ORDERED FORWARD	[hrdb].[dbo].[orders].[id], [hrdb].[dbo].[orders].[customer_id], [hrdb].[dbo].[orders].[status]	1.405213	0.003125	0.0001585457	27	0.003283546	[hrdb].[dbo].[orders].[id], [hrdb].[dbo].[orders].[customer_id], [hrdb].[dbo].[orders].[status]	NULL	PLAN_ROW	0	1

输出结果显示查询利用索引 full_idx 扫描查找所需的数据。

我们可以查看一下索引 full_idx 占用的空间大小:

SELECT ix.name AS "Index name",
SUM(sz.used_page_count) * 8/1024.0 AS "Index size (MB)"
FROM sys.dm_db_partition_stats AS sz
INNER JOIN sys.indexes AS ix ON sz.object_id = ix.object_id
AND sz.index_id = ix.index_id
INNER JOIN sys.tables tn ON tn.OBJECT_ID = ix.object_id
WHERE tn.name = 'orders'
GROUP BY ix.name;

Index name                  |Index size (MB)|
----------------------------+---------------+
full_idx                    |      26.171875|
PK__orders__3213E83F1E3B8A3B|      29.062500|

接下来我们再创建一个部分索引,只包含未完成的订单数据,从而减少索引的数据量:

CREATE INDEX partial_idx ON orders (customer_id)
WHERE status != 'completed';

索引 partial_idx 中只有 customer_id 字段,不需要 status 字段。同样可以查看一下索引 partial_idx 占用的空间大小:

SELECT ix.name AS "Index name",
SUM(sz.used_page_count) * 8/1024.0 AS "Index size (MB)"
FROM sys.dm_db_partition_stats AS sz
INNER JOIN sys.indexes AS ix ON sz.object_id = ix.object_id
AND sz.index_id = ix.index_id
INNER JOIN sys.tables tn ON tn.OBJECT_ID = ix.object_id
WHERE tn.name = 'orders'
GROUP BY ix.name;

Index name                  |Index size (MB)|
----------------------------+---------------+
full_idx                    |      26.171875|
partial_idx                 |       0.289062|
PK__orders__3213E83F1E3B8A3B|      29.062500|

索引只有 0.29 MB,而不是 26 MB,因为绝大多数订单都处于完成状态。

以下查询显式了适用过滤索引时的执行计划:

SELECT * 
FROM orders WITH ( INDEX ( partial_idx ) )
WHERE customer_id = 5043
AND status != 'completed';

Rows	Executes	StmtText	StmtId	NodeId	Parent	PhysicalOp	LogicalOp	Argument	DefinedValues	EstimateRows	EstimateIO	EstimateCPU	AvgRowSize	TotalSubtreeCost	OutputList	Warnings	Type	Parallel	EstimateExecutions
2	1	SELECT *   FROM orders WITH ( INDEX ( partial_idx ) )  WHERE customer_id = 5043  AND status != 'completed'	1	1	0	NULL	NULL	NULL	NULL	1.124088	NULL	NULL	NULL	0.03279812	NULL	NULL	SELECT	0	NULL
2	1	  |--Nested Loops(Inner Join, OUTER REFERENCES:([hrdb].[dbo].[orders].[id]))	1	2	1	Nested Loops	Inner Join	OUTER REFERENCES:([hrdb].[dbo].[orders].[id])	NULL	1.124088	0	4.15295E-05	24	0.03279812	[hrdb].[dbo].[orders].[id], [hrdb].[dbo].[orders].[customer_id], [hrdb].[dbo].[orders].[status]	NULL	PLAN_ROW	0	1
2	1	       |--Index Seek(OBJECT:([hrdb].[dbo].[orders].[partial_idx]), SEEK:([hrdb].[dbo].[orders].[customer_id]=(5043)) ORDERED FORWARD)	1	3	2	Index Seek	Index Seek	OBJECT:([hrdb].[dbo].[orders].[partial_idx]), SEEK:([hrdb].[dbo].[orders].[customer_id]=(5043)) ORDERED FORWARD, FORCEDINDEX	[hrdb].[dbo].[orders].[id], [hrdb].[dbo].[orders].[customer_id]	9.935287	0.003125	0.0001679288	15	0.003292929	[hrdb].[dbo].[orders].[id], [hrdb].[dbo].[orders].[customer_id]	NULL	PLAN_ROW	0	1
2	2	       |--Clustered Index Seek(OBJECT:([hrdb].[dbo].[orders].[PK__orders__3213E83F1E3B8A3B]), SEEK:([hrdb].[dbo].[orders].[id]=[hrdb].[dbo].[orders].[id]) LOOKUP ORDERED FORWARD)	1	5	2	Clustered Index Seek	Clustered Index Seek	OBJECT:([hrdb].[dbo].[orders].[PK__orders__3213E83F1E3B8A3B]), SEEK:([hrdb].[dbo].[orders].[id]=[hrdb].[dbo].[orders].[id]) LOOKUP ORDERED FORWARD, FORCEDINDEX	[hrdb].[dbo].[orders].[status]	1	0.003125	0.0001581	16	0.02946366	[hrdb].[dbo].[orders].[status]	NULL	PLAN_ROW	0	9.935287

我们比较通过 full_idx 和 partial_idx 执行以下查询的时间:

-- 300 ms
SELECT count(*)
FROM orders WITH ( INDEX ( full_idx ) )
WHERE status != 'completed';

-- 10 ms
SELECT count(*) 
FROM orders WITH ( INDEX ( partial_idx ) )
WHERE status != 'completed';

另外,过滤索引还可以用于实现其他的功能。例如,我们可以将索引 partial_idx 定义为唯一索引,从而实现每个用户只能存在一个未完成订单的约束。

DROP INDEX partial_idx ON orders;
TRUNCATE TABLE orders;

CREATE UNIQUE INDEX partial_idx ON orders (customer_id)
WHERE status != 'completed';

INSERT INTO orders(id, customer_id, status) VALUES (1, 1, 'pending');

INSERT INTO orders(id, customer_id, status) VALUES (2, 1, 'pending');
SQL 错误 [2601] [23000]: 不能在具有唯一索引“partial_idx”的对象“dbo.orders”中插入重复键的行。重复键值为 (1)。

用户必须完成一个订单之后才能继续生成新的订单。

通过以上介绍可以看出,过滤索引是一种经过优化的非聚集索引,尤其适用于从特定数据子集中选择数据的查询。

到此这篇关于利用 SQL Server 过滤索引提高查询语句的性能分析的文章就介绍到这了,更多相关SQL Server索引提高语句性能内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

SQL Server 相关文章推荐
SQL Server中交叉联接的用法详解
Apr 22 SQL Server
SQL 窗口函数实现高效分页查询的案例分析
May 21 SQL Server
解决sql server 数据库,sa用户被锁定的问题
Jun 11 SQL Server
数据库之SQL技巧整理案例
Jul 07 SQL Server
Sql Server之数据类型详解
Feb 28 SQL Server
详解在SQLPlus中实现上下键翻查历史命令的功能
Mar 18 SQL Server
SQL Server使用导出向导功能
Apr 08 SQL Server
如何使用SQL Server语句创建表
Apr 12 SQL Server
使用 MybatisPlus 连接 SqlServer 数据库解决 OFFSET 分页问题
Apr 22 SQL Server
SQL Server一个字符串拆分多行显示或者多行数据合并成一个字符串
May 25 SQL Server
SQL解决未能删除约束问题drop constraint
May 30 SQL Server
SqlServer数据库远程连接案例教程
数据库之SQL技巧整理案例
Jul 07 #SQL Server
SQL Server中使用判断语句(IF ELSE/CASE WHEN )案例
Jul 07 #SQL Server
SQL Server代理:理解SQL代理错误日志处理方法
SQL Server作业失败:无法确定所有者是否有服务器访问权限的解决方法
SQLServer 错误: 15404,无法获取有关 Windows NT 组/用户 WIN-8IVSNAQS8T7\Administrator 的信息
SQL 尚未定义空闲 CPU 条件 - OnIdle 作业计划将不起任何作用
You might like
关于Intype一些小问题的解决办法
2008/03/28 PHP
php 判断访客是否为搜索引擎蜘蛛的函数代码
2011/07/29 PHP
php实现12306火车票余票查询和价格查询(12306火车票查询)
2014/01/14 PHP
php表单处理操作
2017/11/16 PHP
PHP中通过getopt解析GNU C风格命令行选项
2019/11/18 PHP
使用Firebug对js进行断点调试的图文方法
2011/04/02 Javascript
JSON+JavaScript处理JSON的简单例子
2013/03/20 Javascript
JS 去前后空格大全(IE9亲测)
2013/07/15 Javascript
Bootstrap入门书籍之(五)导航条、分页导航
2016/02/17 Javascript
nodejs实现截取上传视频中一帧作为预览图片
2017/12/10 NodeJs
JS处理一些简单计算题
2018/02/24 Javascript
js实现弹出框的拖拽效果实例代码详解
2019/04/16 Javascript
ng-alain的sf如何自定义部件的流程
2020/06/12 Javascript
vue element ui validate 主动触发错误提示操作
2020/09/21 Javascript
js通过canvas生成图片缩略图
2020/10/02 Javascript
vue+vant 上传图片需要注意的地方
2021/01/03 Vue.js
pyqt4教程之widget使用示例分享
2014/03/07 Python
Python实现的简单万年历例子分享
2014/04/25 Python
python pycurl验证basic和digest认证的方法
2018/05/02 Python
Python 2.7中文显示与处理方法
2018/07/16 Python
Python3爬虫全国地址信息
2019/01/05 Python
python跳出双层for循环的解决方法
2019/06/24 Python
Django之choices选项和富文本编辑器的使用详解
2020/04/01 Python
vscode配置anaconda3的方法步骤
2020/08/08 Python
Python操控mysql批量插入数据的实现方法
2020/10/27 Python
Python常用断言函数实例汇总
2020/11/30 Python
如何开发一款堪比APP的微信小程序(腾讯内部团队分享)
2016/12/22 HTML / CSS
生产厂厂长岗位职责
2013/12/25 职场文书
对标管理实施方案
2014/03/12 职场文书
公民代理授权委托书
2014/09/24 职场文书
反腐倡廉剖析材料
2014/09/30 职场文书
2015年度团总支工作总结
2015/04/23 职场文书
2015年度招聘工作总结
2015/05/28 职场文书
学习十八大的感悟
2015/08/11 职场文书
推广普通话主题班会
2015/08/17 职场文书
python实现socket简单通信的示例代码
2021/04/13 Python