Запрос к sys.schemas и sys.synonyms выполняется очень медленно для одного пользователя

8

Сценарий: SQL Server 2014 (v12.0.4100.1)

.NET Service выполняет этот запрос:

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Выше не "ДБО.

Если я выполню этот запрос в SSMS как UserA, запрос вернется менее чем за секунду.

При запуске от имени пользователя B (именно так подключается служба .NET), запрос занимает 3–6 минут и все время занимает% ЦП, равный 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]

Я могу продублировать это в SSMS, обернув вышеприведенный SQL в Execute as...Revertблок, так что код .NET будет за кадром.

План выполнения выглядит так же. Я проанализировал XML и есть только незначительные различия (CompileTime, CompileCPU, CompileMemory).

IO Stats все показывают отсутствие физических чтений:

Таблица «sysobjvalues». Сканирование счетчик 0, логическое чтение 19970, физическое чтение 0, чтение с опережением 0, логическое чтение с 0, физическое чтение с 0, чтение с опережением 0.
Таблица «Рабочий файл». Сканирование счетчик 0, логическое чтение 0, физическое чтение 0, чтение с опережением 0, логическое чтение с бита 0, физическое чтение с бита 0, чтение с опережением чтения 0.
Стол «Рабочий стол». Сканирование счетчик 0, логическое чтение 0, физическое чтение 0, чтение с опережением 0, логическое чтение с бита 0, физическое чтение с бита 0, чтение с опережением чтения 0.
Таблица 'sysschobjs'. Сканирование 1, логическое чтение 9122, физическое чтение 0, чтение с опережением 0, логическое чтение 1, физическое чтение 1, чтение с опережением 0.
Таблица 'sysclsobjs'. Сканирование счетчик 0, логическое чтение 2, физическое чтение 0, чтение с опережением 0, логическое чтение с бита 0, физическое чтение с бита 0, чтение с опережением чтения 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}, я полагаю, это список синонимов, созданных в этой схеме.

Профиль запроса в двух точках запроса:

введите описание изображения здесь

введите описание изображения здесь

Я открыл билет с Microsoft.

Кроме того, мы попытались добавить UserB в db_owner, а затем использовать DENYвсе известные нам привилегии, связанные с db_owner. Результатом является быстрый запрос. Либо мы что-то пропустили (вполне возможно), либо есть специальная проверка на db_ownerроль.

Джеймс
источник

Ответы:

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 для дальнейшего изучения

Как предполагает Мартин, мы можем подтвердить, что проверка has_access () является существенным узким местом с помощью sys.dm_exec_query_profilesSQL Server 2014+. Если я db_ownerвыполню следующий запрос, используя учетную запись в базе данных с ~ 700К объектов, запрос займет ~500ms:

SELECT COUNT(*)
FROM sys.objects

При запуске с учетной записью, которая не является db_owner, этот же запрос занимает около восьми минут! Работая с фактическим планом и используя процедуру p_queryProgress , которую я написал для облегчения анализа sys.dm_exec_query_profilesвывода, мы видим, что почти все время обработки тратится на Filterоператора, выполняющего has_access()проверку:

введите описание изображения здесь

Джефф Паттерсон
источник
TokenAndPermUserStore проблема? В этом случае эта статья базы знаний
Мартин Смит,
@MartinSmith Очень интересно, я не знал о параметрах конфигурации access check cache bucket countи access check cache quotaранее. Придется немного поиграться с этими.
Джефф Паттерсон
Я не уверен, что этот кеш имеет отношение к делу здесь. Я просто помню, как это вызывало проблемы в прошлом.
Мартин Смит
1
@MartinSmith Хотя эти настройки не оказали влияния, с кешем происходит кое-что интересное. Кажется, что существование кэша вредно. Например, если я буду работать WHILE(1=1) BEGIN DBCC FREESYSTEMCACHE ('TokenAndPermUserStore') WAITFOR DELAY '00:00:05' ENDв цикле навсегда, запрос будет завершен менее чем за 2 минуты против 8 минут обычно.
Джефф Паттерсон
1
Спасибо всем - ответ от Microsoft повторяет комментарии выше, и переписывание запросов - лучшее решение. Оказывается, что has_access () вначале имеет короткое замыкание для проверки db_owner или sysadmin, что привело к большой разнице во времени.
Джеймс
0

Если это все еще живо - у нас возникла та же проблема - похоже, что если вы либо dbo, либо системный администратор, то любой доступ к sys.objects (или что-то в этом роде) - тогда это происходит мгновенно, без проверок отдельных объектов.

если это скромный db_datareader, то он должен проверять каждый объект по очереди ... он скрыт в плане запросов, так как они ведут себя больше как функции, чем представления / таблицы

план выглядит так же, но он делает разные вещи за капотом

Майк
источник
-2

Использование флага трассировки 9481, кажется, решает эту проблему для меня.

CE (Cardinality Estimator) в 2014 году (Compat 120) изменился по сравнению с 2012 и 2008 годами, и использование TF 9481 заставляет использовать CE 2012 года.

См. «Оценщик количества элементов SQL 2014», «Кушает плохой TSQL на завтрак » Кендры Литтл.

Шмед
источник