优化索引维护以提高查询性能并减少资源消耗

适用于:SQL ServerAzure SQL 数据库Azure SQL 托管实例Analytics Platform System (PDW)Microsoft Fabric 中的 SQL 数据库

本文将帮助你决定何时以及如何执行索引维护。 其中介绍了索引碎片和页面密度等概念,以及它们对查询性能和资源消耗的影响。 它介绍了两种索引维护方法: 重新组织索引重新生成索引。 本文还建议使用索引维护 策略 ,根据维护所需的资源消耗来平衡潜在的性能改进。

注意

本文不适用于 Azure Synapse Analytics 中的专用 SQL 池。 有关 Azure Synapse Analytics 中专用 SQL 池的索引维护的信息,请参阅为 Azure Synapse Analytics 中的专用 SQL 池表编制索引

概念:索引碎片和页面密度

什么是索引碎片以及它对性能有怎样的影响:

  • 在 B 树(行存储)索引中,当索引页的逻辑排序(基于索引的键值)与物理排序不匹配时,就会出现碎片。

    注意

    文档在提到索引时一般使用 B 树这个术语。 在行存储索引中,数据库引擎实现 B+ 树。 这不适用于列存储索引或内存优化表上的索引。 有关详细信息,请参阅 SQL Server 以及 Azure SQL 索引体系结构和设计指南

  • 无论何时对基础数据执行插入、更新或删除操作,数据库引擎都会自动修改索引。 例如,在表中添加行可能会导致拆分行存储索引中的现有页,以腾出空间来插入新行。 随着时间的推移,这些修改可能会导致索引中的数据分散在数据库中(碎片化)。

  • 对于使用完全或范围索引扫描读取多个页面的查询,当需要额外的 I/O 来读取数据时,高度碎片化的索引会降低查询性能。 相比几个大型 I/O 请求,查询将需要许多小型 I/O 请求来读取相同的数据量。

  • 当存储子系统提供比随机 I/O 性能更好的顺序 I/O 性能时,索引碎片会降低性能,因为读取碎片索引需要更多的随机 I/O。

什么是页面密度(也称为页面填充度)及其对性能的影响:

  • 数据库中的每个页面都可以包含可变数量的行。 如果行占用了页面上的所有空间,则页面密度为 100%。 如果页面为空,则页面密度为 0%。 如果一个 100% 密度的页面被分成两个页面来容纳一个新行,那么这两个新页面的密度大约是 50%。
  • 当页面密度较低时,需要更多的页面来存储相同数量的数据。 这意味着,需要更多的 I/O 来读取和写入这些数据,并且需要更多的内存来缓存这些数据。 内存有限时,所缓存的查询所需页面较少,从而导致磁盘 I/O 增加。 因此,低页面密度会对性能产生负面影响。
  • 当数据库引擎在索引创建、重新生成或重组期间向页面添加行时,如果索引的 填充因子 设置为 100 以外的值(或 0,在此上下文中等效),则不会完全填充页面。 这会导致页面密度降低,同样会增加 I/O 开销,并对性能产生负面影响。
  • 低页面密度会增加中间 B 树级别的数量。 这会适度增加在索引扫描和索引查找中找到查找叶级别页面的 CPU 和 I/O 开销。
  • 当查询优化器编译查询计划时,它会考虑读取查询所需数据所需的 I/O 开销。 页面密度较低时,需要读取的页面数量更多,因此 I/O 开销更高。 这可能会影响对查询计划的选择。 例如,随着页面拆分导致页面密度随时间降低,优化器可以为同一查询编译不同的计划,具有不同的性能和资源消耗情况。

提示

在许多工作负荷中,提高页面密度比减少碎片化对性能的积极影响更大。

为了避免不必要的降低页面密度,Microsoft不建议将填充因子设置为除 100 或 0 以外的值,但在某些情况下,索引出现大量 分页的情况除外。 例如,这可能发生在前导列包含非顺序 GUID 值且经常被修改的索引中。

度量索引碎片和页面密度

在决定是否执行索引维护以及使用哪种维护方法时,碎片和页面密度都是需要考虑的因素。

