利用 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索引提高语句性能内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SQL Server 索引结构及其使用(二) 改善SQL语句第1/3页

    比如: select * from table1 where name=''zhangsan'' and tID > 10000 和执行: select * from table1 where tID > 10000 and name=''zhangsan'' 一些人不知道以上两条语句的执行效率是否一样,因为如果简单的从语句先后上看,这两个语句的确是不一样,如果tID是一个聚合索引,那么后一句仅仅从表的10000条以后的记录中查找就行了:而前一句则要先从全表中查找看有几个name=''zhan

  • Sql Server 索引使用情况及优化的相关Sql语句分享

    复制代码 代码如下: --Begin Index(索引) 分析优化的相关 Sql -- 返回当前数据库所有碎片率大于25%的索引 -- 运行本语句会扫描很多数据页面 -- 避免在系统负载比较高时运行 -- 避免在系统负载比较高时运行 declare @dbid int select @dbid = db_id() SELECT o.name as tablename,s.* FROM sys.dm_db_index_physical_stats (@dbid, NULL, NULL, NULL,

  • SQL Server 索引维护sql语句

    使用以下脚本查看数据库索引碎片的大小情况: 复制代码 代码如下: DBCC SHOWCONTIG WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS 以下使用脚本来处理维护作业: 复制代码 代码如下: /*Perform a 'USE <database name>' to select the database in which to run the script.*/ -- Declare variables SET NOCOUNT ON;

  • Sql Server 数据库索引整理语句,自动整理数据库索引

    在一个大型数据库中,数据的更改是非常频繁的. 而建立在这些数据上的索引也是需要经常去维护的. 否则这这些数据索引就起不到起应起的作用.甚至会成为数据库本身的负担. 我们就要定期的对数据库的索引进行维护 我在MSDN上发现了这个脚本不过其中有些小问题我已经修正 大家可以使用这个脚本对数据库的索引进行日常维护 复制代码 代码如下: SET NOCOUNT ON; DECLARE @objectid int; DECLARE @indexid int; DECLARE @partitioncount

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

    大家好,我是只谈技术不剪发的 Tony 老师. Microsoft SQL Server 过滤索引(筛选索引)是指基于满足特定条件的数据行进行索引.与全表索引(默认创建)相比,设计良好的筛选索引可以提高查询性能.减少索引维护开销并可降低索引存储开销.本文就给大家介绍一下 Microsoft SQL Server 中的过滤索引功能. 在创建过滤索引之前,我们需要了解它的适用场景. 在某个字段中只有少量相关值需要查询时,可以针对值的子集创建过滤索引. 例如,当字段中的值大部分为 NULL 并且查询只

  • 详解MySQL的limit用法和分页查询语句的性能分析

    limit用法 在我们使用查询语句的时候,经常要返回前几条或者中间某几行数据,这个时候怎么办呢?不用担心,mysql已经为我们提供了这样一个功能. SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset LIMIT 子句可以被用于强制 SELECT 语句返回指定的记录数.LIMIT 接受一个或两个数字参数.参数必须是一个整数常量.如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参数指定返回记录行的最大数目.初始记

  • SQL设置SQL Server最大连接数及查询语句

    一.设置最大连接数 下面的T-SQL 语句可以配置SQL Server 允许的并发用户连接的最大数目. exec sp_configure 'show advanced options', 1 exec sp_configure 'user connections', 100 第一句用以表示显示sp_configure 系统存储过程高级选项,使用user connections 时,要求show advanced options 值为1. 第二句配置最大连接数为100,0 表示不限制,但不表示无

  • 在SQL Server中使用子查询更新语句

    测试环境准备 create table #table1 ( id int , name varchar(20) ); go create table #table2 ( id int , name varchar(20) ); go insert into #table1 ( id, name ) values ( 1, 'a' ), ( 2, null ), ( 3, 'c' ), ( 4, 'd' ), ( 5, 'e' ); insert into #table2 ( id, name )

  • MySql索引提高查询速度常用方法代码示例

    使用索引提高查询速度 1.前言 在web开发中,业务模版,业务逻辑(包括缓存.连接池)和数据库这三个部分,数据库在其中负责执行SQL查询并返回查询结果,是影响网站速度最重要的性能瓶颈.本文主要针对Mysql数据库,在淘宝的去IOE(I 代表IBM的缩写,即去IBM的存储设备和小型机:O是代表Oracle的缩写,去Oracle数据库,采用Mysql和Hadoop代替:E是代表EMC2,去EMC2的设备性,用PC server代替EMC2),大量使用Mysql集群!而优化数据的重要一步就是索引的建立

  • SQL Server实现全文搜索查询详解

    目录 一.概述 二.全文搜索查询 三.将全文搜索查询与 LIKE 谓词进行比较 四.全文搜索体系结构 4.1.SQL Server 进程 4.2.过滤器守护程序主机进程 五.全文搜索处理 5.1.全文索引过程 5.2.全文查询流程 六.全文索引体系结构 6.1.全文索引结构 6.2.全文索引片段 6.3.全文索引和常规 SQL Server 索引之间的差异 总结 一.概述 全文索引在表中包括一个或多个基于字符的列.这些列可以具有以下任何数据类型:char.varchar.nchar.nvarch

  • 如何使用索引提高查询速度

    使用索引提高查询速度1.前言在web开发中,页面模板,业务逻辑(包括缓存.连接池)和数据库这三个部分,数据库在其中负责执行SQL查询并返回查询结果,是影响网站速度最重要的性能瓶颈.本文主要针对MySql数据库,双十一的电商大战,引发了淘宝技术热议,而淘宝现在去IOE(I代表IBM的缩写,即去IBM的存储设备和小型机;O是代表Oracle的缩写,也即去Oracle数据库,采用MySQL和Hadoop替代的解决方案,;E是代表EMC2,即去EMC2的设备性,用PC Server替代EMC2),大量采

  • SQL Server 2008数据库分布式查询知识

    在接触公司一个系统时,公司使用的是SQL Server 2008数据库,里面涉及到了多个数据库之间的查询,而且数据库是分布式的,数据库分布在多台服务器之间,并且各个数据库各尽其责,负责存放不同模块功能的数据.这里面就要涉及到了数据库的分布式查询. 补充一下分布式查询的知识: 分布式查询从多异类数据源中访问数据.这些数据源可以存储在同一台计算机或不同的计算机上.Microsoft SQL Server 通过使用 OLE DB 来支持分布式查询. SQL Server 用户可以使用分布式查询访问以下

  • 使用SQL Server数据库嵌套子查询的方法

    很多SQL Server程序员对子查询(subqueries)的使用感到困惑,尤其对于嵌套子查询(即子查询中包含一个子查询).现在,就让我们追本溯源地探究这个问题.  有两种子查询类型:标准和相关.标准子查询执行一次,结果反馈给父查询.相关子查询每行执行一次,由父查询找回.在本文中,我将重点讨论嵌套子查询(nested subqueries)(我将在以后介绍相关子查询).  试想这个问题:你想生成一个卖平垫圈的销售人员列表.你需要的数据分散在四个表格中:人员.联系方式(Person.Contac

  • SQL Server数据库按百分比查询出表中的记录数

    SQL Server数据库查询时,能否按百分比查询出记录的条数呢?答案是肯定的.本文我们就介绍这一实现方法. 实现该功能的代码如下: create procedure pro_topPercent ( @ipercent [int] =0 --默认不返回 ) as begin select top (@ipercent ) percent * from books end 或 create procedure pro_topPercent ( @ipercent [int] =0 ) as be

随机推荐