patternsqlMinor
Query against sys.schemas and sys.synonyms runs very slow for one user
Viewed 0 times
synonymsuserqueryslowschemasagainstonesysforvery
Problem
Scenario: SQL Server 2014 (v12.0.4100.1)
.NET Service runs this query:
...which returns about 6500 rows but it often times out after 3+ minutes. The
If I run this query in SSMS as UserA, the query returns in less than a second.
When run as UserB (which is how the .NET service connects), the query takes 3-6 minutes, and has the CPU% at 25% (of a 4 cores) the entire time.
UserA is a Domain Login in the sysadmin role.
UserB is a SQL Login with:
I can duplicate this in SSMS by wrapping the above SQL in an
The execution plan looks the same. I diff'ed the XML and there are only minor differences (CompileTime, CompileCPU, CompileMemory).
IO Stats all show no physical reads:
Table 'sysobjvalues'. Scan count 0, logical reads 19970, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'sysschobjs'. Scan count 1, logical reads 9122, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'sysclsobjs'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0
.NET Service runs this query:
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...which returns about 6500 rows but it often times out after 3+ minutes. The
XXXX above is not 'dbo'.If I run this query in SSMS as UserA, the query returns in less than a second.
When run as UserB (which is how the .NET service connects), the query takes 3-6 minutes, and has the CPU% at 25% (of a 4 cores) the entire time.
UserA is a Domain Login in the sysadmin role.
UserB is a SQL Login with:
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]I can duplicate this in SSMS by wrapping the above SQL in an
Execute as...Revert block, so the .NET code is out of the picture.The execution plan looks the same. I diff'ed the XML and there are only minor differences (CompileTime, CompileCPU, CompileMemory).
IO Stats all show no physical reads:
Table 'sysobjvalues'. Scan count 0, logical reads 19970, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'sysschobjs'. Scan count 1, logical reads 9122, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'sysclsobjs'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0
Solution
You may want to re-write your query as follows (I'm using
This yields a plan like the following:
One very interesting thing about the
Here is the query plan for your original query. Notice that all synonyms on the database (
Using sys.dm_exec_query_profiles to explore further
As Martin suggests, we can confirm that the has_access() check is a significant bottleneck by using
When run with an account that is not a
dbo rather than XXXX so that I do find some synonyms on my testing database). This is similar to the re-write you found to be more efficient, but avoids the need to declare a variable and use two queries.SELECT name, base_object_name
FROM sys.synonyms
WHERE schema_id = SCHEMA_ID(N'dbo')
ORDER BY nameThis yields a plan like the following:
One very interesting thing about the
Filter operator in this plan is that it has a predicate that performs an internal has_access() check. This filter removes any objects that the current account does not have sufficient permissions to see. However, this check is short-circuited (i.e., completes much more quickly) if you are a member of the db_owner role, which may explain the performance differences you are seeing.Here is the query plan for your original query. Notice that all synonyms on the database (
1,126 in my case, but likely many more in your case) pass through the very expensive has_access() filter, even though only 2 synonyms match the schema. By using the simplified query above, we can ensure that has_access() is only invoked for the synonyms that match your query rather than for all synonyms in the database.Using sys.dm_exec_query_profiles to explore further
As Martin suggests, we can confirm that the has_access() check is a significant bottleneck by using
sys.dm_exec_query_profiles on SQL Server 2014+. If I run the following query using a db_owner account on a database with ~700K objects, the query takes ~500ms:SELECT COUNT(*)
FROM sys.objectsWhen run with an account that is not a
db_owner, this same query takes about eight minutes! Running with actual plan on and using a p_queryProgress procedure that I wrote to help parse sys.dm_exec_query_profiles output more easily, we can see that almost all of the processing time is spent on the Filter operator that is performing the has_access() check:Code Snippets
SELECT name, base_object_name
FROM sys.synonyms
WHERE schema_id = SCHEMA_ID(N'dbo')
ORDER BY nameSELECT COUNT(*)
FROM sys.objectsContext
StackExchange Database Administrators Q#135201, answer score: 7
Revisions (0)
No revisions yet.