对于行存储列存储索引的碎片定义有所不同。 对于行存储索引, sys.dm_db_index_physical_stats() 可用于确定特定索引或多个索引中的碎片和页面密度。 对于已分区索引,sys.dm_db_index_physical_stats() 为每个分区提供此信息。

sys.dm_db_index_physical_stats 返回的结果集包含以下列:

说明
avg_fragmentation_in_percent 逻辑碎片(索引中的无序页面)。
avg_page_space_used_in_percent 平均页面密度。

对于列存储索引中的压缩行组,将碎片定义为已删除的行数与总行数之比,并以百分比形式表示。 可以使用 sys.dm_db_column_store_row_group_physical_stats 确定特定索引、表上的所有索引或数据库中所有索引的每个行组的总行数和已删除行数。

sys.dm_db_column_store_row_group_physical_stats 返回的结果集包含以下列:

说明
total_rows 以物理方式存储在行组中的行数。 对于压缩行组,这包括标记为已删除的行。
deleted_rows 以物理方式存储在压缩行组中且标记为要删除的行数。 对于增量存储中的行组,值为 0。

可使用以下公式计算列存储索引中压缩行组的碎片:

100.0 * (ISNULL(total_stored_deleted_rows, 0)) / NULLIF(total_rows, 0)

若要确定非聚集列存储索引的物理存储已删除行的总数,请将 sys.dm_db_column_store_row_group_physical_stats 中的 deleted_rows 列中的值与 sys.internal_partitionsrows 列中内部对象类型 COLUMN_STORE_DELETE_BUFFER 和相同对象、索引和分区的值相加。 有关示例,请参阅检查列存储索引的碎片

提示

对于行存储索引和列存储索引,请在删除或更新大量行后查看索引或堆碎片和页面密度。 对于堆,如果频繁进行更新,请定期检查碎片以避免前推记录激增。 有关堆的详细信息,请参阅堆(没有聚集索引的表)

若要获取用于确定碎片和页面密度的查询示例,请参阅示例

索引维护方法:重新组织和重新生成

可以通过使用以下方法之一来减少索引碎片并提高页面密度:

  • 重新组织索引
  • 重新生成索引

注意

对于已分区索引,可以在索引的所有分区或单个分区上使用以下两种方法之一。

重新组织索引

与重新生成索引相比,重新组织索引所需的资源较少。 因此,除非有特定原因需要使用索引重新生成,否则它应该是首选的索引维护方法。 重新组织始终是一项联机操作。 这意味着不用持有长期对象级锁,并且基础表的查询或更新可以在ALTER INDEX ... REORGANIZE操作期间继续。

  • 对于行存储索引,数据库引擎仅对表和视图中聚集索引和非聚集索引的叶级进行碎片整理。 它以物理方式对叶级页面进行重新排序,以匹配叶节点的逻辑顺序(从左到右)。 重新组织还会压缩索引页,使页面密度等于索引的填充因子。 若要查看填充因子设置,请使用 sys.indexes。 有关语法示例,请参阅示例 - 行存储重新组织
  • 如果使用列存储索引,则在一段时间内插入、更新和删除数据后,增量存储可能最终会有多个小行组。 重新组织列存储索引会强制增量存储行组移到列存储中的压缩行组,并将较小的压缩行组合并为较大的行组。 重新组织操作还会以物理方式删除在列存储中标记为已删除的行。 重新组织列存储索引可能需要额外的 CPU 资源来压缩数据。 当操作正在运行时,性能可能会变慢。 但在压缩数据后,查询性能就会提高。 有关语法示例,请参阅示例 - 列存储重新组织

注意

从 SQL Server 2019 (15.x)、Azure SQL 数据库、Azure SQL 托管实例开始,元组移动器通过后台合并任务获得帮助,该任务会自动压缩较小的已存在一段时间(由内部阈值确定)的开放增量行组,或者合并已从中删除大量行的压缩行组。 随着时间的推移,这会提高列存储索引的质量。 在大多数情况下,这消除了发出 ALTER INDEX ... REORGANIZE 命令的需要。

提示

如果取消了重新组织操作,或者以其他方式中断了该操作,则在此之前所取得的进展将保留到数据库中。 若要重新组织大型索引,可以多次启动和停止操作,直到完成。

重新生成索引

