Azure SQL 数据库上 PAGEIOLATCH_SH 的 20 秒,执行计划的子树成本为 0.06
20s of PAGEIOLATCH_SH on Azure SQL Database with execution plan of 0.06 subtree cost
(Azure 支持给出了模糊的答案,所以我满怀希望地求助于 Stack Overflow!:))
情况
用户抱怨查询超时错误。我从 MSMS 运行 same 查询两次(相同的参数)。第一个 运行 需要 looong(23 秒或有时 50 秒),第二个、第三个等 运行 需要 <1 秒。执行计划相同,子树成本为 0.0671..
我注意到的区别是第一个执行计划中的 WaitStats
部分具有以下值:
WaitCount: 2751
WaitTimeMS: 6
WaitType: MEMORY_ALLOCATION_EXT
WaitCount: 2751
WaitTimeMS: 6
WaitType: IO_QUEUE_LIMIT
WaitCount: 669
WaitTimeMS: 20360
WaitType: PAGEIOLATCH_SH
Azure SQL DTU 平均最大约为 5%。
Azure 支持说这可能是执行计划编译时间。我怀疑因为 clearing the Proc Cache 确实 而不是 在第一个 运行.
之后重新引入漫长的等待
执行计划 "leafs" 是 Index Seek (NonClustedred)
、Key Lookup (Clustered)
和 RID Lookup (Heap)
。 RID Lookup
为 39%(0.0671)。返回一行(即 TOP 1
)。
3 个表在 CROSS APPLY
的查询中。最大的一个有 800 万行,包含一个 ~40KB VARBINARY
列(未在查询中的任何地方引用或返回)。
查询
DECLARE @p0 VARCHAR(50); SET @p0 = '<GUID1>'
SELECT TOP 1 p.Id, p.DateCreatedUtc, p.PreviousOwnerId
FROM (
-- last project save
SELECT ps.Id AS psId, p.*
FROM Projects p
CROSS APPLY (
SELECT TOP 1 *
FROM ProjectSaves
WHERE ProjectId = p.Id
ORDER BY LastModifiedUtc DESC
) AS ps
WHERE p.OwnerId = @p0
) p
CROSS APPLY (
SELECT TOP 1 *
FROM ProjectSavePhotos
WHERE ProjectSaveId = p.psId AND (name LIKE 'uploads%')
) ps
WHERE P.IsDeleted = 0 AND p.Id NOT IN ('<GUID2>')
ORDER BY p.DateCreatedUtc DESC
p.OwnerId
被索引并且 Azure 自动创建了另外两个索引:
- 在
OwnerId
和 Id
和
- 在
IsDeleted
、OwnerId
和 Id
ps.ProjectId
已编入索引并包含 LastModifiedUtc
psp.ProjectSaveId
已编入索引并包含 name
问题
如何诊断 20 多岁 PAGEIOLATCH_SH
的根本原因?可能仅仅是 VARBINARY
列的存在吗?如果是这样,我如何确认?
参考阅读
https://www.sqlshack.com/handling-excessive-sql-server-pageiolatch_sh-wait-types/
https://sqlperformance.com/2014/06/io-subsystem/knee-jerk-waits-pageiolatch-sh
除非我遗漏了什么这听起来很正常,但我必须查看执行计划。您可能想要摆脱那个 Key Lookup。 Key Lookup 是否抓取了 Index Seek 中使用的索引中缺少的列?如果是,将它添加到 Index Seek 中的索引中,看看会发生什么。如此大 table 的交叉应用会占用大量缓冲区 space,具体取决于它的宽度,但如果没有看到查询,我不能说是否有更好的方法来获取您的数据。
这也可能有帮助:
将集群 PK 添加到 ProjectSaves 也会产生很大的不同。
您看到的是 SQL Azure 数据库在一段时间未使用数据库或数据库层已扩展后收缩内存分配的效果。行为与您提到的完全一样,第一次执行或前几次执行 运行 性能不佳,直到内存分配恢复正常。您不会在持续使用的数据库上看到它。
这种内存分配行为会造成您在第一次执行查询时看到的那些等待,而您在 Microsoft SQL 服务器上看不到这种行为。对于这些事情,我通常会说 Azure SQL 数据库和 SQL 服务器不一样,它们在很多方面都不同。
任何类型的数据都作为页面存储在 SQL 服务器中。根据页面包含的内容(数据、索引等),有不同类型的页面。参见 documentation page。
The fundamental unit of data storage in SQL Server is the page. The disk space allocated to a data file (.mdf or .ndf) in a database is logically divided into pages numbered contiguously from 0 to n. Disk I/O operations are performed at the page level. That is, SQL Server reads or writes whole data pages.
当您运行 对您的数据库进行查询时,SQL 引擎将在系统内存(缓冲区)中查找它是否具有执行查询所需的所有页面。如果缺少某些页面,SQL 引擎会将它们从磁盘加载到内存中。
PAGEIOLATCH_SH
等待对应于页面从磁盘拉到内存(缓冲区)。一旦页面被加载到系统内存中,它们就会一直保留在那里直到被驱逐。这就是为什么您的查询的第一个 运行 比随后的 运行 花费更多时间的原因。在第一个 运行 期间,SQL 引擎需要从磁盘检索数据。后续运行不再如此
为了减少第一次查询等待,有不同的策略。正如 Alberto 所提到的,如果您经常 运行 此查询,那么页面就不太可能被从缓冲区中逐出。如 influent 所述,您可以重写查询或创建新索引,这样 SQL 引擎就不必加载那么多页面。请post进一步调查查询计划。
(Azure 支持给出了模糊的答案,所以我满怀希望地求助于 Stack Overflow!:))
情况
用户抱怨查询超时错误。我从 MSMS 运行 same 查询两次(相同的参数)。第一个 运行 需要 looong(23 秒或有时 50 秒),第二个、第三个等 运行 需要 <1 秒。执行计划相同,子树成本为 0.0671..
我注意到的区别是第一个执行计划中的 WaitStats
部分具有以下值:
WaitCount: 2751
WaitTimeMS: 6
WaitType: MEMORY_ALLOCATION_EXT
WaitCount: 2751
WaitTimeMS: 6
WaitType: IO_QUEUE_LIMIT
WaitCount: 669
WaitTimeMS: 20360
WaitType: PAGEIOLATCH_SH
Azure SQL DTU 平均最大约为 5%。
Azure 支持说这可能是执行计划编译时间。我怀疑因为 clearing the Proc Cache 确实 而不是 在第一个 运行.
之后重新引入漫长的等待执行计划 "leafs" 是 Index Seek (NonClustedred)
、Key Lookup (Clustered)
和 RID Lookup (Heap)
。 RID Lookup
为 39%(0.0671)。返回一行(即 TOP 1
)。
3 个表在 CROSS APPLY
的查询中。最大的一个有 800 万行,包含一个 ~40KB VARBINARY
列(未在查询中的任何地方引用或返回)。
查询
DECLARE @p0 VARCHAR(50); SET @p0 = '<GUID1>'
SELECT TOP 1 p.Id, p.DateCreatedUtc, p.PreviousOwnerId
FROM (
-- last project save
SELECT ps.Id AS psId, p.*
FROM Projects p
CROSS APPLY (
SELECT TOP 1 *
FROM ProjectSaves
WHERE ProjectId = p.Id
ORDER BY LastModifiedUtc DESC
) AS ps
WHERE p.OwnerId = @p0
) p
CROSS APPLY (
SELECT TOP 1 *
FROM ProjectSavePhotos
WHERE ProjectSaveId = p.psId AND (name LIKE 'uploads%')
) ps
WHERE P.IsDeleted = 0 AND p.Id NOT IN ('<GUID2>')
ORDER BY p.DateCreatedUtc DESC
p.OwnerId
被索引并且 Azure 自动创建了另外两个索引:
- 在
OwnerId
和Id
和 - 在
IsDeleted
、OwnerId
和Id
ps.ProjectId
已编入索引并包含 LastModifiedUtc
psp.ProjectSaveId
已编入索引并包含 name
问题
如何诊断 20 多岁 PAGEIOLATCH_SH
的根本原因?可能仅仅是 VARBINARY
列的存在吗?如果是这样,我如何确认?
参考阅读
https://www.sqlshack.com/handling-excessive-sql-server-pageiolatch_sh-wait-types/
https://sqlperformance.com/2014/06/io-subsystem/knee-jerk-waits-pageiolatch-sh
除非我遗漏了什么这听起来很正常,但我必须查看执行计划。您可能想要摆脱那个 Key Lookup。 Key Lookup 是否抓取了 Index Seek 中使用的索引中缺少的列?如果是,将它添加到 Index Seek 中的索引中,看看会发生什么。如此大 table 的交叉应用会占用大量缓冲区 space,具体取决于它的宽度,但如果没有看到查询,我不能说是否有更好的方法来获取您的数据。 这也可能有帮助:
将集群 PK 添加到 ProjectSaves 也会产生很大的不同。
您看到的是 SQL Azure 数据库在一段时间未使用数据库或数据库层已扩展后收缩内存分配的效果。行为与您提到的完全一样,第一次执行或前几次执行 运行 性能不佳,直到内存分配恢复正常。您不会在持续使用的数据库上看到它。
这种内存分配行为会造成您在第一次执行查询时看到的那些等待,而您在 Microsoft SQL 服务器上看不到这种行为。对于这些事情,我通常会说 Azure SQL 数据库和 SQL 服务器不一样,它们在很多方面都不同。
任何类型的数据都作为页面存储在 SQL 服务器中。根据页面包含的内容(数据、索引等),有不同类型的页面。参见 documentation page。
The fundamental unit of data storage in SQL Server is the page. The disk space allocated to a data file (.mdf or .ndf) in a database is logically divided into pages numbered contiguously from 0 to n. Disk I/O operations are performed at the page level. That is, SQL Server reads or writes whole data pages.
当您运行 对您的数据库进行查询时,SQL 引擎将在系统内存(缓冲区)中查找它是否具有执行查询所需的所有页面。如果缺少某些页面,SQL 引擎会将它们从磁盘加载到内存中。
PAGEIOLATCH_SH
等待对应于页面从磁盘拉到内存(缓冲区)。一旦页面被加载到系统内存中,它们就会一直保留在那里直到被驱逐。这就是为什么您的查询的第一个 运行 比随后的 运行 花费更多时间的原因。在第一个 运行 期间,SQL 引擎需要从磁盘检索数据。后续运行不再如此
为了减少第一次查询等待,有不同的策略。正如 Alberto 所提到的,如果您经常 运行 此查询,那么页面就不太可能被从缓冲区中逐出。如 influent 所述,您可以重写查询或创建新索引,这样 SQL 引擎就不必加载那么多页面。请post进一步调查查询计划。