Получение зависимостей базы данных SQL Server Cross
Версия SQL Server - 2008 R2
Я работаю над оценкой решения DMS с целью получения обслуживания. Исходное решение имеет одну центральную базу данных, имеющую данные, относящиеся к производителю. Он также имеет одну базу данных для каждого дилера, что означает, что существует множество зависимостей между базами данных.
Проблемы:
- Документация без DB
- Нет комментариев коментария
- Много кучи
- Нет стандартных соглашений об именах объектов
- Центральный БД имеет 460+ таблиц и 900+ SProcs, в дополнение к другим
объекты
- Каждая база данных дилера имеет 370+ таблиц и 2350+ SProcs, в дополнение к другим
объекты
В качестве первого шага я рекомендую полную очистку БД, для которой важно понимать зависимости объектов, в том числе кросс-зависимые базы данных. Я попытался использовать решение Red Gate, но выход слишком объемный. Все, что я хочу, это список объектов в базах данных, которые не имеют каких-либо зависимостей - они не зависят от других объектов и не зависят от каких-либо объектов.
Вот script Я использовал для получения списка зависимостей:
SELECT
DB_NAME() referencing_database_name,
OBJECT_NAME (referencing_id) referencing_entity_name,
ISNULL(referenced_schema_name,'dbo') referenced_schema_name,
referenced_entity_name,
ao.type_desc referenced_entity_type,
ISNULL(referenced_database_name,DB_NAME()) referenced_database_name
FROM sys.sql_expression_dependencies sed
JOIN sys.all_objects ao
ON sed.referenced_entity_name = ao.name
Я создам таблицу - Dependencies - в которую я буду вставлять этот результирующий набор из каждого БД. В качестве следующего шага я также создам другую таблицу - AllObjects - которая будет содержать список всех объектов в Базах данных. Для этого выполните script:
SELECT
DB_NAME() DBName,
name,
type_desc
FROM sys.all_objects
WHERE type_desc IN
(
'VIEW',
'SQL_TABLE_VALUED_FUNCTION',
'SQL_STORED_PROCEDURE',
'SQL_INLINE_TABLE_VALUED_FUNCTION',
'USER_TABLE',
'SQL_SCALAR_FUNCTION'
)
Теперь список имен из этой таблицы, которые не отображаются в столбце referenced_entity_name в таблице зависимостей, должен содержать список объектов, которые я ищу.
SELECT
AO.DBName,
AO.name,
AO.type_desc
FROM AllObjects AO
LEFT OUTER JOIN Dependencies D ON
D.referenced_database_name = AO.DBName AND
D.referenced_entity_name = AO.name AND
D.referenced_entity_type = AO.type_desc
WHERE
D.referenced_database_name IS NULL AND
D.referenced_entity_name IS NULL AND
D.referenced_entity_type IS NULL
Теперь вопросы:
- На выходе отсутствуют некоторые зависимостей объектов. Что я
отсутствует?
- Как подтвердить, что мои выводы верны?
- Я имею в виду, есть ли другой способ сделать это, поэтому я могу сравнить
результаты и двойная проверка?
Спасибо заранее,
Раджа
Ответы
Ответ 1
Вы можете сравнить свои результаты с теми, которые найдет следующий скрипт. Вот полная статья
CREATE PROCEDURE [dbo].[get_crossdatabase_dependencies] AS
SET NOCOUNT ON;
CREATE TABLE #databases(
database_id int,
database_name sysname
);
INSERT INTO #databases(database_id, database_name)
SELECT database_id, [name]
FROM sys.databases
WHERE 1 = 1
AND [state] <> 6 /* ignore offline DBs */
AND database_id > 4; /* ignore system DBs */
DECLARE
@database_id int,
@database_name sysname,
@sql varchar(max);
CREATE TABLE #dependencies(
referencing_database varchar(max),
referencing_schema varchar(max),
referencing_object_name varchar(max),
referenced_server varchar(max),
referenced_database varchar(max),
referenced_schema varchar(max),
referenced_object_name varchar(max)
);
WHILE (SELECT COUNT(*) FROM #databases) > 0 BEGIN
SELECT TOP 1 @database_id = database_id,
@database_name = database_name
FROM #databases;
SET @sql = 'INSERT INTO #dependencies select
DB_NAME(' + convert(varchar,@database_id) + '),
OBJECT_SCHEMA_NAME(referencing_id,'
+ convert(varchar,@database_id) +'),
OBJECT_NAME(referencing_id,' + convert(varchar,@database_id) + '),
referenced_server_name,
ISNULL(referenced_database_name, db_name('
+ convert(varchar,@database_id) + ')),
referenced_schema_name,
referenced_entity_name
FROM ' + quotename(@database_name) + '.sys.sql_expression_dependencies';
EXEC(@sql);
DELETE FROM #databases WHERE database_id = @database_id;
END;
SET NOCOUNT OFF;
SELECT * FROM #dependencies;
Ответ 2
О, MS приложила немало усилий для обнаружения зависимостей между базами данных с sys.sql_expression_dependencies, но я видел, как это раньше не хватало. В вашем случае я бы нашел пример отсутствующей зависимости и начал backtracking: вы отбросили его из своего запроса каким-то образом? Если это так, исправьте свой запрос. Зависит ли sys.sql_expression_dependencies определенный класс зависимостей? При каких условиях? Является ли динамический SQL виновником? и др.
Вы также должны запустить sp_refreshsqlmodule
для каждого объекта в sys.sql_modules, а затем повторно запустить свой код. Это заставляет SQL Server обновлять информацию о зависимостях (насколько это возможно).
Теперь, для проверки, настройте трассировку и прослушайте событие 114 "Событие доступа к объекту схемы аудита", а также начальные и завершенные события для хранимых процедур и/или вызовов RPC. Включить столбцы DatabaseName
, ParentName
, ObjectName
, ServerName
, SPID
и RequestID
(для подключений с поддержкой MARS). Возможно, некоторые другие тоже. "Событие доступа к объекту аудиторской проверки" происходит в любое время, когда к объекту обращаются, поэтому используйте приложение во время выполнения этой трассировки, затем сопоставляйте данные с помощью SPID + RequestId и сравнивайте его с результатами, используя sys.sql_expression_dependencies. Если в данных трассировки нет данных, которые не отображаются в ваших данных зависимостей, то вы что-то пропустили.
Ответ 3
Если вам приходится иметь дело со связанными серверами, я адаптировал ответ @MilicaMedic для работы с межсерверными зависимостями. Я также выводю имена столбцов, где они доступны в зависимости.
Вы можете использовать это так:
create table #dependencies (
referencing_server nvarchar(128),
referencing_database nvarchar(128),
referencing_schema nvarchar(128),
referencing_object_name nvarchar(128),
referencing_column nvarchar(128),
referenced_server nvarchar(128),
referenced_database nvarchar(128),
referenced_schema nvarchar(128),
referenced_object_name nvarchar(128),
referenced_column nvarchar(128)
);
insert @dependencies
exec crossServerDependencies
'ThisServerName, LinkedServerName, LinkedServerName2, etc'
Оттуда вы присоединяете его к своей таблице AllObjects, как вы описали в своем ответе.
Мой код требует две внешние функции: "splitString" и "AddBracketsWhenNeeded". Вы можете упростить первое и полностью исключить второе по своему желанию. Но я использую их для других вещей, чтобы они вошли в мою реализацию. Код для обоих находится внизу.
Вот основная процедура:
create procedure crossServerDependencies
@server_names_csv nvarchar(500) = null -- csv list of server names you want to pull dependencies for
as
-- Create output table
if object_id('tempdb..#dependencies') is not null
drop table #dependencies;
create table #dependencies (
referencing_server nvarchar(128),
referencing_database nvarchar(128),
referencing_schema nvarchar(128),
referencing_object_name nvarchar(128),
referencing_column nvarchar(128),
referenced_server nvarchar(128),
referenced_database nvarchar(128),
referenced_schema nvarchar(128),
referenced_object_name nvarchar(128),
referenced_column nvarchar(128)
);
-- Split server csv into table
set @server_names_csv = isnull(@server_names_csv, @@servername);
declare @server_names table (
server_row int,
server_name nvarchar(128),
actuallyExists bit
);
insert @server_names
select server_row = id,
server_name,
actuallyExists = case when sv.name is not null then 1 else 0 end
from dbo.splitString(@server_names_csv, ',') sp
cross apply (select server_name = dbo.AddBracketsWhenNecessary(val)) ap
left join sys.servers sv on sp.val = dbo.AddBracketsWhenNecessary(sv.name);
-- Loop servers
declare
@server_row int = 0,
@server_name nvarchar(50),
@server_exists bit = 0,
@server_is_local bit = 0,
@server_had_some_inserts bit = 0;
while @server_row <= (select max(server_row) from @server_names)
begin
-- Server loop initializations
set @server_row += 1;
set @server_had_some_inserts = 0;
select @server_name = server_name,
@server_exists = actuallyExists
from @server_names
where server_row = @server_row;
set @server_is_local =
case when @server_name = dbo.AddBracketsWhenNecessary(@@servername) then 1 else 0 end;
-- Handle non-existent server (and prevent sql injection)
if @server_exists = 0
begin
print
'"' + @server_name + '" does not exist. ' +
'Please check your spelling and/or access to view the linked server ' +
'(running under ' + user_name() + ').';
continue;
end
-- Get database list
if object_id('tempdb..#databases') is not null
drop table #databases;
create table #databases (
rownum int identity(1,1),
database_id int,
database_name nvarchar(128)
);
declare @sql nvarchar(max) = '
select database_id, [name]
from master.sys.databases
where state <> 6 -- ignore offline dbs
and database_id > 4 -- ignore system dbs
and has_dbaccess([name]) = 1
and [name] not in (''ReportServer'', ''ReportServerTempDB'')
';
if @server_is_local = 0
begin
set @sql = replace(@sql, '''', '''''');
set @sql = 'select * from openquery( @server_name, ''' + @sql + ''')';
end
set @sql = 'insert #databases (database_id, database_name)' + @sql;
set @sql = replace(@sql, '@server_name', @server_name);
exec (@sql);
delete #databases
where database_name = 'ReportServer';
-- Loop databases
declare @rowNum int = 0;
while @rowNum <= (select max(rownum) from #databases)
begin
-- Database loop initializations
set @rowNum += 1;
declare
@database_id nvarchar(max),
@database_name nvarchar(max);
select @database_id = database_id,
@database_name = dbo.AddBracketsWhenNecessary(database_name)
from #databases
where rownum = @rowNum;
-- Get object dependency info
set @sql = '
with
getTableColumnIds as (
select table_id = o.object_id,
table_name = o.name,
column_id = c.column_id,
column_name = c.name
from @database_name.sys.objects o
join @database_name.sys.all_columns c on o.object_id = c.object_id
)
@insertStatement
select ''@server_name'',
db_name(@database_id),
object_schema_name(referencing_id, @database_id),
object_name(referencing_id, @database_id),
referencing_column = ringTCs.column_name,
isnull(referenced_server_name, ''@server_name''),
isnull(referenced_database_name, db_name(@database_id)),
isnull(referenced_schema_name, ''dbo''),
referenced_entity_name,
referenced_column = redTCs.column_name
from @database_name.sys.sql_expression_dependencies d
left join getTableColumnIds ringTCs
on d.referencing_id = ringTCs.table_id
and d.referencing_minor_id = ringTCs.column_id
left join getTableColumnIds redTCs
on d.referenced_id = redTCs.table_id
and d.referenced_minor_id = redTCs.column_id
';
set @sql = replace(@sql, '@database_id', @database_id);
set @sql = replace(@sql, '@database_name', @database_name);
if @server_is_local = 0
begin
set @sql = replace(@sql, '''', '''''');
set @sql = replace(@sql, '@insertStatement', '');
set @sql = 'select * from openquery(@server_name, ''' + @sql + ''')';
end
set @sql = replace(@sql, '@insertStatement', 'insert #dependencies ');
set @sql = replace(@sql, '@server_name', @server_name);
exec (@sql);
-- Database loop terminations
if @@rowcount > 0
set @server_had_some_inserts = 1;
end -- database loop
-- server loop terminations
if @server_had_some_inserts = 0
begin
declare @remote_user_name nvarchar(255);
select @remote_user_name = remote_name
from sys.linked_logins li
join sys.servers s on li.server_id = s.server_id
where remote_name is not null
and s.name = 'sisag'
print (
'No dependencies found for ' + @server_name + '. ' +
'If this is unexpected, you may need to run "grant view any definition to ' +
'[' + isnull(@remote_user_name, '?') + ']" ' +
'on the remote server.'
);
end
end -- server loop
-- Terminate
select * from #dependencies
Код для AddBracketsWeNeeded:
create function AddBracketsWhenNecessary (
@objectName nvarchar(250)
)
returns nvarchar(250) as
begin
if left(@objectName, 1) = '[' and right(@objectName, 1) = ']'
return @objectName;
declare @hasInvalidCharacter bit;
select @hasInvalidCharacter = max(isInvalid)
from dbo.splitString(@objectName, null) chars
cross apply (select
isLetter = patindex('[a-z,_]', val),
isNumber = PATINDEX('[0-9]', val)
) getCharType
cross apply (select
isInvalid =
case
when isLetter = 1 then 0
when isNumber = 1 and not chars.id = 1 then 0
else 1
end
) getValidity
return
case when @hasInvalidCharacter = 1 then '[' else '' end
+ @objectName
+ case when @hasInvalidCharacter = 1 then ']' else '' end;
end
Наконец, моя функция сплиттера (но смотрите Арнольд Фриббл здесь, если вы хотите более простую версию, или используйте встроенную функцию, если у вас SqlServer 2016 или выше):
create function splitString (
@stringToSplit nvarchar(max),
@delimiter nvarchar(50)
)
returns table as
return
with
split_by_delimiter as (
select id = 1,
start = 1,
stop = convert(int,
charindex(@delimiter, @stringToSplit)
)
union all
select id = id + 1,
start = newStart,
stop = convert(int,
charindex(@delimiter, @stringToSplit, newStart)
)
from split_by_delimiter
cross apply (select newStart = stop + len(@delimiter)) ap
where Stop > 0
),
split_into_characters as (
select id = 1,
chr = left(@stringToSplit,1)
union all
select id = id + 1,
chr = substring(@stringToSplit, ID + 1, 1)
from split_into_characters
where id < len(@stringToSplit)
)
select id,
val =
ltrim(rtrim(substring(
@stringToSplit,
start,
case
when stop > 0 then stop - start
else len(@stringtosplit)
end
)))
from split_by_delimiter
where len(@delimiter) > 0
union all
select id,
val = chr
from split_into_characters
where @delimiter = ''
or @delimiter is null
Мне пришлось внести небольшие изменения в реальный код, который я использую, поэтому, если будут какие-либо ссылочные ошибки, пожалуйста, дайте мне знать в комментариях, и я буду редактировать.