索引举例介绍

本文探讨了查询优化器如何选择最佳的访问模式以提高查询效率,包括考虑I/O数量、索引键选择性等因素,并通过实例展示了不同场景下索引选择的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description:
 查询优化器在从表中查询数据时,需要选择一个合适的访问模式,在决定使用哪一种索引,使用扫描还是查找,使用书签查询时,查询优化器要考虑许多因素,这些因素包括:
  • 索引执行时,查找或扫描所需的I/O数
  • 评估查询中的索引键是否是最佳
  • 谓词的选择性(也就是说,相对于表中总记录数满足谓词的百分比)
  • 索引是否覆盖所有列?

下面通过一个例子来介绍:

create table T (a int, b int, c int, d int, x char(200))

create unique clustered index Ta on T(a)

create index Tb on T(b)

create index Tcd on T(c, d)

create index Tdc on T(d, c)

插入一些数据:

set nocount on

declare @i int

set @i = 0

while @i < 100000

begin

insert T values (@i, @i, @i, @i, @i)

set @i = @i + 1

end

无Where条件

Select a,b FROM T,

该 查询不包含Where条件语句,而使用扫描,可是这里有两种索引可用:聚集索引(Ta)和非聚集索引(Tb),这两个索引均覆盖a和b两列,另外,聚集索 引也覆盖c和x列.由于x列是字符型,长度为200个字符,聚集索引的每一行总宽度超过了200个字节,对于每一个8KB的页面,存储的行数也不超过40 行.而索引需要2500个页来存储所有10万行数据,与之相反的是,非聚集索引中每一行的总宽仅有8个字节,加一些头部信息,每一页可以存储上百行数据, 索引则需要不到250页来存储所有的10万行数据.通过扫描非聚集索引,当执行查询时则需要较少的I/O操作.因而使用的最佳计划是:

|--Index Scan(OBJECT:([T].[Tb]))

我们也可以使用sys.dm_db_index_physical_stats视图来比较聚集索引与非聚集索引两者所使用的页数

select index_id, page_count

from sys.dm_db_index_physical_stats

(DB_ID('northwind'), OBJECT_ID('T'), NULL, NULL, NULL)

执行上述查询后,结果如下:

索引ID号页数
12858
2174
3223
4223

从输出结果可以看出,非聚集索引存储行所使用的页数明显小于聚集索引使用的页数.

当然我们也可以使用stats I/O和索引hints来比较聚集索引与非聚集索引的I/O数.

set statistics io on

select a, b from T with (index(Ta))

表'T'。扫描计数1,逻辑读取2872 次,物理读取0 次,预读0 次,lob 逻辑读取0 次,lob 物理读取0 次,lob 预读0 次。

select a, b from T with (index(Tb))

表'T'。扫描计数1,逻辑读取176 次,物理读取0 次,预读0 次,lob 逻辑读取0 次,lob 物理读取0 次,lob 预读0 次。

从stats I/O数可以看出,非聚集索引在获取数据时,读取较少的数据页.

索引的选择性

select a from T

where c > 150 and c < 160 and d > 100 and d < 200

此查询有两个不同的谓词用于索引查找,可以使用位于c列上的非聚集索引Tcd,也可以使用位于d列上的非聚集索引Tdc.

查询优化器通过查看两个谓词的选择性来确定使用哪一个索引,在c列上的谓词选择的行仅有9行,而在d列上则有99行,显然使用索引Tcd来评估位于d列上的residual谓词比使用Tdc索引的I/O开销要小得多.

以下是该查询的计划:

|--Index Seek(OBJECT:([T].[Tcd]), SEEK:([T].[c] > (150) AND [T].[c] < (160)),

Where:([T].[d]>(100) AND [T].[d]<(200)) orDERED FORWARD)

索引查找与索引扫描示例

select a from T where a between 1001 and 9000

select a from T where a between 101 and 90000

其执I/O信息如下:

表'T'。扫描计数1,逻辑读取234 次,物理读取0 次,预读0 次,lob 逻辑读取0 次,lob 物理读取0 次,lob 预读0 次。

表'T'。扫描计数1,逻辑读取176 次,物理读取0 次,预读0 次,lob 逻辑读取0 次,lob 物理读取0 次,lob 预读0 次。

如您所预料的,对于第一个查询来说,查询优化器在a列上选择使用聚集索引来获取数据,以下是其的查询计划:

