SQL Server 索引设计的核心思想:覆盖索引(Covering Index)

INCLUDE 中是否包含字段是否能提高查询性能
不包含任何字段有可能提升性能,但可能需要回表(Key Lookup),效率有限
只包含主键或 ID可能不如预期,具体取决于查询语句
包含所有 SELECT 字段最佳方案 —— 覆盖索引,避免 Key Lookup,性能最佳

什么是 INCLUDE 的作用?

在 SQL Server 中,CREATE NONCLUSTERED INDEX ... INCLUDE (...) 的作用是:

在索引中“附带”一些字段,这些字段不会作为排序依据(非键列),但可以直接从索引中获取数据,从而避免访问基表(Clustered Index 或 Heap)。

完全命中索引(Index Seek + Covering)
❌ 不需要再进行 Key Lookup(也叫 Bookmark Lookup) 

情况一:索引不包含任何字段(仅用于查找)
  • ✅ 可以快速定位到满足 del=0 AND city='北京' 的记录;
  • ❌ 但要返回 dtcompletemoney1 这些字段,必须回表查 Clustered Index(Key Lookup);
  • ⚠️ 如果结果集很大,Key Lookup 会显著影响性能。
情况二:索引只包含 ID(比如聚集索引键)
  • id 是聚集索引键,可用于回表;
  • ❌ 但仍然需要 Key Lookup 才能获取 dtcompletemoney1
  • ⚠️ 性能没有本质提升。
情况三:索引包含所有需要的字段(覆盖索引)
  • 查询所需的所有字段都来自这个索引;
  • ❌ 不需要 Key Lookup;
  • ✅ 查询速度更快,效率更高。

什么时候可以不用 INCLUDE 所有字段?
有时候你并不需要将所有字段都包含进索引,这取决于你的查询模式:

场景    是否建议 INCLUDE 所有字段
查询字段少、过滤条件多    ✅ 只需 INCLUDE 需要的字段
查询字段多、常变化    ❌ INCLUDE 太多字段,维护成本高
查询只取主键用于关联其他表    ✅ 只 INCLUDE 主键即可
查询频繁执行且性能瓶颈明显    ✅ 建立覆盖索引(包含所有字段)

如何判断是否应该 INCLUDE 某些字段?

你可以通过以下方式辅助判断:

✅ 使用缺失索引视图(Missing Indexes)

SELECT 
    migs.avg_total_user_cost * migs.avg_user_impact AS improvement_measure,
    mid.equality_columns,
    mid.inequality_columns,
    mid.included_columns
FROM 
    sys.dm_db_missing_index_groups mig
INNER JOIN 
    sys.dm_db_missing_index_group_stats migs ON migs.group_handle = mig.index_group_handle
INNER JOIN 
    sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle
WHERE 
    mid.database_id = DB_ID()
ORDER BY 
    improvement_measure DESC;

该视图会告诉你:

  • 哪些字段适合作为等值列(equality)
  • 哪些字段适合放在 INCLUDE
  • 提升幅度有多大

总结建议

情况是否推荐说明
INCLUDE 不包含字段❌ 一般不够高效需要 Key Lookup
INCLUDE 包含 ID(主键)⚠️ 有时有用可用于连接其他表,但仍需回表
INCLUDE 包含所有查询字段✅ 推荐使用覆盖索引,性能最佳
字段太多或经常变动❌ 避免 INCLUDE 所有字段维护成本高,得不偿失

实用技巧
优先将 WHERE 条件中的字段放在索引键列(ON (...));
将 SELECT 中需要返回的字段放在 INCLUDE;
对于大数据类型(如 VARCHAR(MAX)、TEXT),只能放在 INCLUDE;
针对高频慢查询,建立针对性的覆盖索引;
定期分析缺失索引和实际执行计划,优化索引结构。

Key Lookup 的工作原理

我们先了解几个基本概念:

名称含义
Clustered Index(聚集索引)表数据按此索引物理排序;一张表只能有一个
Nonclustered Index(非聚集索引)独立于表数据的索引结构,可有多个
Bookmark(书签)在非聚集索引中指向实际数据行的指针(即主键或RID)

✅ 示例说明
假设你有一个表 app_payback_ext,其主键是 id,并且你创建了一个非聚集索引如下:

Sql

CREATE NONCLUSTERED INDEX IX_app_payback_ext_del_city ON app_payback_ext(del, city)


此时你运行一个查询:

Sql

SELECT id, dt, complete, money1
FROM app_payback_ext
WHERE del = 0 AND city = '北京';


