111、sqlserver 表A有1亿条数据,表中每个不同值的字段B大约有100条数据,给B字段建索引和不建索引查询时性能相差多少倍?并进行分析
1、直接结论
在该场景下,建立索引后的查询性能可能会比不建索引快上 几十万甚至上百万倍。查询时间可能从小时级或分钟级,缩短到毫秒级。
2、性能差异的核心原因
同样,我们可以用电话簿的比喻来理解:
- 不建索引(全表扫描 - Table Scan):SQL Server 需要从头到尾扫描表中的每一条记录(1 亿条),逐一比较字段B的值是否符合查询条件。这是一个 I/O 密集型操作,速度极慢。
- 建立索引(索引查找 - Index Seek):SQL Server 会创建一个索引结构(默认是 B + 树),这个结构就像电话簿的目录。数据库可以通过这个 “目录” 直接定位到所有B字段值符合条件的数据行,然后再去数据表中读取这些行的详细信息(如果索引不是覆盖索引的话)。
3、性能差异的量化分析
我们做一个同样粗略的量化估算:
假设:
- 表 A 有 1 亿条 记录。
- 每条记录大小为 1KB。
- 存储设备是 SATA III SSD,连续读取速度约为 500MB/s。
- B + 树索引的高度通常在 3-4 层。
(1)不建索引(全表扫描)
- 数据量:1 亿条 * 1KB / 条 = 100GB
- 查询时间(理论最小值):100GB / 500MB/s = 200 秒 (约 3.3 分钟)
- 注意:这是理想情况下的最快速度。实际中,数据可能不连续存储,涉及磁盘寻道、SQL Server 的各种开销等,实际时间会远大于200 秒。
(2)建立索引(非聚集索引)
当你在 B 字段上建立非聚集索引后,查询时:
-
SQL Server 在索引中快速定位到 B 值对应的位置(O(log n) 复杂度,非常快)。
-
从索引中获取到这些记录的聚集索引键(如果表有聚集索引)或堆指针(如果是堆表)。
-
根据这些键或指针,去聚集索引或堆中书签查找(Bookmark Lookup)对应的完整数据行。
-
索引查找时间:微秒(μs)级别,可忽略。
-
书签查找时间:假设找到 100 条记录,每条记录的查找是一次随机 I/O。SSD 的随机 I/O 性能远好于 HDD,但 100 次随机 I/O 仍然需要一点时间。我们保守估计为 1-10 毫秒(ms)。
-
总查询时间:约 1-10 毫秒。
(2) 性能差异倍数
- 差异倍数 ≈ 200 秒 / 0.001 秒 = 200,000 倍 (基于 1ms 估算)
- 差异倍数 ≈ 200 秒 / 0.01 秒 = 20,000 倍 (基于 10ms 估算)
即使是最保守的估计,性能差异也在数万倍以上。
4、关于该场景的特别说明
该场景中 “每个不同值的字段 B 大约有 100 条数据”,这表示字段B的选择性(Selectivity) 非常好。
- 选择性 = 不重复的索引值数量 / 总记录数
- 在场景里,选择性 = (1 亿 / 100) / 1 亿 = 1/100 = 1%。
选择性越高(越接近 100%),索引的效率就越高。1% 的选择性意味着通过索引筛选后,需要访问的数据量(100 条)远小于总数据量(1 亿条),因此索引能极大地减少 I/O 操作,性能提升非常显著。
5、SQL Server 的一些额外考量
(1) 聚集索引 vs. 非聚集索引:
-
如果字段B是表的聚集索引键,那么查询性能会更好,因为数据本身就是按照B的顺序存储的,找到索引位置就找到了数据,不需要书签查找。
-
如果B上建立的是非聚集索引,那么查询通常会经历 “索引查找 + 书签查找” 的过程。
(2) 覆盖索引(Covering Index):
- 如果你的查询只需要返回B字段和主键字段,那么非聚集索引本身就包含了这些信息,SQL Server
可以直接从索引中获取数据,无需访问基表,这就是索引覆盖,性能会极高。 - 如果你还需要查询其他字段,可以考虑将这些字段加入到非聚集索引的 包含列(Included Columns) 中,以实现索引覆盖,避免书签查找的开销。
- 示例:
CREATE NONCLUSTERED INDEX IX_A_B ON A(B) INCLUDE (Column1, Column2);
(3) 索引维护:
- 对于 1 亿条数据的大表,索引的建立过程会比较耗时,并且会产生锁和日志。建议在业务低峰期操作。
- 大表的索引也需要定期维护(如重建或重组),以保持其性能。
总结与建议
1、巨大的性能差异
在 1 亿条数据的表上,对查询频繁、选择性良好(1%)的字段B建立索引,查询性能可能会提升几十万倍甚至更多。
2、索引是必须的
对于这种场景,建立索引是提升查询性能的关键,几乎是必须的优化手段。
3、索引的代价
索引会占用额外的存储空间,并且会轻微降低INSERT, UPDATE, DELETE的性能。但在你的查询场景下,查询性能的收益远大于这些代价。
4、选择合适的索引类型
- 如果B字段适合作为聚集索引键(例如,经常用于范围查询、排序等),可以考虑将其设为聚集索引。
- 否则,建立非聚集索引即可。如果查询需要其他字段,可以考虑使用包含列来创建覆盖索引,以获得最佳性能。

