Наш ИТ-отдел унаследовал абсолютный кошмар системы баз данных. У нас есть 5 серверов баз данных (3 из которых MSSQL 2008R2 и 2 - MSSQL 2016). Вместо того, чтобы быть многопользовательскими, они однопользовательские. Это означает, что у нас есть сотни шардов на 5 серверах (у нас есть более 300 шардов базы данных).
Для запроса ВСЕХ данных по всем сегментам у каждого сервера есть «родительская» база данных, состоящая из представлений, которые затем СОЕДИНЯЮТ ВСЕ данные из каждого сегмента на сервере в одно представление (мы называем их «Представления между серверами»). ). Затем на «материнском» сервере межсерверное представление использует связанные серверные соединения с 4 «дочерними» серверами для извлечения их данных.
По сути, это выглядит так:
Если мы хотим получить ВСЕ данные от клиентов, мы выполняем простой запрос SELECT * FROM CUSTOMERS для сервера базы данных «main» / «mother», где CUSTOMERS - это представление, которое получает данные из своих собственных сегментов, плюс другие 4 "дочерних" сервера. Каждый «дочерний» сервер БД также имеет представление «Клиенты», которое получает данные из своих собственных сегментов. Это «материнское» представление построено так:
--Its own shards, plus the rollup views from the child db servers
select * from shard1..Customers union all
select * from shard2..Customers union all
select * from shard3..Customers union all
--etc... union all
select * from DB2..Customers union all
select * from DB3..Customers union all
select * from DB4..Customers union all
select * from DB5..Customers
и каждое из «дочерних» представлений строится так:
--ONLY its own shards
select * from shard4..Customers union all
select * from shard5..Customers union all
--etc...
Всего существует более 300 осколков, поэтому эти представления намного больше, чем я описал выше. В основном, большой волосатый беспорядок !!!!
Из-за этой архитектуры у нас есть проблема в том, что если один осколок выйдет из строя, все они выйдут из строя (схемы ВСЕГДА будут идентичны, поэтому мы пропускаем эту проблему). Перед нами была поставлена задача переписать эти представления, чтобы они были «умными» в том смысле, что если шард или дочерний сервер недоступен, он пропустит их и вернет данные для найденных шардов / серверов.
Я думал, что у меня есть решение, которое работало нормально, пока я не добрался до двух последних серверов, которые являются серверами MSSQL 2016.
В двух словах, я создал хранимую процедуру, которая собирала имена баз данных через sys.databases, исключил базы данных, которые нам не нужны, и сгенерировал динамический SQL-оператор COALESCE'd, который затем выполнялся через sp_executesql (код ниже ):
SET FMTONLY OFF;
SET NOCOUNT ON;
DECLARE @sql NVARCHAR(MAX)
SELECT @sql = COALESCE(@sql + ' UNION ALL ', '') + 'SELECT * FROM ' + name + '.dbo.' + @table + ' WITH (NOLOCK)'
FROM sys.databases
WHERE state_desc = 'ONLINE'
AND name NOT IN ('master', 'model', 'msdb', 'tempdb', 'DBLOOKUP', 'MM_STATE_MT', 'kids', 'distribution', 'LiteSpeedLocal')
IF (SELECT @@SERVERNAME) = 'MOTHER'
BEGIN
DECLARE @available INT
BEGIN TRY
EXEC @available = sys.sp_testlinkedserver N'CHILD1'
SET @sql = @sql + ' UNION ALL SELECT * FROM [CHILD1].viewdb.dbo.' + @table + ' WITH (NOLOCK)'
END TRY
BEGIN CATCH
SET @available = 0
END CATCH
BEGIN TRY
EXEC @available = sys.sp_testlinkedserver N'CHILD2'
SET @sql = @sql + ' UNION ALL SELECT * FROM [CHILD2].viewdb.dbo.' + @table + ' WITH (NOLOCK)'
END TRY
BEGIN CATCH
SET @available = 0
END CATCH
BEGIN TRY
EXEC @available = sys.sp_testlinkedserver N'CHILD3'
SET @sql = @sql + ' UNION ALL SELECT * FROM [CHILD3].viewdb.dbo.' + @table + ' WITH (NOLOCK)'
END TRY
BEGIN CATCH
SET @available = 0
END CATCH
BEGIN TRY
EXEC @available = sys.sp_testlinkedserver N'CHILD4'
SET @sql = @sql + ' UNION ALL SELECT * FROM [CHILD4].viewdb.dbo.' + @table + ' WITH (NOLOCK)'
END TRY
BEGIN CATCH
SET @available = 0
END CATCH
END
EXEC sp_executesql @sql;
Затем в представлениях viewdb я использовал связанный сервер с самоссылкой и OPENQUERY для выполнения процедуры следующим образом (удалил имя хранимой процедуры и имя таблицы):
SELECT * FROM OPENQUERY(local, 'SET FMTONLY OFF; SET NOCOUNT ON; EXEC [stored procedure] ''[table_we_want_to_query]''')
Это отлично работало на серверах MSSQL 2008 из-за того, что FMTONLY OFF работает в этой редакции. Однако, начиная с MSSQL 2012, они изменили работу FMTONLY. Поэтому на серверах 2016 года, если мы попытаемся запросить представления, мы получим следующую ошибку:
The metadata could not be determined because statement 'EXEC sp_executesql @sql' in procedure '[stored procedure]' contains dynamic SQL. Consider using the WITH RESULT SETS clause to explicitly describe the result set.
Я пробовал С РЕЗУЛЬТАТАМИ РЕЗУЛЬТАТОВ, который не работает. Я не могу указать результирующий набор в хранимой процедуре, потому что он является общим (принимает параметр ввода имени таблицы, чтобы определить, какую таблицу запрашивать).
Я испробовал все способы динамического sql и переключения параметров, которые я могу придумать или найти через Google. Я мертв в воде с этим процессом на серверах 2016 года. У кого-нибудь есть идеи, как обойти этот или другой подход к решению проблемы? Имейте в виду, что изменение всей архитектуры базы данных в настоящее время недоступно. Мы должны работать с тем, что имеем сейчас.
Пожалуйста, дайте мне знать, если есть какая-либо дополнительная информация, которая может быть полезной. Спасибо!