UPDATE STATISTICS
语句只使用表的记录样本。使用UPDATE STATISTICS WITH FULLSCAN
将扫描整个表。
默认情况下,UPDATE STATISTICS
语句同时更新索引和列的统计信息。使用COLUMNS
选项只会更新列的统计信息。使用INDEX
选项只会更新索引的统计信息。
例如,通过使用ALTER INDEX … REBUILD
重建索引也会使用等效于使用WITH FULLSCAN
进行索引统计信息更新,除非表是分区的,在这种情况下只对统计信息进行抽样(适用于SQL Server 2012及更高版本)。
手动创建的使用CREATE STATISTICS
命令的统计信息在任何ALTER INDEX ... REBUILD
操作中都不会被更新,包括ALTER TABLE ... REBUILD
。如果在重建的表上定义了聚集索引,则ALTER TABLE ... REBUILD
会更新聚集索引的统计信息。
例如使用ALTER INDEX ... REORGANIZE
重新组织索引不会更新任何统计信息。
简短的回答是,你需要使用UPDATE STATISTICS
来更新列统计信息,而索引重建只会更新索引统计信息。你可以使用UPDATE STATISTICS (tablename) WITH FULLSCAN;
语法强制更新表上的所有统计信息,包括索引统计信息和手动创建的统计信息。
以下代码示例说明了上述封装的规则:
首先,我们将创建一个带有几个列和一个聚集索引的表:
USE tempdb;
IF OBJECT_ID(N'dbo.SomeTable', N'U') IS NOT NULL
DROP TABLE dbo.SomeTable;
CREATE TABLE dbo.SomeTable
(
rn int NOT NULL IDENTITY(1,1)
CONSTRAINT pk
PRIMARY KEY NONCLUSTERED
, i int NOT NULL INDEX i
, d sysname NOT NULL
) ON [PRIMARY] WITH (DATA_COMPRESSION = NONE);
CREATE UNIQUE CLUSTERED INDEX cx ON dbo.SomeTable (i, d);
CREATE STATISTICS d ON dbo.SomeTable (d) WITH FULLSCAN;
INSERT INTO dbo.SomeTable (d, i)
SELECT c1.name, c1.id
FROM sys.syscolumns c1;
这个查询显示了每个统计对象的最后更新日期。
SELECT ObjectName = sc.name + N'.' + o.name
, StatsName = s.name
, StatsDate = STATS_DATE(s.object_id, s.stats_id)
FROM sys.stats s
INNER JOIN sys.objects o ON s.object_id = o.object_id
INNER JOIN sys.schemas sc ON o.schema_id = sc.schema_id
WHERE sc.name = N'dbo'
AND o.name = N'SomeTable';
结果显示尚未进行任何更新,这是正确的,因为我们刚刚创建了该表:
╔═══════════════╦═══════════╦═══════════╗ ║ 对象名称 ║ 统计名称 ║ 统计日期 ║ ╠═══════════════╬═══════════╬═══════════╣ ║ dbo.SomeTable ║ cx ║ NULL ║ ║ dbo.SomeTable ║ i ║ NULL ║ ║ dbo.SomeTable ║ pk ║ NULL ║ ║ dbo.SomeTable ║ d ║ NULL ║ ╚═══════════════╩═══════════╩═══════════╝
让我们重新构建整个表格,看看是否更新了统计数据:
ALTER TABLE dbo.SomeTable REBUILD;
SELECT ObjectName = sc.name + N'.' + o.name
, StatsName = s.name
, StatsDate = STATS_DATE(s.object_id, s.stats_id)
FROM sys.stats s
INNER JOIN sys.objects o ON s.object_id = o.object_id
INNER JOIN sys.schemas sc ON o.schema_id = sc.schema_id
WHERE sc.name = N'dbo'
AND o.name = N'SomeTable';
对象名称 统计名称 统计日期
dbo.SomeTable cx 2018-09-17 14:09:13.590
dbo.SomeTable i NULL
dbo.SomeTable pk NULL
dbo.SomeTable d NULL
结果显示只有聚集索引统计信息被更新。
接下来,我们执行离散的UPDATE STATS操作:
UPDATE STATISTICS dbo.SomeTable(d) WITH FULLSCAN;
SELECT ObjectName = sc.name + N'.' + o.name
, StatsName = s.name
, StatsDate = STATS_DATE(s.object_id, s.stats_id)
FROM sys.stats s
INNER JOIN sys.objects o ON s.object_id = o.object_id
INNER JOIN sys.schemas sc ON o.schema_id = sc.schema_id
WHERE sc.name = N'dbo'
AND o.name = N'SomeTable';
如您所见,我们刚刚更新了d
列的统计数据:
╔═══════════════╦═══════════╦═════════════════════════╗ ║ 对象名称 ║ 统计名称 ║ 统计日期 ║ ╠═══════════════╬═══════════╬═════════════════════════╣ ║ dbo.SomeTable ║ cx ║ 2018-09-17 14:09:13.590 ║ ║ dbo.SomeTable ║ i ║ NULL ║ ║ dbo.SomeTable ║ pk ║ NULL ║ ║ dbo.SomeTable ║ d ║ 2018-09-17 14:09:13.597 ║ ╚═══════════════╩═══════════╩═════════════════════════╝现在,我们将更新整个表的统计数据:
UPDATE STATISTICS dbo.SomeTable WITH FULLSCAN;
SELECT ObjectName = sc.name + N'.' + o.name
, StatsName = s.name
, StatsDate = STATS_DATE(s.object_id, s.stats_id)
FROM sys.stats s
INNER JOIN sys.objects o ON s.object_id = o.object_id
INNER JOIN sys.schemas sc ON o.schema_id = sc.schema_id
WHERE sc.name = N'dbo'
AND o.name = N'SomeTable';
如您所见,确保所有统计数据更新的唯一方法是手动更新每个统计项,或者使用UPDATE STATISTICS (table);
命令更新整个表。
ALTER INDEX ... REBUILD
或UPDATE STATISTICS
语句明确更新的统计数据才会被更新。如果表本身被重建,只有聚集索引的统计数据会被更新。顺便提一下,主键和聚集索引不一定由同一个索引对象支持。 - Hannah VernonSQL Server统计信息的Microsoft Docs页面states:
重建、碎片整理或重新组织索引等操作不会改变数据的分布。因此,在执行ALTER INDEX REBUILD、DBCC DBREINDEX、DBCC INDEXDEFRAG或ALTER INDEX REORGANIZE操作后,您无需更新统计信息。当使用ALTER INDEX REBUILD或DBCC DBREINDEX在表或视图上重建索引时,查询优化器会更新统计信息,但这个统计信息更新是重新创建索引的副产品。查询优化器不会在DBCC INDEXDEFRAG或ALTER INDEX REORGANIZE操作后更新统计信息。
REINDEX
在重建索引的过程中会作为副作用更新列统计信息 - 你不需要强制更新统计信息。表中的数据并没有发生变化。它仍然是相同的数据,只是 a) 它在磁盘上的位置发生了改变(当页面被重新组织时),或者 b) 它位于不同的页面上(在重新构建的情况下)。因此:重新索引确实会更新(某些)统计信息:没有必要再次进行更新。 - Ian Boyd