SQL Server 对非聚簇索引的 INCLUDE 列数量和大小有限制

SQL Server 对非聚簇索引的 INCLUDE 列数量和大小有限制(最多 1023 列,但总大小 ≤ 900 字节键 + 8060 字节 INCLUDE)。

你的表有 大量 nvarchar(500)、decimal(25,12) 等大字段,无法将所有列放入 INCLUDE。

因此,完全覆盖所有字段不现实。我们应优先让索引快速定位行,然后通过 Key Lookup(回表) 获取其余字段。

Key Lookup(回表) ,这是 SQL Server 执行计划中一个非常关键的概念。我们来结合你的场景(app_contractlist_ext_three 表 + 非聚集索引 + SELECT *)深入解释:


🔍 什么是 Key Lookup(回表)?

  • 当你使用 非聚集索引(Nonclustered Index) 查询数据时:
    • 如果该索引 不包含查询所需的所有列,
    • SQL Server 会先通过非聚集索引 找到符合条件的行的"指针"(即聚簇索引键 或 RID),
    • 然后再 回到主表(堆表或聚簇索引)中查找完整行数据。
  • 这个"再回去查一次"的操作,就叫 Key Lookup (如果表有聚簇索引)或 RID Lookup(如果表是堆表)。

✅ 在你的表中,由于没有定义 PRIMARY KEY 或 CLUSTERED INDEX,默认是堆表(Heap) ,所以实际执行的是 RID Lookup,但大家习惯统称 "Key Lookup"。


📌 为什么你的查询一定会有 Key Lookup?

因为你写的是:

复制代码
SELECT * FROM app_contractlist_ext_three WHERE ...

而你的非聚集索引(比如 idx_app_contractlist_ext_three_dt_del_filter)只包含 6 个字段 ,但表总共有 50+ 个字段,包括大字段如:

  • product_title NVARCHAR(500)
  • contract_title NVARCHAR(100)
  • 多个 DECIMAL(25,12) 等

👉 非聚集索引无法包含所有列 (受 900 字节键限制 + 8060 字节 INCLUDE 限制,且大字段太多),所以 必须回表 才能返回完整数据。

✅ 这是正常且不可避免的 ,只要你是 SELECT *。


⚠️ Key Lookup 的性能影响

表格

情况 性能影响
返回行数少(< 100 行) 影响很小,可忽略
返回行数多(> 1万行) 每一行都要额外 I/O 回表,性能急剧下降
高并发查询 大量随机 I/O,可能成为瓶颈

💡 例如:索引找到 5 万行,就要做 5 万次 RID Lookup ------ 这比一次聚簇索引扫描还慢!


✅ 如何优化或避免(在无法避免 SELECT * 的前提下)?

1. 确保非聚集索引能快速过滤出最少的行
  • 你的索引设计(dt, del, ...)已经很好,能大幅减少需要回表的行数。
  • ✅ 这是最关键的优化点 :让索引尽可能精准定位,减少回表次数。
2. 考虑将表改为聚簇索引表(推荐)

目前你的表是堆表(无聚簇索引),回表用的是 物理行地址(RID),效率较低。

如果你给表加一个 聚簇索引,比如:

复制代码
-- 假设 id 是唯一且递增的
CREATE CLUSTERED INDEX CX_app_contractlist_ext_three_id ON app_contractlist_ext_three (id);

那么:

  • 非聚集索引会以 (id) 作为行指针;
  • 回表变成 聚簇索引查找(Clustered Index Seek),通常更高效(尤其数据按 id 顺序存储时);
  • 同时,很多分析查询也可直接按 id 范围扫描。

✅ 对于日志型、明细型大表,强烈建议有聚簇索引(通常选自增 ID 或 dt + id)。

3. 避免 SELECT(理想但常不现实)

如果前端/报表真的只需要部分字段,改成明确列名,并把它们放入 INCLUDE,就能 消除 Key Lookup。

例如:

复制代码
CREATE NONCLUSTERED INDEX IX_covering
ON app_contractlist_ext_three (dt, del, gate_ord)
INCLUDE (contract_title, product_title, shipment_num, PriceAfterTax);

→ 查询这些字段时,无需回表。

但你说"要返回所有字段",所以这条不适用。


🔎 如何查看是否发生了 Key Lookup?

执行查询时,点击 "显示实际执行计划"(Actual Execution Plan),你会看到:

  • 一个 "Index Seek" 或 "Index Scan"(你的非聚集索引)
  • 旁边连着一个 "Key Lookup (Clustered) " 或 "RID Lookup (Heap)" 操作符
  • 鼠标悬停可看到 Estimated Number of Rows 和 I/O Cost

如果 "Lookup" 的行数很大(比如几万+),就要警惕性能问题。


✅ 总结

表格

问题 回答
我的 SELECT * 查询会有 Key Lookup 吗? ✅ 一定会,因为非聚集索引不包含所有列
这是 bad 吗? ❌ 不一定 ------ 只要过滤后行数少,就完全 OK
如何减轻影响? 1. 让索引精准过滤 2. 考虑加聚簇索引 3. 避免大结果集查询
需要为消除 Key Lookup 而建覆盖索引吗? ❌ 不需要(因你要所有字段,不可能覆盖)

🛠️ 建议行动项

  1. 确认表是否有聚簇索引 :

    复制代码
    SELECT i.name, i.type_desc
    FROM sys.indexes i
    WHERE i.object_id = OBJECT_ID('dbo.app_contractlist_ext_three')
      AND i.type IN (1, 2); -- 1=clustered, 2=nonclustered
    • 如果没有 type=1 的索引 → 是堆表 → 建议加聚簇索引(如 id 或 (dt, id))
  2. 监控高频查询的执行计划 ,关注:

    • 是否用了你的新索引?
    • Key Lookup 的行数是否合理?

这样就能在"返回所有字段"的硬需求下,获得最佳性能。

相关推荐
KIDULT°10 小时前
MySQL系列 (1):数据库原理
运维·数据库·mysql·云计算
我不是程序员三三10 小时前
透明加密软件偶发解密异常排错实录:文件时能打开时乱码
数据库·域智盾
snhna10 小时前
向量数据库深度实战:从 HNSW 到 pgvector、Milvus 选型与 RAG 落地
数据库·人工智能·python·postgresql·milvus
天行健,君子而铎11 小时前
数据分类分级的范式转换:从规则匹配到场景化高准确率一键部署
网络·数据库·安全
牛战士coding11 小时前
MySQL 读写分离完整链路:主从复制、多数据源与 @DS 路由
数据库·mysql
goehou12 小时前
MCP 查数实战:让 LLM 安全地查数据库(只读账号、SQL 校验、参数漂移)
数据库·ai·llm·教程·mcp·function calling
我不会插花弄玉12 小时前
7.函数【由浅入深-MySQL】
android·数据库·mysql
周小董12 小时前
[1369]Windows上安装PostgreSQL
数据库·windows·postgresql
一只积极向上的小咸鱼14 小时前
第一性原理思考:从拆解问题到用实验检验答案
数据库
这个DBA有点耶15 小时前
时序数据库InfluxDB高基数问题深度拆解:内存索引膨胀机制与OOM完整链路
数据库·架构·dba