利用 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 相关文章推荐
2021-4-5课程——SQL Server查询【3】
Apr 05 SQL Server
SQLServer2019 数据库环境搭建与使用的实现
Apr 08 SQL Server
SQL Server2019数据库之简单子查询的具有方法
Apr 27 SQL Server
利用 SQL Server 过滤索引提高查询语句的性能分析
Jul 15 SQL Server
SQL中的三种去重方法小结
Nov 01 SQL Server
SQL Server2019数据库备份与还原脚本,数据库可批量备份
Nov 20 SQL Server
SQL Server实现分页方法介绍
Mar 16 SQL Server
SQL Server内存机制浅探
Apr 06 SQL Server
SQL Server表分区降低运维和维护成本
Apr 08 SQL Server
SQL Server使用T-SQL语句批处理
May 20 SQL Server
详解SQL报错盲注
Jul 23 SQL Server
SqlServer常用函数及时间处理小结
May 08 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
php实现批量压缩图片文件大小的脚本
2014/07/04 PHP
用javascript来实现动画导航效果的代码
2007/12/16 Javascript
JavaScript 对象成员的可见性说明
2009/10/16 Javascript
javascript天然的迭代器
2010/10/29 Javascript
js中各浏览器中鼠标按键值的差异
2011/04/07 Javascript
关于div自适应高度/左右高度自适应一致的js代码
2013/03/22 Javascript
js中reverse函数的用法详解
2013/12/26 Javascript
iframe子页面与父页面在同域或不同域下的js通信
2014/05/07 Javascript
javascript通过元素id和name直接取得元素的方法
2015/04/28 Javascript
对jquery的ajax进行二次封装以及ajax缓存代理组件:AjaxCache详解
2016/04/11 Javascript
vue.js实现含搜索的多种复选框(附源码)
2017/03/23 Javascript
vue单页应用加百度统计代码(亲测有效)
2018/01/31 Javascript
[52:40]完美世界DOTA2联赛PWL S2 Magma vs GXR 第一场 11.29
2020/12/02 DOTA
Python操作SQLite简明教程
2014/07/10 Python
Linux下使用python调用top命令获得CPU利用率
2015/03/10 Python
python使用wmi模块获取windows下硬盘信息的方法
2015/05/15 Python
在Python程序中操作文件之flush()方法的使用教程
2015/05/24 Python
python虚拟环境virtualenv的使用教程
2017/10/20 Python
对python中for、if、while的区别与比较方法
2018/06/25 Python
pyqt5利用pyqtDesigner实现登录界面
2019/03/28 Python
python将类似json的数据存储到MySQL中的实例
2019/07/12 Python
对Python获取屏幕截图的4种方法详解
2019/08/27 Python
git查看、创建、删除、本地、远程分支方法详解
2020/02/18 Python
Python操作Excel把数据分给sheet
2020/05/20 Python
Python3爬虫mitmproxy的安装步骤
2020/07/29 Python
Python+unittest+requests 接口自动化测试框架搭建教程
2020/10/09 Python
用python发送微信消息
2020/12/21 Python
Ticketmaster意大利:音乐会、节日、艺术和剧院的官方门票
2019/12/23 全球购物
高分子材料个人求职信范文
2013/09/25 职场文书
毕业生自荐书模版
2014/01/04 职场文书
出国签证在职证明
2014/01/16 职场文书
北京大学自荐信范文
2014/01/28 职场文书
房产委托公证书
2014/04/08 职场文书
影子教师研修方案
2014/06/14 职场文书
应届生自荐信
2014/06/30 职场文书
经典演讲稿开场白
2014/08/25 职场文书