查询存储的总体资源消耗报告是否告诉我我的数据库运行得很糟糕,或者这些数字只是被破坏了?

J.D*_*.D. 7 performance sql-server upgrade query-store sql-server-2019

最近,我们将一台生产服务器从 SQL Server 2016 升级到 SQL Server 2019 (CU 15)。这对我来说是一个在我们的主应用程序数据库上启用查询存储的绝佳机会。它已经运行了几天,这是总体资源消耗报告显示的内容:

总体资源消耗报告

在屏幕截图中,我精心挑选了一些看起来很疯狂的数字(查询存储启用的第一天),并将它们标准化为更容易讨论的度量单位。诸如逻辑读取消耗约 183 TB的数据,或内存消耗约 5 TB的数据之类的事情,在这台服务器上似乎几乎是不可能的。

该数据库是数据库中的 John Smith,数据文件大小仅为100 GB ,日志文件大小为200 GB 。全天最多可能有 100 个不同的用户连接到它,并且一天内不会创建大量交易。服务器本身仅为其配置了32 GB内存。要消耗5 TB内存,一天中分配的内存需要被填满150 多次。

我可以考虑添加的唯一其他可能相关的信息是升级后,我们立即将此数据库的“兼容性级别”设置为 150 (SQL Server 2019) 并保留“旧基数估计”设置。我知道这并不理想,最好在收集基线指标时让尘埃落定,但升级的部分原因是为了解决一些紧急的性能问题,从我们的测试来看,这些设置组合实际上最有效(并且仍然看起来)工作得很好)。

我们之前遇到的一些性能问题是由于疯狂的基数估计造成的,如果查询存储使用估计数据点,那么我实际上可以看到此报告的数字是相关的,但我不得不想象该报告是使用实际数据点?不过,如果这是我的生产服务器/数据库在我不断征服基数估计问题时的配置方式存在根本错误的另一个迹象,那将会很有趣。

我是否读错了这些数字,查询存储是否出问题了,或者我的服务器是否正常?

Dav*_*oft 6

我的工作站可以执行大约 100K-150K LIO/CPU 其次。逻辑 IO (LIO) 正在从页面缓存中读取单个 8KB 页面。即,当使用一个简单的查询计划对缓存表进行大型并行扫描时,我得到如下 IO 统计信息:

表“frs_big”。扫描计数 9,逻辑读取519631,物理读取 0,页面服务器读取 0,预读读取 0,页面服务器预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 页面服务器读取 0,lob 读取提前读取 0,lob 页面服务器预读读取 0。

(1 行受影响)

SQL Server 执行时间:CPU 时间 = 5109毫秒,运行时间 = 655 毫秒。

对于 MAXDOP 1,相同的扫描只需要 3625 CPU 毫秒。它运行 Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz。

在此期间,您有 22,902,891,616KB 的逻辑 IO,其中每个 LIO 为 8KB,因此22902891616/8LIO 和 20,393,171ms 的 CPU 时间。所以

with q as
(
  select 22902891616/8 lio, 20393171 cpu 
)
select 1000*lio/cpu lio_per_cpu_sec, lio/(24*60*60) lio_per_sec
from q 
Run Code Online (Sandbox Code Playgroud)

或者

lio_per_cpu_sec                         lio_per_sec
--------------------------------------- ---------------------------------------
140,383                                 33,134
Run Code Online (Sandbox Code Playgroud)

140,383 LIO/CPU 第二。因此,逻辑 IO 的数量大致相当于 CPU 的总使用量,并且在 24 小时内平均为 33,000 LIO/秒。

鉴于您升级到 2019 年并使用了所有较新的优化器行为,您可能有一些糟糕的计划,导致过度扫描,但服务器的速度和适度的大小使您的缓存命中率保持非常高,并保持感知性能可接受。

我不确定该内存指标意味着什么。这是报告背后的查询:

