一个用户对sys.schemas和sys.synonyms的查询运行非常慢


8

方案: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

...返回约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]

我可以通过将上述SQL封装在一个Execute as...Revert块中来在SSMS中复制此代码,因此.NET代码不在图片之内。

执行计划看起来相同。我对XML进行了差异化处理,只有微小的差异(CompileTime,CompileCPU,CompileMemory)。

IO统计信息均未显示任何物理读取:

表'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分钟的查询)为:

+ --------------------- + ------------ + -------------- -------- + ------------------------------ + ---------- ------------------- +
| 等待类型 等待计数| 总等待时间(毫秒)| 总资源等待时间(毫秒)| 总信号等待时间(毫秒)|
+ --------------------- + ------------ + -------------- -------- + ------------------------------- + --------- -------------------- +
| SOS_SCHEDULER_YIELD | 37300 | 427 | 20 | 407 |
| NETWORK_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

然后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

我相信{2}是在该架构中创建的同义词列表。

查询配置文件进入查询的两点:

在此处输入图片说明

在此处输入图片说明

我已经和微软开了张票。

另外,我们尝试将UserB添加到中db_owner,然后获取DENY与关联的所有已知特权db_owner。结果是快速查询。我们要么错过了某件事(完全有可能),要么对该db_owner角色进行了特殊检查。

Answers:


5

您可能希望按如下方式重新编写查询(我使用dbo而不是XXXX在测试数据库上找到某些同义词)。这类似于您发现更有效的重写,但是避免了声明变量和使用两个查询的需要。

SELECT name, base_object_name 
FROM sys.synonyms 
WHERE schema_id = SCHEMA_ID(N'dbo')
ORDER BY name

这将产生一个如下的计划:

在此处输入图片说明

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

如果使用的帐户不是db_owner,则此查询大约需要八分钟!运行实际计划并使用我编写的p_queryProgress过程帮助sys.dm_exec_query_profiles更轻松地解析输出,我们可以看到几乎所有处理时间都花在了Filter执行has_access()检查的运算符上:

在此处输入图片说明


TokenAndPermUserStore问题?在这种情况下,此知识库文章可能会有助于support.microsoft.com/en-gb/kb/955644
Martin Smith

@MartinSmith非常有趣,我之前并不了解access check cache bucket countaccess check cache quota配置选项。将不得不与那些玩。
Geoff Patterson

我不确定此缓存是否与这里的情况有关。我只记得它过去曾引起问题。
马丁·史密斯

1
@MartinSmith虽然这些设置没有影响,但是缓存中发生了一些有趣的事情。似乎缓存的存在是有害的。例如,如果我WHILE(1=1) BEGIN DBCC FREESYSTEMCACHE ('TokenAndPermUserStore') WAITFOR DELAY '00:00:05' END永远循环运行,查询将在2分钟内完成,而通常是8分钟。
Geoff Patterson

1
谢谢大家-Microsoft的回复与上述评论相呼应,而重写查询是最好的解决方案。事实证明,has_access()在开始时就有短路来测试db_owner或sysadmin,这导致了巨大的时差。
詹姆斯

0

如果这仍然有效-我们遇到了同样的问题-看来,如果您是dbo还是sysadmin,那么对sys.objects(或类似的东西)的任何访问权限-都是即时的,无需检查单个对象。

如果它是一个较低的db_datareader,那么它必须依次检查每个对象...它隐藏在查询计划中,因为它们的行为更像是函数,而不是视图/表

计划看起来是一样的,但是背后却做了不同的事情


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.