重新生成索引将会删除并重新创建索引。 重新生成操作可以脱机或联机执行,具体取决于索引类型和数据库引擎版本。 脱机索引重新生成耗费的时间通常比联机重新生成少,但它会在重新生成操作持续期间保留对象级锁,阻止查询访问表或视图。

联机索引重新生成操作结束时才需要对象级锁,届时必须将锁短暂保留一段时间,重新生成才能完成。 根据数据库引擎的版本,可以将联机索引重新生成作为可恢复操作启动。 可恢复的索引重新生成操作可以暂停,并保留截至到该时刻完成的进度。 在可恢复的重建操作暂停、中断后可以继续进行,或在不再需要完成重建时中止。

有关 Transact-SQL 语法,请参阅 ALTER INDEX REBUILD。 有关联机索引重新生成操作的详细信息,请参阅联机执行索引操作

注意

在联机重新生成索引时,对索引列中数据的每次修改都必须更新索引的额外副本。 这可能会导致数据修改语句在联机重新生成期间出现较小性能降低。

可恢复的联机索引操作暂停时,这会继续影响此性能,直到此可恢复的操作完成或中止。 如果您不打算完成可恢复索引操作,请终止操作,而不是暂停操作。

提示

根据可用资源和工作负荷模式,在 ALTER INDEX REBUILD 语句中指定高于默认 MAXDOP 值的值可能会缩短重新生成操作的持续时间,但会提高 CPU 使用率。

  • 对于行存储索引,重新生成会删除所有级别的碎片,并根据指定的或当前的填充因子来压缩页面。 如果指定 ALL,将删除表中的所有索引,然后在单个操作中重新生成。 重新生成具有 128 或更多个区的索引时,数据库引擎会推迟页面取消分配,并获取相关联的锁,直到重新生成完成。 有关语法示例,请参阅示例 - 行存储重新生成

  • 对于列存储索引,重新生成会消除碎片,将任何增量存储行移到列存储中,并以物理方式删除标记为已删除的行。 有关语法示例,请参阅示例 - 列存储重新生成

    提示

    自 SQL Server 2016 (13.x) 起,通常不需要重新生成列存储索引,因为 REORGANIZE 以联机操作形式执行重新生成的基本操作。

使用索引重新生成从数据损坏中恢复

在 SQL Server 2008 (10.0.x) 之前,有时可以重新生成行存储非聚集索引,以更正索引数据损坏导致的不一致问题。

仍可以通过脱机重新生成非聚集索引,修复非聚集索引中的此类不一致问题。 但是,无法通过联机重新生成索引来修复非聚集索引不一致,因为联机重新生成机制使用现有的非聚集索引作为重新生成的基础,从而延续不一致。 脱机重新生成索引有时可以强制扫描聚集索引(或堆),进而将非聚集索引中的不一致数据替换为聚集索引或堆中的数据。

若要确保将聚集索引或堆用作数据源,请删除并重新创建非聚集索引,而不是重新生成它。 与早期版本一样,可以通过从备份还原受影响的数据来从不一致中恢复。 但是,可以通过脱机重新生成索引或重新创建索引来修复非聚集索引不一致问题。 有关详细信息,请参阅 DBCC CHECKDB (Transact-SQL)

自动索引和统计信息管理

使用自适应索引碎片整理等解决方案,自动管理一个或多个数据库的索引碎片和统计信息更新。 此过程根据碎片级别以及其他参数,自动选择是重新生成索引还是重新组织索引,并使用线性阈值更新统计信息。

有关重新生成和重新组织行存储索引的注意事项

以下方案会导致表上的所有行存储非聚集索引自动重新生成:

  • 在表上创建聚集索引,包括通过 CREATE CLUSTERED INDEX ... WITH (DROP_EXISTING = ON) 使用不同的键重新创建聚集索引
  • 删除聚集索引,从而使表存储为堆

以下方案不会在同一表中自动重新生成所有行存储非聚集索引:

  • 重新生成聚集索引
  • 更改聚集索引存储,例如应用分区方案或将聚集索引移动到其他文件组

重要说明

如果索引所在的文件组处于脱机或只读状态,则无法重新组织或重新生成索引。 如果指定了关键字 ALL,但有一个或多个索引位于脱机文件组或只读文件组中,该语句将失败。