查询执行过程:
SQL Server 使用 IX_app_payback_ext_del_city 索引快速找到满足条件的数据行(通过 del=0 AND city='北京');
但这个索引中没有 dt, complete, money1 字段;
所以 SQL Server 使用索引中的 id(因为每个非聚集索引默认都会包含主键)回到 Clustered Index 去查找这些字段;

这个“回去找”的操作就是 Key Lookup。
📉 Key Lookup 对性能的影响
虽然 Key Lookup 是 SQL Server 自动完成的操作,但它会带来以下性能问题:

影响    说明
❌ 性能下降    每次 Key Lookup 都是一次额外的 I/O 操作
⚠️ 高频使用时显著影响    如果结果集很大,会导致大量随机读取
📈 图形执行计划显示为红色警告    表示可能是性能瓶颈
📊 在执行计划中表现为高成本    实际执行时间变长

🛠 如何避免 Key Lookup?
最有效的方法是使用 覆盖索引(Covering Index):

即:将查询中需要返回的所有字段都包含在非聚集索引中,这样就不再需要回表查找。

✅ 示例优化
原始查询:

Sql
 

SELECT id, dt, complete, money1
FROM app_payback_ext
WHERE del = 0 AND city = '北京';



原始索引(导致 Key Lookup):

Sql
 

CREATE NONCLUSTERED INDEX IX_app_payback_ext_del_city ON app_payback_ext(del, city)


优化后的索引(避免 Key Lookup):

Sql

CREATE NONCLUSTERED INDEX IX_app_payback_ext_del_city_covering 
ON app_payback_ext(del, city)
INCLUDE (id, dt, complete, money1)


现在这个索引包含了查询所需要的所有字段,SQL Server 就不会再进行 Key Lookup,而是直接从非聚集索引中获取所有数据。

🔍 如何识别 Key Lookup?
你可以通过以下方式查看执行计划是否出现了 Key Lookup:

方法一:图形执行计划(SSMS)
在 SSMS 中执行查询后,点击“包括实际执行计划”按钮(⚡图标);
查看执行步骤中是否有 Key Lookup 图标;
可以看到它在整个查询成本中的占比。
方法二:XML 执行计划
在 XML 执行计划中搜索关键词:

Xml

<Lookup>
  <Object ... />
</Lookup>



✅ 总结 Key Lookup 的含义和应对方法
内容    描述
🔹 Key Lookup 是什么?    当非聚集索引不包含查询所需字段时,SQL Server 回到聚集索引查找数据的过程
📉 是否会影响性能?    会,尤其是当返回行数多、Key Lookup 次数频繁时
✅ 如何避免?    创建 覆盖索引(Covering Index),将查询所需字段全部包含进索引
💡 覆盖索引建议    把 WHERE 条件字段放在索引键列,把 SELECT 字段放在 INCLUDE 中
🛠 推荐工具    使用缺失索引视图(sys.dm_db_missing_index_details)辅助发现推荐的索引结构
如果你提供具体的查询语句和索引结构,我可以帮你分析是否存在 Key Lookup,并给出优化建议。

=========================

查看表的索引信息

SELECT 
    i.name AS index_name,
    i.type_desc,
    i.is_unique,
    i.is_primary_key,
    COL_NAME(ic.object_id, ic.column_id) AS column_name,
    ic.key_ordinal
FROM sys.indexes i
JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
WHERE i.object_id = OBJECT_ID('dbo.app_contractlist_ext_three')
ORDER BY i.name, ic.key_ordinal;

 

 

