Beg*_*DBA 2 sql-server statistics sql-server-2012 log-shipping
我们在主 DC 中有一个大约 15 TB 的数据库,其中包含分区表,其中最大的一个大约 7 TB。
为避免用户报告 LS 设置是在不同 DC 中为上述数据库完成的,其中 LS 还原作业每晚运行一次。
在 Primay-update stats 和 update stats AYNC 都设置为 True,因此在辅助上反映相同。我不确定为什么有人同时启用这两个功能,应该是这样吗?
现在在次要方面,我看到当从用户运行 SELECT 查询时会发生很多阻塞,结果是 select statman 语句用于该只读数据库的自动更新统计信息。
我不明白为什么在启用自动更新异步时会有一个?
此外,在 LS 恢复期间是否会恢复在主要(每周一次)上完成的更新统计信息?如果没有,在 LS 备用模式中涉及的辅助数据库上运行更新统计信息的更好方法是什么,可能是每天运行以获得更好的选择性能?
请指教
TL; 博士
在只读数据库中,可以利用定期的永久统计信息来满足查询计划。如果 sql server 需要只读数据库的新统计信息,或者数据库中的这些永久统计信息已经过时,则可以创建/更新临时统计信息。这些统计信息驻留在TempDBSQL Server 中并由其管理(您只能删除它们)。
有两种临时统计信息,一种是由于缺少统计信息而创建的,另一种是“更新”的。
可以通过在主数据库上手动添加统计信息或生成报告查询的估计执行计划(也在主实例/数据库上)来删除临时统计信息的创建。在这个答案中进一步详细说明。
永久统计信息可以更新并“转换”为只读数据库上的临时统计信息。
临时统计更新可以通过解决更新更频繁地在主数据库的统计或禁用了自动更新统计的只读数据库日志恢复完成后。
要禁用自动更新统计信息,您可以在只读数据库上执行此操作:ALTER DATABASE [Database] SET AUTO_UPDATE_STATISTICS OFF;这将停止此只读数据库上临时统计信息的更新。
关于恢复到备用数据库和临时统计数据的另一个重要部分是,当应用日志备份时,临时统计数据会再次更新,即使它们仍然存在于 sys.stats 中。
在您的情况下,解释Statman查询的日常问题(如果需要创建/更新,则每天重新计算临时统计信息)。
Norecovery --> Standby --> Norecovery ... 删除临时统计信息
关于临时统计信息的另一个有趣部分是,当数据库状态更改为使用RESTORE DATABASE ... WITH NORECOVERY.
use MASTER
GO
RESTORE DATABASE [ReadOnly2] with NORECOVERY
RESTORE DATABASE [ReadOnly2] WITH STANDBY = 'D:\temp\ReadOnly_Standby.bak'
Run Code Online (Sandbox Code Playgroud)
有效刷新所有对象的临时统计信息
SELECT * From sys.stats where is_temporary = 1;
Run Code Online (Sandbox Code Playgroud)
并在每个状态更改 + 测试查询运行之间重新计算相同的 2 个统计信息。
这两个永久统计信息都显示在我们执行计划的 xml 中
<StatisticsInfo Database="[ReadOnly2]" Schema="[dbo]" Table="[Bla]" Statistics="[IX_Bla_indexedval]" ModificationCount="12000000" SamplingPercent="15.8812" LastUpdate="2019-06-12T10:52:32.25" />
<StatisticsInfo Database="[ReadOnly2]" Schema="[dbo]" Table="[Bla]" Statistics="[PK__Bla__3214EC075017BD54]" ModificationCount="12000000" SamplingPercent="15.2345" LastUpdate="2019-06-12T10:52:35.34" />
Run Code Online (Sandbox Code Playgroud)
使用modificationcount, samplingpercent&lastupdate在临时统计信息“更新”的情况下再次执行查询后更改。
ModificationCount="0" SamplingPercent="5.71018" LastUpdate="2019-06-13T11:32:36.5"
Run Code Online (Sandbox Code Playgroud)
创建临时统计信息
常规的非临时统计信息不会更新/您无法更新(甚至不是临时的)只读数据库的统计信息。
您在报告实例上看到的是临时统计信息的创建/“更新”。
这些统计信息驻留在 TempDB 中,SQL Server 创建和更新它们。
复制行为
我能够在只读数据库(附录 #1)中复制具有 100M 行的表上创建临时统计信息的行为

使用您提到的麻烦的 StatMan 查询。
SELECT StatMan([SC0], [SB0000]) FROM (SELECT TOP 100 PERCENT [SC0], step_direction([SC0]) over (order by NULL) AS [SB0000] FROM (SELECT [NonIndexedVal] AS [SC0] FROM [dbo].[Bla] TABLESAMPLE SYSTEM (7.707678e-001 PERCENT) WITH (READUNCOMMITTED) ) AS _MS_UPDSTATS_TBL_HELPER ORDER BY [SC0], [SB0000] ) AS _MS_UPDSTATS_TBL OPTION (MAXDOP 16)
Run Code Online (Sandbox Code Playgroud)
Maxdop 16(最大核心数),因为我在我的测试机器上将 MAXDOP 设置为 0,YMMV
重启实例
当我重新启动实例时,我看到了相同的行为,表明这些实际上是临时统计数据。可以在此处找到有关临时统计数据的一些问答。
现在我们看到的是临时统计信息的创建,它们是在查询运行之前创建的。
对于大表,他们确实注意到采样率仍然适用于临时统计数据。
另一点要注意的是,作为自动统计的一部分创建的统计数据使用数据采样,因此这些统计数据的创建速度很快,并且不依赖于表的大小
当 sql server 重新启动/数据库恢复时,您如何解决统计信息的创建问题?
如果可以这样做,您可以编写它们的脚本并在主“主”数据库上创建它们。
查找临时统计信息
SELECT OBJECT_ID, name, auto_created,
user_created, is_temporary
FROM sys.stats
WHERE is_temporary = 1;
Run Code Online (Sandbox Code Playgroud)
编写统计数据
不使用 T-SQL 或使用 T-SQL 回答如何在 Sql Server 中编写统计脚本?(使用 T-SQL)作者:Martin Smith
这可以解决您一次又一次创建统计信息的主要问题。
如果问题是由于创建了临时统计数据而您无法正确编写它们的脚本,则另一个想法可能是为主数据库上的报告查询创建估计的执行计划。这应该创建自动创建统计信息 = on 时所需的统计信息。
临时统计更新
另一个可能出现的问题是陈旧的永久性统计数据。正如在前面提到的博客中所指出的,陈旧的永久统计信息可以更新并设置为is_temporary=1.
这意味着只读数据库上的永久统计信息可以成为临时统计信息,直到实例重新启动。当您更新主服务器上的统计信息时,它应该会在应用日志时转移到辅助服务器。
异步临时统计更新
我们看到异步统计更新也适用于这些临时统计!
运行附录 #1 后,我们运行下一个代码段:
USE MASTER
GO
ALTER DATABASE [ReadOnly] SET READ_WRITE;
ALTER DATABASE [ReadOnly] SET AUTO_UPDATE_STATISTICS OFF
USE [ReadOnly]
GO
INSERT INTO dbo.Bla WITH(TABLOCK)(Indexedval,NonIndexedVal)
SELECT TOP(10000000) --10M
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum2
FROM master..spt_values spt1
CROSS JOIN master..spt_values spt2
CROSS JOIN master..spt_values spt3;
ALTER DATABASE [ReadOnly] SET AUTO_UPDATE_STATISTICS ON
USE MASTER
GO
ALTER DATABASE [ReadOnly] SET READ_ONLY;
SELECT Indexedval
FROM dbo.Bla
WHERE Indexedval =999999
AND 1= (SELECT 1);
Run Code Online (Sandbox Code Playgroud)
以上SELECT立即运行。
运行查询后,将显示后果。
临时统计更新在查询执行后运行。
SELECT StatMan([SC0]) FROM (SELECT TOP 100 PERCENT [Indexedval] AS [SC0] FROM [dbo].[Bla] WITH (READUNCOMMITTED) ORDER BY [SC0] ) AS _MS_UPDSTATS_TBL OPTION (MAXDOP 1)
Run Code Online (Sandbox Code Playgroud)
禁用只读数据库的自动更新统计信息
您可以通过运行下一条语句禁用将永久统计信息更新为临时统计信息,您可以在只读数据库上更改此设置,它仍然可以工作。
ALTER DATABASE [ReadOnly] SET AUTO_UPDATE_STATISTICS OFF;
Run Code Online (Sandbox Code Playgroud)
临时统计升级的解决方案。
此外,在 LS 恢复期间是否会恢复在主要(每周一次)上完成的更新统计信息?
更新您的统计数据将被带到二级,在可能的情况下更多地更新它们将导致较少的陈旧统计数据和较少陈旧的永久统计数据转换为临时统计数据。
研究这两个将是我解决这个问题的方法。
当大量启用 traceflags 以帮助同事并深入挖掘 2 到 3000 之间时,似乎可以使用traceflag 2362禁用临时统计信息。
您可以像这样启用它们:
DBCC TRACEON(2362,-1);
Run Code Online (Sandbox Code Playgroud)
并且不会创建所有新的临时统计信息。现有的临时统计数据将保留,直到它们被删除。例如,通过将数据库设置为离线和再次在线。
附录 2
当运行附录 #2 中的查询并将日志备份应用到备用数据库时,统计信息会在每次恢复后更新。
即使在应用“空”日志备份之后。
在附录 2 中,在每个日志备份还原之间运行以下查询:
SELECT Indexedval
FROM dbo.Bla
WHERE Indexedval =999999
AND 1= (SELECT 1);
Run Code Online (Sandbox Code Playgroud)
这些每次都会触发临时统计数据更新。
证明
所有这一切意味着在晚上应用日志将使临时统计更新每天运行,而无需重新启动实例。
解决这个问题
恢复日志备份时,它们仍然存在:
SELECT name, is_temporary From sys.stats where is_temporary = 1;
name is_temporary
PK__Bla__3214EC075017BD54 1
IX_Bla_indexedval 1
Run Code Online (Sandbox Code Playgroud)
附录 #1(在只读数据库中包含 100M 记录的表。)
CREATE DATABASE [ReadOnly]
CONTAINMENT = NONE
ON PRIMARY
( NAME = N'ReadOnly', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL02\MSSQL\DATA\ReadOnly.mdf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
LOG ON
( NAME = N'ReadOnly_log', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL02\MSSQL\DATA\ReadOnly_log.ldf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
GO
ALTER DATABASE [ReadOnly] SET COMPATIBILITY_LEVEL = 140
ALTER DATABASE [ReadOnly] SET AUTO_CREATE_STATISTICS ON(INCREMENTAL = OFF)
ALTER DATABASE [ReadOnly] SET AUTO_UPDATE_STATISTICS ON
ALTER DATABASE [ReadOnly] SET AUTO_UPDATE_STATISTICS_ASYNC ON
ALTER DATABASE [ReadOnly] SET READ_WRITE
ALTER DATABASE [ReadOnly] SET RECOVERY SIMPLE
ALTER DATABASE [ReadOnly] SET MULTI_USER
ALTER DATABASE [ReadOnly] SET PAGE_VERIFY CHECKSUM
USE [ReadOnly]
GO
CREATE TABLE dbo.Bla(Id INT IDENTITY(1,1) PRIMARY KEY NOT NULL, Indexedval INT,NonIndexedVal INT);
CREATE INDEX IX_Bla_indexedval on dbo.Bla(Indexedval);
INSERT INTO dbo.Bla WITH(TABLOCK)(Indexedval,NonIndexedVal)
SELECT TOP(10000000) --10M
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum2
FROM master..spt_values spt1
CROSS JOIN master..spt_values spt2
CROSS JOIN master..spt_values spt3;
GO 10
USE MASTER
GO
ALTER DATABASE [ReadOnly] SET READ_ONLY;
USE [ReadOnly]
GO
SELECT NonIndexedVal
FROM dbo.Bla
WHERE NonIndexedVal = 999999;
Run Code Online (Sandbox Code Playgroud)
#附录 2
ALTER DATABASE [ReadOnly] SET READ_WRITE;
ALTER DATABASE [ReadOnly] SET RECOVERY FULL
BACKUP DATABASE [ReadOnly] to disk = 'D:\temp\ReadOnly.bak'
WITH COMPRESSION, STATS=5
RESTORE FILELISTONLY FROM DISK = 'D:\temp\ReadOnly.bak'
RESTORE DATABASE [ReadOnly2] FROM disk = 'D:\temp\ReadOnly.bak'
WITH MOVE 'ReadOnly' to 'D:\temp\ReadOnly2.mdf'
,MOVE 'ReadOnly_log' to 'F:\temp\ReadOnly_log2.ldf'
, STANDBY = 'D:\temp\ReadOnly_Standby.bak'
USE [ReadOnly2]
GO
ALTER DATABASE [ReadOnly2] SET AUTO_UPDATE_STATISTICS ON
SELECT Indexedval
FROM dbo.Bla
WHERE Indexedval =999999
AND 1= (SELECT 1);
USE [ReadOnly]
INSERT INTO dbo.Bla WITH(TABLOCK)(Indexedval,NonIndexedVal)
SELECT TOP(2000000) --2M
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum1,
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum2
FROM master..spt_values spt1
CROSS JOIN master..spt_values spt2
CROSS JOIN master..spt_values spt3;
USE MASTER
GO
BACKUP LOG [ReadOnly] to disk = 'D:\temp\ReadOnlyLog.trn'
WITH COMPRESSION, STATS=5
RESTORE LOG [ReadOnly2] FROM DISK='D:\temp\ReadOnlyLog.trn'
WITH STANDBY = 'D:\temp\ReadOnly_Standby.bak'
USE [ReadOnly2]
SELECT Indexedval
FROM dbo.Bla
WHERE Indexedval =999999
AND 1= (SELECT 1);
BACKUP LOG [ReadOnly] to disk = 'D:\temp\ReadOnlyLog2.trn'
WITH COMPRESSION, STATS=5
RESTORE LOG [ReadOnly2] FROM DISK='D:\temp\ReadOnlyLog2.trn'
WITH STANDBY = 'D:\temp\ReadOnly_Standby.bak'
USE [ReadOnly2]
SELECT Indexedval
FROM dbo.Bla
WHERE Indexedval =999999
AND 1= (SELECT 1);
SELECT * From sys.stats where is_temporary = 1
Run Code Online (Sandbox Code Playgroud)