在进行索引重新生成时,物理介质必须有足够的空间来存储索引的两个副本。 在重新生成完成后,数据库引擎会删除原始索引。

当用 ALTER INDEX ... REORGANIZE 语句指定 ALL 时,表上的聚集索引、非聚集索引和 XML 索引将被重新组织。

重新生成或重新组织小型行存储索引通常不会减少碎片。 直至 SQL Server 2014 (12.x) 版本(包含该版本),SQL Server 数据库引擎都使用混合盘区分配空间。 因此,小型索引的页面有时存储在混合区上,这会让这类索引隐式产生碎片。 混合区最多可由八个对象共享,因此小型索引中的碎片在重新组织或重新生成索引后可能不会减少。

有关重新生成列存储索引的注意事项

重新生成列存储索引时,数据库引擎会从原始列存储索引(包括增量存储)中读取所有数据。 它将数据合并到新行组中,并将所有行组压缩到列存储中。 数据库引擎通过物理删除被标记为已删除的行来对列存储进行碎片整理。

注意

从 SQL Server 2019 (15.x) 开始,元组移动器通过后台合并任务获得帮助,该任务会自动压缩较小的已存在一段时间(由内部阈值确定)的开放增量存储行组,或者合并已从中删除大量行的压缩行组。 随着时间的推移,这会提高列存储索引的质量。 有关列存储术语和概念的详细信息,请参阅列存储索引:概述

重新生成分区,而不是整个表

如果索引较大,则重新生成整个表需要很长时间,并且需要足够的磁盘空间来在重新生成期间存储整个索引的副本。

对于分区表来说,当碎片仅出现在某些特定分区中,例如那些由UPDATEDELETEMERGE语句修改了大量行的分区,则无需重新构建整个列存储索引。

在加载或修改数据后重新生成分区,这样可确保所有数据都存储在列存储中的压缩行组中。 当数据加载进程使用行数少于 102,400 的批将数据插入到分区时,该分区的增量存储中最终可能会有多个开放的行组。 重新生成会将所有增量存储行移到列存储中的压缩行组。

有关重新组织列存储索引的注意事项

重新组织列存储索引时,数据库引擎会将增量存储中的每个关闭的行组作为压缩行组压缩到列存储中。 自 SQL Server 2016 (13.x) 起,在 Azure SQL 数据库中,REORGANIZE 命令会联机执行以下额外的碎片整理优化:

  • 当一个行组中被逻辑删除的行数达到或超过10%时,物理删除行。 例如,当包含 100 万行的压缩行组删除了 10 万行时,数据库引擎会移除已删除的行,并重新压缩包含 90 万行的行组,以减少存储使用量。
  • 合并一个或多个压缩行组,以将每个行组的行增加到最多 1,048,576 行。 例如,如果分别批量插入 5 批 102,400 行,则会收到 5 个压缩行组。 如果运行 REORGANIZE,这些行组将合并为一个包含 512,000 行的压缩行组。 这假定不存在任何字典大小或内存限制。
  • 数据库引擎尝试合并行组,其中 10 个% 或更多行被标记为与其他行组一起删除。 例如,行组 1 被压缩并且具有 500,000 行,而行组 21 被压缩并且具有 1,048,576 行。 Rowgroup 21 有 60% 的行标记为已删除,剩下 409,830 行。 数据库引擎倾向于合并这两个行组来压缩为一个有 909,830 行的新行组。

在执行数据加载后,增量存储中可能会有多个小型行组。 可以使用 ALTER INDEX REORGANIZE 将这些行组强制放入列存储中,然后将较小的压缩行组组合成较大的压缩行。 重新组织操作还会删除列存储标记为已删除的行。

注意

使用 Management Studio 重新组织列存储索引会将压缩的行组组合在一起,但不强制将所有行组压缩到列存储中。 已关闭的行组被压缩,但开放行组没有被压缩到列存储中。 若要强制压缩所有行组,请使用包含 COMPRESS_ALL_ROW_GROUPS = ON 的 Transact-SQL 示例

执行索引维护之前要考虑的事项

索引维护通过重新组织或重新生成索引实现,需要占用大量资源。 这会导致 CPU 消耗、内存占用量和存储 I/O 大幅增加。 但是,它提供的好处从至关重要到微不足道不等,具体取决于数据库工作负荷和其他因素。

