ошибка в database_scoped_configurations

9

Я пытаюсь вставить набор результатов из:

SELECT * FROM sys.database_scoped_configurations

во временную таблицу, потому что я хочу проверить настройки для всех баз данных на моем сервере. Итак, я написал этот код:

DROP TABLE IF EXISTS #h
CREATE TABLE #h(dbname sysname, configuration_id INT, name sysname,     value SQL_VARIANT,  value_for_secondary SQL_VARIANT)
EXEC sys.sp_MSforeachdb 'USE ?; insert into #h(dbname, configuration_id, name, value,value_for_secondary)  SELECT ''?'' as dbname, * FROM sys.database_scoped_configurations  D'
SELECT * FROM #h H

Но тогда в каждой базе данных будет только одна строка, а не четыре, которые я ожидаю от запуска простого выбора в каждой базе данных.

Я знаю, что есть лучшие способы кодирования, чем использование sp_MSForEachDB, и я пробовал несколько. Но я все еще получаю только одну строку на базу данных. Я пробовал это на SQL Server 2016 RTM и SP1

Это ошибка в SQL Server 2016 или я что-то не так делаю?

Хенрик Стаун Поулсен
источник
ошибка была исправлена, по крайней мере, в Microsoft SQL Server 2017 (RTM-CU15-GDR)
Хенрик Стаун Поулсен,

Ответы:

8

Это ошибка в SQL Server 2016?

Да. Определенно это не правильное поведение. Я сообщил об этом здесь и исправлен в SQL Server 2016 SP2 CU9 .

Как говорит Микаэль Эрикссон в комментариях sys.database_scoped_configurationsи sys.dm_exec_sessionsреализованы как представления в формате

SELECT ...  
FROM OpenRowset(TABLE xxxx)  

Однако, сравнивая два плана ниже, есть очевидная разница.

DBCC TRACEON(3604);

DECLARE @database_scoped_configurations TABLE(x INT);

INSERT INTO @database_scoped_configurations
SELECT configuration_id
FROM   sys.database_scoped_configurations
OPTION (QUERYTRACEON 8608, QUERYTRACEON 8615, QUERYTRACEON 8619, QUERYTRACEON 8620 );


DECLARE @dm_exec_sessions TABLE(x INT);

INSERT INTO @dm_exec_sessions
SELECT session_id
FROM   sys.dm_exec_sessions
OPTION (QUERYTRACEON 8608, QUERYTRACEON 8615, QUERYTRACEON 8619, QUERYTRACEON 8620 );

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

Вывод флага трассировки 8619 для обоих этих запросов показывает

Применить правило: EnforceHPandAccCard - x0-> Spool or Top (x0)

SQL Server, по-видимому, не может установить, что источник для TVF также не является целевым объектом вставки, поэтому он требует защиты на Хэллоуин.

В случае сессий это было реализовано как спул, который сначала захватывает все строки. В database_scoped_configurationsдобавив TOP 1к плану. Использование TOPдля защиты Хэллоуина обсуждается в этой статье . В статье также упоминается недокументированный флаг трассировки для принудительной установки катушки, а не TOPтот, который работает, как ожидалось.

DECLARE @database_scoped_configurations TABLE(x INT);

INSERT INTO @database_scoped_configurations
SELECT configuration_id
FROM   sys.database_scoped_configurations
OPTION (QUERYTRACEON 8692)

Очевидная проблема с использованием, TOP 1а не спула, заключается в том, что он будет произвольно ограничивать количество вставляемых строк. Так что это будет допустимо только в том случае, если число строк, возвращаемых функцией, было <= 1.

Начальная записка выглядит так

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

Сравните это с первоначальной запиской для запроса 2

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

Если я правильно понимаю вышесказанное, он думает, что первый TVF может вернуть максимум одну строку, и поэтому применяет неверную оптимизацию. Максимальное значение для второго запроса установлено в 1.34078E+154( 2^512).

Я понятия не имею, откуда это максимальное количество строк. Возможно, метаданные предоставлены автором DMV? Также странно, что TOP(50)обходной путь не переписывается, TOP(1)потому TOP(50)что не помешает возникновению проблемы Хэллоуина (хотя остановит его на неопределенный срок)

Мартин Смит
источник
6

Пожалуйста, прекратите использование sp_MSForEachDB. Это не поддерживается, недокументировано и содержит ошибки - что может быть проблемой здесь. Моя замена демонстрирует ту же проблему здесь, но в целом это безопаснее.

Для таких вещей я предпочитаю генерировать динамический SQL, чем передавать одну команду процедуре, которая должна выполняться несколько раз (даже моей процедуре, которой я доверяю намного больше), таким образом, я могу просто напечатать команды вместо их выполнения, и убедитесь, что они все будут делать то, что говорят.

Исходя из наблюдения, что код, лежащий в основе системного представления, реализует TOP (1), мы можем попробовать это следующим образом:

DROP TABLE IF EXISTS #h;

CREATE TABLE #h(dbname sysname, configuration_id INT, name sysname, 
  value SQL_VARIANT,  value_for_secondary SQL_VARIANT);

DECLARE @sql nvarchar(max) = N'', @base nvarchar(max) = N'insert into #h
  (dbname, configuration_id, name, value,value_for_secondary)  SELECT TOP ($c$) 
  $db$ as dbname, * FROM $qdb$.sys.database_scoped_configurations;';

SELECT @sql += REPLACE(REPLACE(REPLACE(@base, N'$qdb$', QUOTENAME(name)), 
  N'$db$', CHAR(39) + name + CHAR(39)), N'$c$', RTRIM(COUNT(*) OVER()))
FROM sys.databases WHERE state = 0;

PRINT @sql;
EXEC sys.sp_executesql @sql;
SELECT * FROM #h;

Обратите внимание, что я не использую USEздесь, а вместо этого префикс представления sysкаталога с именем базы данных.

Почему представление работает волшебным образом, я не знаю; Я не знаю, что вы получите хороший ответ здесь, поскольку для этого, вероятно, требуются комментарии от Microsoft (или любого, кто имеет доступ к исходному коду или желает запустить отладчик).

Аарон Бертран
источник
это был первый из нескольких методов, которые я попробовал, но я не думал, что смогу использовать этот метод в примере.
Хенрик Стаун Поульсен
6

Спасибо за сообщение об этой проблеме!

Это действительно ошибка в том, как Оптимизатор запросов генерирует план для представления sys.database_scoped_configurationsкаталога. Мы рассмотрим это в одном из следующих обновлений SQL Server 2016 и в базе данных SQL Azure.

В качестве обходного пути вы можете добавить TOPпункт со SELECTсвоей вставки, чтобы получить правильный план, например:

DECLARE @database_scoped_configurations TABLE(x INT); 
INSERT INTO @database_scoped_configurations 
SELECT **TOP 100** configuration_id 
FROM sys.database_scoped_configurations 
Панагиотис Антонопулос
источник
3

Я согласен, что это очень странная и потенциальная ошибка, но добавление TOP (50), например, к вашему select действительно возвращает все строки, так что это, по крайней мере, поможет вам. Похоже, что результат исходит от системной функции табличных значений ([DB_SCOPED_CONFIG]), поэтому я не могу точно сказать, что происходит.

Я буду следить за этой веткой, чтобы узнать, знают ли «умные» люди, ПОЧЕМУ это происходит.

Скотт Ходжин
источник
Вы получаете только строку MAXDOP для каждой базы данных?
Дан Гусман
@DanGuzman - да. Сам по себе выбор отлично работает (без всякого foreach-компонента, только для одной базы данных). Это когда вы добавляете
Вставку