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 KEYCLUSTERED 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 RowsI/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 的行数是否合理?

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

相关推荐
爱学习的阿磊2 小时前
使用Fabric自动化你的部署流程
jvm·数据库·python
枷锁—sha2 小时前
【SRC】SQL注入快速判定与应对策略(一)
网络·数据库·sql·安全·网络安全·系统安全
惜分飞2 小时前
ORA-600 kcratr_nab_less_than_odr和ORA-600 4193故障处理--惜分飞
数据库·oracle
chian-ocean2 小时前
CANN 生态进阶:利用 `profiling-tools` 优化模型性能
数据库·mysql
m0_550024632 小时前
持续集成/持续部署(CI/CD) for Python
jvm·数据库·python
AC赳赳老秦3 小时前
代码生成超越 GPT-4:DeepSeek-V4 编程任务实战与 2026 开发者效率提升指南
数据库·数据仓库·人工智能·科技·rabbitmq·memcache·deepseek
啦啦啦_99993 小时前
Redis-2-queryFormat()方法
数据库·redis·缓存
玄同7654 小时前
SQLite + LLM:大模型应用落地的轻量级数据存储方案
jvm·数据库·人工智能·python·语言模型·sqlite·知识图谱
吾日三省吾码4 小时前
别只会“加索引”了!这 3 个 PostgreSQL 反常识优化,能把性能和成本一起打下来
数据库·postgresql
chian-ocean4 小时前
百万级图文检索实战:`ops-transformer` + 向量数据库构建语义搜索引擎
数据库·搜索引擎·transformer