为了避免利用不必要的资源,请避免不加选择地执行索引维护。 相反,应使用建议的策略根据经验为每个工作负荷确定指数维护的性能收益,并与实现这些收益所需的资源成本和工作负荷影响进行权衡。

当索引严重碎片化或页面密度较低时,从重新组织或重新生成索引中获得性能优势的可能性更高。 但是,这些并不是唯一需要考虑的事情。 各种因素,如查询模式(事务处理与分析和报告)、存储子系统行为、可用内存以及随时间推移的数据库引擎改进,都会起到一定的作用。

重要说明

应该在考虑每个工作负荷的特定上下文中的多个因素(包括维护的资源成本)之后做出索引维护决策。 不应仅根据固定的碎片或页面密度阈值做出这些决策。

索引重新生成的积极影响

客户经常在重新生成索引后观察到性能改进。 然而,在许多情况下,这些改进与碎片减少或页面密度增加无关。

索引重新生成具有一个重大的好处:它会通过扫描索引中的所有行来更新索引键列的统计信息。 此操作等同于执行 UPDATE STATISTICS ... WITH FULLSCAN,这使统计数据保持最新状态,有时与默认的采样统计数据更新相比会提高其质量。 更新统计信息时,将重新编译引用统计信息的查询计划。 如果由于统计信息过时、统计信息采样率不足或出于其他原因,查询的上一个计划不是最佳计划,则重新编译的计划通常性能更好。

客户经常错误地将这种改进归因于索引重新生成本身,认为这是碎片减少和页面密度增加的结果。 实际上,通过 更新统计信息 而不是重新生成索引,通常可以通过降低资源成本来实现相同的优势。

提示

与索引重新生成相比,更新统计信息消耗的资源成本很少,并且此操作通常几分钟内便可完成。 而索引重新生成可能需要数小时。

索引维护策略

Microsoft 建议客户考虑并采用以下索引维护策略:

  • 不要假设索引维护始终会显著改善工作负荷。
  • 衡量重新组织或重新生成索引对工作负荷的查询性能的特定影响。 可通过查询存储使用 A/B 测试技术衡量“维护前”和“维护后”的性能。
  • 如果你观察到重新生成索引可以提高性能,请尝试用更新统计信息替换它。 这可以导致类似的改进。 在这种情况下,可能不需要那么频繁地(或完全不需要)重新生成索引,而是可以执行定期的统计信息更新。 若要获得某些统计信息,可能需要使用 WITH SAMPLE ... PERCENTWITH FULLSCAN 子句提高采样率(这种情况并不常见)。
  • 随着时间的推移,监视索引碎片和页面密度,以查看这些值的上升或下降趋势与查询性能之间是否存在相关性。 如果增加碎片或降低页面密度导致性能下降到让人无法接受的程度,请重新组织或重新生成索引。 通常,只需重新组织或重新生成性能下降的查询所使用的特定索引就足够了。 这样就不必因维护数据库中每个索引产生更高的资源成本。
  • 在碎片/页面密度和性能之间建立相关性也可以让你确定索引维护的频率。 不要假设必须按固定计划执行维护。 更好的策略是监视碎片和页面密度,并在性能下降到不可接受的程度前根据需要运行索引维护。
  • 如果确定需要索引维护并且其资源成本是可接受的,请在资源使用时间较低(如果可能)期间执行维护。
  • 定期进行测试,因为资源使用模式会随着时间的推移而变化。

Azure SQL 数据库与 Azure SQL 托管实例中的索引维护