|--Clustered Index Seek(OBJECT:([T].[Ta]),

SEEK:([T].[a] >= CONVERT_IMPLICIT(int,[@1],0) AND [T].[a] <= CONVERT_IMPLICIT(int,[@2],0)) orDERED FORWARD)

注意:该计划中的两个参数是由自动参数化功能所生成的,当执行该计划时,@1参数为1001,@2参数为9000.

对于第二个查询来说,查询优化器却选择了非聚集索引来扫描数据,以下其查询计划:

|--Index Scan(OBJECT:([T].[Tb]), Where:([T].[a]>=(101) AND [T].[a]<=(90000)))

为 什么是这样呢?注意第一个查询选择的记录数有8千行(相对于10万行数据而言),对于聚集索引来说,选择度为表的8%,约230个数据页,而第二查询选择 的记录数有89000行,选择度为表的约90%,若使用聚集索引来读取89000行数据时,则需要读2500个数据页.通过比较,非聚集索引仅需要读取 174个页面,查询优化器选择此计划,大大减少了I/O操作.

带书签查询的查询与扫描示例

select x from T where b between 101 and 200

select x from T where b between 1001 and 2000

对 于上述的两个查询而言,可以通过聚集索引直接扫描然后在列b上应用谓词,或者使用非聚集索引Tb在列b上执行索引查找,然后在聚集索引上执行书签查询来读 取满足x列值的行.(注意:书签查询采用的I/O开销比较大的方式是随机读.)对于查找的选择度高的书签查询,则是值得的.

以下是第一个包含书签查询的查询计划(仅需要读取100行):

|--Nested Loops(Inner Join, OUTER REFERENCES:([T].[a], [Expr1005]) ...)

|--Index Seek(OBJECT:([T].[Tb]), SEEK:([T].[b] >= (101) AND [T].[b] <= (200)) ...)

|--Clustered Index Seek(OBJECT:([T].[Ta]), SEEK:([T].[a]=[T].[a]) LOOKUP ...)

  而第二个查询则读取1000行,对于表而言,仅有1%.查询优化器由此推出,执行1000次的随机读要比执行2800次的顺序读的开销要大得多,第二个查询的计划如下:

|--Clustered Index Scan(OBJECT:([T].[Ta]), Where:([T].[b]>=(1001) AND [T].[b]<=(2000)))

源文出处:http://www.haixiait.com/article.asp?id=162

### MongoDB 索引 示例 用法 #### 单字段索引 单字段索引是最基本的索引类型,用于加速基于单一字段的查询。以下是创建单字段索引的示例: ```javascript db.collection.createIndex({ fieldName: 1 }); // 升序索引 db.collection.createIndex({ fieldName: -1 }); // 降序索引 ``` 此方法适用于简单的查询场景,能够显著提升查询效率[^2]。 #### 复合索引 复合索引涉及多个字段,适合于复杂的查询条件。以下是一个型的复合索引创建示例: ```javascript db.collection.createIndex({ field1: 1, field2: -1 }); ``` 这种类型的索引特别适用于需要按多个字段排序或过滤的情况[^2]。 #### 部分索引 部分索引仅针对满足特定条件的文档建立索引,从而减少存储开销并提高性能。下面展示了如何创建一个部分索引: ```javascript db.user.createIndex( { salary: 1 }, { partialFilterExpression: { salary: { $gt: 10000 } } } ); ``` 该语句会为 `salary` 字段大于 10000 的记录创建索引[^3]。 #### 唯一索引 唯一索引确保集合中的某个字段不会出现重复值。可以通过设置选项 `{ unique: true }` 来实现这一功能: ```javascript db.collection.createIndex({ fieldName: 1 }, { unique: true }); ``` 这有助于防止数据冗余和保持数据一致性。 #### 文本索引 对于全文搜索的应用场景,可以使用文本索引。需要注意的是,MongoDB 支持多种语言的文本索引配置: ```javascript db.articles.createIndex({ content: "text" }); // 查询包含关键词的文章 db.articles.find({ $text: { $search: "keyword" } }); ``` 文本索引允许执行模糊匹配以及更灵活的内容检索操作[^2]。 #### 删除索引 如果不再需要某些索引或者希望重新构建它们,则可通过如下命令删除现有索引: ```javascript db.collection.dropIndexes(); // 删除所有非 _id_ 的索引 db.collection.dropIndex("indexName"); // 删除指定名称的索引 ``` 注意,在删除之前应确认当前业务逻辑不受影响[^4]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值