<think>好的,我需要回答用户关于数据库有序表查询的问题。首先,我要确定用户的具体需求是什么。他们可能想了解如何在数据库中高效地查询有序数据,或者想了解有序表的结构和优化方法。接下来,我需要回忆数据库中有序表的相关知识,包括索引结构、查询优化以及不同数据库系统的实现差异。 首先,数据库中的有序表通常指的是数据按照某个或某些字段排序存储的表。有序表查询的关键在于如何利用这种预先排序的结构来加速查询操作,比如范围查询或排序操作。常见的实现方式包括使用B树索引、聚集索引等。 用户可能关心的是如何设计这样的表,以及查询时的最佳实践。例如,使用聚集索引可以让数据物理上按顺序存储,从而减少磁盘I/O,提高范围查询的效率。此外,覆盖索引的概念也很重要,当查询的列都包含在索引中时,可以避免回表操作,进一步提升性能。 同时,需要提到不同的数据库系统(如MySQL、PostgreSQL)在有序表实现上的差异。例如,MySQL的InnoDB引擎使用聚集索引,而PostgreSQL的堆表结构则需要通过索引来访问数据,这可能影响查询效率。 另外,用户可能想知道何时应该使用有序表,以及可能的缺点,比如插入和更新操作可能变慢,因为需要维护数据的物理顺序。这时候需要权衡读写比例,如果读多写少,有序表可能更合适;反之,如果写操作频繁,可能需要考虑其他优化策略。 最后,应该给出一些实际例子或使用场景,比如日志按时间戳排序、电商产品按价格排序等,帮助用户更好地理解如何应用有序表查询优化。总结时,要强调合理设计索引、利用数据库的有序结构以及根据具体场景选择合适策略的重要性。</think>在数据库中,**有序表查询**是通过利用数据预先排序的特性来提升查询效率的核心技术。以下是逐步解析与实现要点: --- ### **一、有序表的核心作用** 有序表(如索引有序或物理有序存储)可通过**减少数据扫描范围**和**避免全表排序**优化以下操作: - **范围查询**(`BETWEEN`、`>`、`<` 等) - **排序操作**(`ORDER BY`) - **分组聚合**(`GROUP BY`) - **去重**(`DISTINCT`) --- ### **二、有序表的实现方式** #### **1. 聚集索引(Clustered Index)** - **定义**:数据物理存储顺序与索引键值顺序一致(如MySQL的InnoDB主键索引)。 - **优势**: - 范围查询时,连续磁盘读取减少I/O。 - `ORDER BY` 索引列时无需额外排序。 - **示例**: ```sql -- 创建聚集索引(通常为主键) CREATE TABLE orders ( order_id INT PRIMARY KEY, -- 聚集索引按order_id排序存储 user_id INT, amount DECIMAL ); ``` #### **2. 非聚集索引(Non-Clustered Index)** - **定义**:索引单独存储,数据行通过指针引用(如MySQL的二级索引)。 - **适用场景**:查询条件或排序字段为非聚集索引键。 - **优化技巧**: - **覆盖索引Covering Index)**:索引包含查询所需所有字段,避免回表。 ```sql CREATE INDEX idx_user_amount ON orders(user_id, amount); -- 查询命中覆盖索引 SELECT user_id, amount FROM orders WHERE user_id = 100 ORDER BY amount; ``` --- ### **三、有序表查询优化策略** #### **1. 范围查询加速** - **场景**:查找某时间段内的订单。 - **优化**: ```sql -- 按时间字段建立聚集索引 CREATE CLUSTERED INDEX idx_time ON logs(log_time); -- 范围查询直接定位数据块 SELECT * FROM logs WHERE log_time BETWEEN '2023-01-01' AND '2023-01-31'; ``` #### **2. 排序免计算** - **场景**:分页查询按价格排序的商品。 - **优化**: ```sql -- 对price字段建立索引 CREATE INDEX idx_price ON products(price); -- 直接利用索引顺序,无需临时排序 SELECT * FROM products ORDER BY price LIMIT 20 OFFSET 40; -- 分页查询 ``` #### **3. 索引合并(Index Merge)** - **场景**:多条件查询,单个索引无法覆盖。 - **示例**: ```sql -- 对age和city分别建索引 CREATE INDEX idx_age ON users(age); CREATE INDEX idx_city ON users(city); -- 数据库可能合并索引结果 SELECT * FROM users WHERE age > 25 AND city = 'Beijing'; ``` --- ### **四、不同数据库的实现差异** | **数据库** | **有序表实现** | **特点** | |--------------|----------------------------------------|------------------------------------------| | **MySQL** | InnoDB默认主键为聚集索引 | 主键查询极快,二级索引需回表 | | **PostgreSQL** | 堆表结构,索引均为非聚集 | 通过Index-Only Scan实现覆盖索引优化 | | **SQL Server** | 可指定非主键字段为聚集索引 | 更灵活,但聚集索引重建代价高 | --- ### **五、注意事项** 1. **维护成本**: - 有序表插入/更新时需维护排序,可能影响写入性能。 - 频繁更新的字段不适合作为聚集索引键。 2. **选择合适索引键**: - 高基数(唯一性高)字段优先(如时间戳、ID)。 - 避免过长字段(如TEXT类型)作为索引键。 --- ### **六、实战案例** **场景**:电商订单表按时间范围统计销售额 **优化步骤**: 1. 创建聚集索引: ```sql ALTER TABLE orders ADD PRIMARY KEY (order_time, order_id); ``` 2. 执行统计查询: ```sql SELECT SUM(amount) FROM orders WHERE order_time BETWEEN '2023-07-01' AND '2023-07-31'; ``` **效果**:直接按物理顺序扫描目标时间段数据,避免全表扫描。 --- ### **总结** - **核心思想**:通过预排序减少运行时计算和I/O开销。 - **关键策略**:合理设计聚集索引、利用覆盖索引、理解数据库特性。 - **权衡点**:在查询加速与写入性能之间找到平衡。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值