在 Azure SQL 数据库和 Azure SQL 托管实例中,除了上述注意事项和策略外,考虑索引维护的成本和好处也尤为重要。 客户应仅在有明确需求时执行,并考虑以下几点。

  • Azure SQL 数据库和 Azure SQL 托管实例实施资源管理,以根据预配的定价层限制 CPU、内存和 I/O 消耗。 这些限制适用于所有用户工作负荷,包括索引维护。 如果所有工作负荷的累积资源消耗接近资源边界,则由于资源争用,重新生成或重新组织操作可能会降低其他工作负荷的性能。 例如,由于并发的索引重新生成操作,事务日志 I/O 达到 100%,导致大批量加载速度变慢。 在 Azure SQL 托管实例中,通过在具有受限资源分配的单独 资源调控器 工作负荷组中运行索引维护来减少这种影响,代价是延长索引维护持续时间。
  • 为了节省成本,客户通常以最小的资源剩余空间提供数据库、弹性池和托管实例。 定价层的选择要足以满足应用程序工作负荷。 为了在不降低应用程序性能的情况下适应索引维护导致的资源使用量的显著增加,客户可能不得不预配更多资源并增加成本,而不一定会提高应用程序性能。
  • 在弹性池中,资源在池中的所有数据库之间共享。 即使特定数据库处于空闲状态,对该数据库执行索引维护也会影响同一池中其他数据库中并发运行的应用程序工作负荷。 有关详细信息,请参阅密集弹性池中的资源管理
  • 对于 Azure SQL 数据库和 Azure SQL 托管实例使用的大多数类型的存储而言,顺序 I/O 和随机 I/O 之间没有性能差异。 这减少了索引碎片对查询性能的影响。
  • 使用 读取横向扩展异地复制时,在主副本上执行索引维护时,副本上的数据延迟通常会增加。 如果为异地副本预配的资源量不足以支撑索引维护导致的事务日志生成增长,则异地副本可能会远远滞后于主副本,并导致系统重新设定其种子。 这会导致此副本在重设种子完成前不可用。 此外,在“高级”和“业务关键”服务层级中,用于实现高可用性的副本可能同样会在执行索引维护期间远远落后于主副本。 如果在索引维护执行期间或完成不久后需要执行故障转移,则故障转移所需的时间可能会超出预期。
  • 如果在主副本上执行索引重新生成,同时在可读副本上执行长时间运行的查询,则可以自动终止查询,以防止阻塞副本上的重做线程。

在特定情况下,Azure SQL 数据库和 Azure SQL 托管实例可能需要执行一次性或定期的索引维护,但这种情况不常见:

提示

如果确定 Azure SQL 数据库和 Azure SQL 托管实例工作负荷需要索引维护,则应重新组织索引或使用联机索引重新生成。 这允许查询工作负荷在重新生成索引时访问表。

此外,让此操作可恢复,可以避免在计划内或计划外的数据库故障转移中断操作时从头重新启动操作。 当索引较大时,使用可恢复索引操作尤为重要。

提示

脱机索引操作通常比联机操作完成得更快。 只有查询将无法在执行脱机索引操作期间访问表时(例如通过顺序 ETL 进程将数据加载到临时表后),才能使用此操作。

限制和局限

具有超过 128 个扩展数据块的行存储索引在两个单独的阶段重新生成:逻辑阶段和物理阶段。 在逻辑阶段,将把由索引使用的现有分配单元标记为释放,对数据行进行复制并排序,然后将它们移到为存储重新生成的索引而创建的新分配单元。 在物理阶段,先前标记为取消分配的分配单元在发生在后台的短事务中被物理删除,而且不需要很多锁。 有关分配单元的详细信息,请参阅页和区体系结构指南

ALTER INDEX REORGANIZE 语句要求包含索引的数据文件有可用空间,因为该操作只能在同一文件中分配临时工作页,而不能在同一文件组中的其他文件中分配临时工作页。 即使文件组有可用的空闲空间,如果数据文件空间不足,在重新组织操作期间,用户仍然会遇到错误1105:Could not allocate space for object '###' in database '###' because the '###' filegroup is full. Create disk space by deleting unneeded files, dropping objects in the filegroup, adding additional files to the filegroup, or setting autogrowth on for existing files in the filegroup

ALLOW_PAGE_LOCKS 设置为 OFF 时,无法重新组织索引。

在 SQL Server 2017 (14.x) 及更低版本中,重新生成聚集列存储索引是一项脱机操作。 执行重新生成操作时,数据库引擎必须获取表或分区上的排他锁。 即使在使用 NOLOCK、读取已提交的照隔离 (RCSI) 或快照隔离时,数据在重新生成期间仍处于脱机状态且不可用。 自 SQL Server 2019 (15.x) 起,可以使用 ONLINE = ON 选项重新生成聚集列存储索引。

警告

