Jam*_*mes 8 performance sql-server sql-server-2014 query-performance
场景:SQL Server 2014 (v12.0.4100.1)
.NET 服务运行此查询:
SELECT name, base_object_name
FROM sys.synonyms
WHERE schema_id IN (SELECT schema_id
FROM sys.schemas
WHERE name = N'XXXX')
ORDER BY name
Run Code Online (Sandbox Code Playgroud)
...返回大约 6500 行,但通常在 3+ 分钟后超时。的XXXX
上面是不“DBO”。
如果我以 UserA 的身份在 SSMS 中运行此查询,该查询将在不到一秒的时间内返回。
当以 UserB 运行(这是 .NET 服务连接的方式)时,查询需要 3-6分钟,并且 CPU%始终为 25%(4 核)。
UserA 是 sysadmin 角色中的域登录名。
UserB 是一个 SQL 登录,具有:
EXEC sp_addrolemember N'db_datareader', N'UserB'
EXEC sp_addrolemember N'db_datawriter', N'UserB'
EXEC sp_addrolemember N'db_ddladmin', N'UserB'
GRANT EXECUTE TO [UserB]
GRANT CREATE SCHEMA TO [UserB]
GRANT VIEW DEFINITION TO [UserB]
Run Code Online (Sandbox Code Playgroud)
我可以通过将上面的 SQL 包装在一个Execute as...Revert
块中来在 SSMS 中复制它,因此 .NET 代码不在图片中。
执行计划看起来是一样的。我对 XML 进行了比较,只有很小的差异(CompileTime、CompileCPU、CompileMemory)。
IO Stats 都显示没有物理读取:
表'sysobjvalues'。扫描计数 0,逻辑读取 19970,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表“工作文件”。扫描计数 0,逻辑读 0,物理读 0,预读 0,lob 逻辑读 0,lob 物理读 0,lob 预读 0。 表“工作台”。扫描计数 0,逻辑读 0,物理读 0,预读 0,lob 逻辑读 0,lob 物理读 0,lob 预读 0。 表'sysschobjs'。扫描计数1,逻辑读9122,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。 表'sysclsobjs'。扫描计数 0,逻辑读 2,物理读 0,预读 0,lob 逻辑读 0,lob 物理读 0,lob 预读 0。
XEvent 等待状态(大约 3 分钟的查询)是:
+---------------------+------------+-------------- --------+------------------------------+--------- -------------------+ | 等待类型 | 等待计数 | 总等待时间(毫秒)| 总资源等待时间 (ms) | 总信号等待时间 (ms) | +---------------------+------------+-------------- --------+----------------------------------------------+--------- ---------------------+ | SOS_SCHEDULER_YIELD | 37300 | 第427话 20 | 407 | | 网络_IO | 5 | 26 | 26 | 0 | | IO_COMPLETION | 3 | 1 | 1 | 0 | +---------------------+------------+-------------- --------+----------------------------------------------+--------- ---------------------+
如果我将查询(在 SSMS 中,我无权访问应用程序代码)重写为
declare @id int
SELECT @id=schema_id FROM sys.schemas WHERE name = N'XXXX'
SELECT a.name, base_object_name FROM sys.synonyms a
WHERE schema_id = @id
ORDER BY name
Run Code Online (Sandbox Code Playgroud)
然后 UserB 以与 UserA 相同(快速)的速度运行。
如果我添加db_owner
到 UserB,那么再次查询运行 < 1 秒。
通过此模板创建的架构:
DECLARE @TranName VARCHAR(20)
SELECT @TranName = 'MyTransaction'
BEGIN TRANSACTION @TranName
GO
IF NOT EXISTS (SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA
WHERE SCHEMA_NAME = '{1}')
BEGIN
EXEC('CREATE SCHEMA [{1}]')
EXEC sp_addextendedproperty @name='User', @value='{0}', @level0type=N'Schema', @level0name=N'{1}'
END
GO
{2}
COMMIT TRANSACTION MyTransaction;
GO
Run Code Online (Sandbox Code Playgroud)
我相信,{2} 是在该模式中创建的同义词列表。
Query Profile 在两个点进入查询:
我已经在微软开了一张票。
此外,我们尝试将 UserB 添加到db_owner
,然后DENY
将我们知道的所有与 相关联的权限添加到db_owner
。结果是快速查询。要么我们错过了某些东西(完全有可能),要么对该db_owner
角色进行了特殊检查。
您可能想按如下方式重写您的查询(我使用dbo
而不是XXXX
这样我确实在我的测试数据库中找到了一些同义词)。这类似于您发现更高效的重写,但避免了声明变量和使用两个查询的需要。
SELECT name, base_object_name
FROM sys.synonyms
WHERE schema_id = SCHEMA_ID(N'dbo')
ORDER BY name
Run Code Online (Sandbox Code Playgroud)
这产生了如下计划:
关于Filter
这个计划中的操作符的一个非常有趣的事情是它有一个执行内部has_access()
检查的谓词。此过滤器会删除当前帐户没有足够权限查看的所有对象。但是,如果您是该db_owner
角色的成员,则此检查是短路的(即完成得更快),这可以解释您所看到的性能差异。
这是原始查询的查询计划。请注意,数据库中的所有同义词(1,126
在我的情况下,但在您的情况下可能更多)通过非常昂贵的has_access()
过滤器,即使只有2
同义词与模式匹配。通过使用上面的简化查询,我们可以确保has_access()
只为与您的查询匹配的同义词而不是数据库中的所有同义词调用它。
使用 sys.dm_exec_query_profiles 进一步探索
正如 Martin 所建议的,我们可以通过sys.dm_exec_query_profiles
在 SQL Server 2014+ 上使用来确认 has_access() 检查是一个重要的瓶颈。如果我使用db_owner
具有 ~700K 对象的数据库上的帐户运行以下查询,则查询将采用~500ms
:
SELECT COUNT(*)
FROM sys.objects
Run Code Online (Sandbox Code Playgroud)
当使用不是 的帐户运行时db_owner
,同样的查询大约需要八分钟!运行实际计划并使用我编写的p_queryProgress过程来帮助sys.dm_exec_query_profiles
更轻松地解析输出,我们可以看到几乎所有的处理时间都花在了Filter
执行has_access()
检查的运算符上:
归档时间: |
|
查看次数: |
1909 次 |
最近记录: |