exec sp_executesql N'WITH DateGenerator AS
(
SELECT CAST(@interval_start_time AS DATETIME) DatePlaceHolder
UNION ALL
SELECT  DATEADD(d, 1, DatePlaceHolder)
FROM    DateGenerator
WHERE   DATEADD(d, 1, DatePlaceHolder) < @interval_end_time
), WaitStats AS
(
SELECT
    ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms))*1,2) total_query_wait_time
FROM sys.query_store_wait_stats ws
    JOIN sys.query_store_runtime_stats_interval itvl ON itvl.runtime_stats_interval_id = ws.runtime_stats_interval_id
WHERE NOT (itvl.start_time > @interval_end_time OR itvl.end_time < @interval_start_time)
GROUP BY DATEDIFF(d, 0, itvl.end_time)
),
UnionAll AS
(
SELECT
    CONVERT(float, SUM(rs.count_executions)) as total_count_executions,
    ROUND(CONVERT(float, SUM(rs.avg_duration*rs.count_executions))*0.001,2) as total_duration,
    ROUND(CONVERT(float, SUM(rs.avg_cpu_time*rs.count_executions))*0.001,2) as total_cpu_time,
    ROUND(CONVERT(float, SUM(rs.avg_logical_io_reads*rs.count_executions))*8,2) as total_logical_io_reads,
    ROUND(CONVERT(float, SUM(rs.avg_logical_io_writes*rs.count_executions))*8,2) as total_logical_io_writes,
    ROUND(CONVERT(float, SUM(rs.avg_physical_io_reads*rs.count_executions))*8,2) as total_physical_io_reads,
    ROUND(CONVERT(float, SUM(rs.avg_clr_time*rs.count_executions))*0.001,2) as total_clr_time,
    ROUND(CONVERT(float, SUM(rs.avg_dop*rs.count_executions))*1,0) as total_dop,
    ROUND(CONVERT(float, SUM(rs.avg_query_max_used_memory*rs.count_executions))*8,2) as total_query_max_used_memory,
    ROUND(CONVERT(float, SUM(rs.avg_rowcount*rs.count_executions))*1,0) as total_rowcount,
    ROUND(CONVERT(float, SUM(rs.avg_log_bytes_used*rs.count_executions))*0.0009765625,2) as total_log_bytes_used,
    ROUND(CONVERT(float, SUM(rs.avg_tempdb_space_used*rs.count_executions))*8,2) as total_tempdb_space_used,
    DATEADD(d, ((DATEDIFF(d, 0, rs.last_execution_time))),0 ) as bucket_start,
    DATEADD(d, (1 + (DATEDIFF(d, 0, rs.last_execution_time))), 0) as bucket_end
FROM sys.query_store_runtime_stats rs
WHERE NOT (rs.first_execution_time > @interval_end_time OR rs.last_execution_time < @interval_start_time)
GROUP BY DATEDIFF(d, 0, rs.last_execution_time)
)
SELECT 
    total_count_executions,
    total_duration,
    total_cpu_time,
    total_logical_io_reads,
    total_logical_io_writes,
    total_physical_io_reads,
    total_clr_time,
    total_dop,
    total_query_max_used_memory,
    total_rowcount,
    total_log_bytes_used,
    total_tempdb_space_used,
    total_query_wait_time,
    SWITCHOFFSET(bucket_start, DATEPART(tz, @interval_start_time)) , SWITCHOFFSET(bucket_end, DATEPART(tz, @interval_start_time))
FROM
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY bucket_start ORDER BY bucket_start, total_duration DESC) AS RowNumber
FROM UnionAll , WaitStats
) as UnionAllResults
WHERE UnionAllResults.RowNumber = 1
OPTION (MAXRECURSION 0)',N'@interval_start_time datetimeoffset(7),@interval_end_time datetimeoffset(7)',@interval_start_time='2022-01-03 15:57:04.2571919 -06:00',@interval_end_time='2022-02-03 15:57:04.2571919 -06:00'
Run Code Online (Sandbox Code Playgroud)

ROUND(CONVERT(float, SUM(rs.avg_query_max_used_memory*rs.count_executions))*8,2) as total_query_max_used_memory,
Run Code Online (Sandbox Code Playgroud)

在我看来这并不是一个非常有用的指标。