对超过 1,000 个分区的表创建和重新生成非对齐索引是可能的,但不支持。 这可能会导致性能下降,或在执行这些操作的过程中占用过多内存。 Microsoft 建议,当分区数超过 1,000 时,只使用对齐索引

统计信息限制

  • 创建重新生成索引时,将通过扫描表中的所有行创建或更新统计信息,这等同于在 CREATE STATISTICSUPDATE STATISTICS 中使用 FULLSCAN 子句。 但是,从 SQL Server 2012 (11.x) 开始,当创建或重新生成已分区索引时,不会通过扫描表中的所有行来创建或更新统计信息。 而会改为使用默认采样率。 若要通过扫描表中所有行的方法创建或更新已分区索引上的统计信息,请使用 CREATE STATISTICSUPDATE STATISTICS 以及 FULLSCAN 子句。
  • 同样,当索引创建或索引重新生成操作可恢复时,将使用默认采样率来创建或更新统计信息。 如果统计信息是在 PERSIST_SAMPLE_PERCENT 子句设置为 ON 的情况下创建或最后更新的,则可恢复索引操作使用持久的采样率来创建或更新统计信息。
  • 重新组织索引时,不会更新统计信息。

示例

检查行存储索引的碎片和页面密度

以下示例确定当前数据库中所有行存储索引的平均碎片和页面密度。 它使用 SAMPLED 模式快速返回可操作的结果。 若要获得更准确的结果,请使用 DETAILED 模式。 这需要扫描所有索引页面,并且可能需要很长时间。

SELECT OBJECT_SCHEMA_NAME(ips.object_id) AS schema_name,
       OBJECT_NAME(ips.object_id) AS object_name,
       i.name AS index_name,
       i.type_desc AS index_type,
       ips.avg_fragmentation_in_percent,
       ips.avg_page_space_used_in_percent,
       ips.page_count,
       ips.alloc_unit_type_desc
FROM sys.dm_db_index_physical_stats(DB_ID(), default, default, default, 'SAMPLED') AS ips
INNER JOIN sys.indexes AS i
ON ips.object_id = i.object_id
   AND
   ips.index_id = i.index_id
ORDER BY page_count DESC;

上一语句返回类似如下的结果集:

schema_name  object_name           index_name                               index_type    avg_fragmentation_in_percent avg_page_space_used_in_percent page_count  alloc_unit_type_desc
------------ --------------------- ---------------------------------------- ------------- ---------------------------- ------------------------------ ----------- --------------------
dbo          FactProductInventory  PK_FactProductInventory                  CLUSTERED     0.390015600624025            99.7244625648629               3846        IN_ROW_DATA
dbo          DimProduct            PK_DimProduct_ProductKey                 CLUSTERED     0                            89.6839757845318               497         LOB_DATA
dbo          DimProduct            PK_DimProduct_ProductKey                 CLUSTERED     0                            80.7132814430442               251         IN_ROW_DATA
dbo          FactFinance           NULL                                     HEAP          0                            99.7982456140351               239         IN_ROW_DATA
dbo          ProspectiveBuyer      PK_ProspectiveBuyer_ProspectiveBuyerKey  CLUSTERED     0                            98.1086236718557               79          IN_ROW_DATA
dbo          DimCustomer           IX_DimCustomer_CustomerAlternateKey      NONCLUSTERED  0                            99.5197553743514               78          IN_ROW_DATA

有关详细信息,请参阅 sys.dm_db_index_physical_stats

检查列存储索引的碎片

以下示例确定当前数据库中具有压缩行组的所有列存储索引的平均碎片。

SELECT OBJECT_SCHEMA_NAME(i.object_id) AS schema_name,
       OBJECT_NAME(i.object_id) AS object_name,
       i.name AS index_name,
       i.type_desc AS index_type,
       100.0 * (ISNULL(SUM(rgs.deleted_rows + ISNULL(ip.rows, 0)), 0)) / NULLIF(SUM(rgs.total_rows), 0) AS avg_fragmentation_in_percent
FROM sys.indexes AS i
INNER JOIN sys.dm_db_column_store_row_group_physical_stats AS rgs
ON i.object_id = rgs.object_id
   AND
   i.index_id = rgs.index_id
