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

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

相关推荐
Coder_Boy_2 小时前
基于SpringAI的在线考试系统-核心业务流程图
java·数据库·spring boot·软件工程
松涛和鸣2 小时前
DAY49 DS18B20 Single-Wire Digital Temperature Acquisition
linux·服务器·网络·数据库·html
海边的Kurisu2 小时前
苍穹外卖日记 | Day3 公共字段填充、菜品模块
数据库
摆烂z2 小时前
mysql通过binlog恢复数据
数据库·mysql
老邓计算机毕设3 小时前
SSM学期分析与学习行为分析系统c8322(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·学习·ssm 框架·学期分析·学习行为分析
学Linux的语莫3 小时前
python创建redis连接池
数据库·redis·缓存
运维有小邓@3 小时前
Log360 的可扩展架构(三):数据流管道
数据库·架构
醇氧3 小时前
【Windows】安装mysql8
数据库·windows·mysql
温暖小土3 小时前
ClickHouse vs Apache Doris:2026年实时OLAP数据库选型深度解析
数据库·数据仓库·clickhouse·apache