/* For nonclustered columnstore, include rows in the delete buffer */
LEFT JOIN sys.internal_partitions AS ip
ON i.object_id = ip.object_id
   AND
   i.index_id = ip.index_id
   AND
   rgs.partition_number = ip.partition_number
   AND
   ip.internal_object_type_desc = 'COLUMN_STORE_DELETE_BUFFER'
WHERE rgs.state_desc = 'COMPRESSED'
GROUP BY i.object_id, i.index_id, i.name, i.type_desc
ORDER BY schema_name, object_name, index_name, index_type;

上一语句返回类似如下的结果集:

schema_name  object_name            index_name                           index_type                avg_fragmentation_in_percent
------------ ---------------------- ------------------------------------ ------------------------- ----------------------------
Sales        InvoiceLines           NCCX_Sales_InvoiceLines              NONCLUSTERED COLUMNSTORE  0.000000000000000
Sales        OrderLines             NCCX_Sales_OrderLines                NONCLUSTERED COLUMNSTORE  0.000000000000000
Warehouse    StockItemTransactions  CCX_Warehouse_StockItemTransactions  CLUSTERED COLUMNSTORE     4.225346161484279

使用 SQL Server Management Studio 维护索引

重新组织或重新生成索引

  1. 在“对象资源管理器”中,展开包含要重新组织索引的表的数据库。
  2. 展开 “表” 文件夹。
  3. 展开要为其重新组织索引的表。
  4. 展开“索引”文件夹。
  5. 右键单击要重新组织的索引,然后选择重新组织
  6. 在“重新组织索引”对话框中,确认正确的索引位于“要重新组织的索引”网格中,然后选择“确定”
  7. 选中压缩大型对象列数据复选框,指定对包含大对象 (LOB) 数据的所有页面也进行压缩。
  8. 选择确定

重新组织表中的所有索引

  1. 在“对象资源管理器”中,展开包含你要重新组织索引的表的数据库。
  2. 展开 “表” 文件夹。
  3. 展开要为其重新组织索引的表。
  4. 右键单击索引文件夹,然后选择全部重新组织
  5. 重新组织索引对话框中,确认正确的索引位于要重新组织的索引中。 若要从要重新组织的索引网格中删除索引,请选择该索引,再按 Delete 键。
  6. 选中压缩大型对象列数据复选框,指定对包含大对象 (LOB) 数据的所有页面也进行压缩。
  7. 选择确定

使用 Transact-SQL 维护索引

注意

有关使用 Transact-SQL 重新生成或重新组织索引的更多示例,请参阅 ALTER INDEX 示例 - 行存储索引ALTER INDEX 示例 - 列存储索引

重新组织索引

下面的示例重新组织 AdventureWorks2022 数据库中 HumanResources.Employee 表内的 IX_Employee_OrganizationalLevel_OrganizationalNode 索引。

ALTER INDEX IX_Employee_OrganizationalLevel_OrganizationalNode
    ON HumanResources.Employee
    REORGANIZE;

下面的示例重新组织 AdventureWorksDW2022 数据库中 dbo.FactResellerSalesXL_CCI 表内的 IndFactResellerSalesXL_CCI 列存储索引。 运行此命令以将所有关闭和打开的行组强制到列存储中。

-- This command forces all closed and open row groups into columnstore.
ALTER INDEX IndFactResellerSalesXL_CCI
    ON FactResellerSalesXL_CCI
    REORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);

重新组织表中的所有索引

下面的示例重新组织 AdventureWorks2022 数据库中 HumanResources.Employee 表内的所有索引。

ALTER INDEX ALL ON HumanResources.Employee
   REORGANIZE;

重新生成索引

下面的示例在 AdventureWorks2022 数据库的 Employee 表中重新生成单个索引。

ALTER INDEX PK_Employee_BusinessEntityID ON HumanResources.Employee
REBUILD
;

重新生成表中的所有索引

下面的示例使用 ALL 关键字重新生成与 AdventureWorks2022 数据库中的表关联的所有索引。 其中指定了三个选项。

ALTER INDEX ALL ON Production.Product
REBUILD WITH (FILLFACTOR = 80, SORT_IN_TEMPDB = ON,
              STATISTICS_NORECOMPUTE = ON)
;

有关详细信息,请参阅 ALTER INDEX