Восстановить базу данных, исключая данные FILESTREAM

20

Контекст
Мы разрабатываем систему с большой базой данных внизу. Это база данных MS SQL, работающая на SQL Server 2008 R2. Общий размер базы данных составляет около 12 ГБ.

Из них примерно 8,5 ГБ находятся в одной таблице BinaryContent. Как следует из названия, это таблица, в которой мы храним простые файлы любого типа непосредственно в таблице как BLOB. Недавно мы тестировали возможность перемещения всех этих файлов из базы данных в файловую систему с помощью FILESTREAM.

Мы внесли необходимые изменения в нашу базу данных без каких-либо проблем, и наша система все еще работает нормально после миграции. BinaryContentТаблица выглядит примерно так:

CREATE TABLE [dbo].[BinaryContent](
    [BinaryContentID] [int] IDENTITY(1,1) NOT NULL,
    [FileName] [varchar](50) NOT NULL,
    [BinaryContentRowGUID] [uniqueidentifier] ROWGUIDCOL  NOT NULL
) ON [PRIMARY] FILESTREAM_ON [FileStreamContentFG]
ALTER TABLE [dbo].[BinaryContent] ADD [FileContentBinary] [varbinary](max) FILESTREAM  NULL
ALTER TABLE [dbo].[BinaryContent] ADD  CONSTRAINT [DFBinaryContentRowGUID]  DEFAULT (newsequentialid()) FOR [BinaryContentRowGUID]

Со всем, что находится в PRIMARYфайловой группе, кроме поля, FileBinaryContentкоторое находится в отдельной файловой группе FileStreamContentFG.

Сценарий
С точки зрения разработчика, мы часто хотели бы иметь свежую копию базы данных из нашей производственной среды, чтобы иметь возможность обрабатывать самые последние данные. В этих случаях мы редко интересуемся файлами, хранящимися в BinaryContent (теперь использующем FILESTREAM).

У нас это почти работает, как мы хотели. Мы создаем резервную копию базы данных без потока файлов, например:

BACKUP DATABASE FileStreamDB
FILEGROUP = 'PRIMARY' 
TO DISK = 'c:\backup\FileStreamDB_WithoutFS.bak' WITH INIT

И восстановить это так:

RESTORE DATABASE FileStreamDB
FROM DISK = 'c:\backup\FileStreamDB_WithoutFS.bak'

Кажется, это работает нормально, и наша система работает, пока мы избегаем частей, которые используют FileBinaryContentполе. Например, мы можем без проблем выполнить следующий запрос:

SELECT TOP 10 [BinaryContentID],[FileName],[BinaryContentRowGUID]
--,[FileContentBinary]
FROM [dbo].[BinaryContent]

Естественно, если я откомментирую строку выше, в том числе FileContentBinaryв запросе, я получаю сообщение об ошибке:

Данные больших объектов (LOB) для таблицы "dbo.BinaryContent" находятся в автономной файловой группе ("FileStreamContentFG"), к которой нет доступа.

Наша система обрабатывает файлы, для которых установлено содержимое null, поэтому я хотел бы сделать что-то вроде этого:

UPDATE [dbo].[BinaryContent]
SET [FileContentBinary] = null

Но это, конечно, дает мне ту же ошибку, что и выше. На данный момент я застрял.

Вопрос
Можно ли как-нибудь восстановить базу данных без необходимости восстанавливать все из FileStreamContentFGфайловой группы? Либо путем обновления значений на ноль, как я пытаюсь выше, либо по умолчанию на ноль, когда файл отсутствует или что-то?

Или я, возможно, подхожу к проблеме неправильно?

Я по натуре разработчик и не очень разбираюсь в качестве администратора, поэтому прошу прощения, если я пропускаю некоторые тривиальные вещи здесь.

юлианский
источник
Не могли бы вы выполнить полное восстановление один раз, чтобы у вас были данные из [BinaryContent] FILEGROUP, а затем выполнить восстановление основной файловой группы, когда вы хотите обновить ее?
jgardner04
@ jgardner04: это не похоже на работу. База данных оказывается в несогласованном состоянии, если я сначала выполняю полное восстановление, а затем восстановление из резервной копии, содержащей только первичную файловую группу (сообщение об ошибке: «База данных не может быть восстановлена, поскольку журнал не был восстановлен (...) база данных не может быть переведена в оперативный режим, потому что требуется один или несколько шагов RESTORE " ).
Джулиан
Ваш доступ к dbo.BinaryContent всегда через хранимые процедуры? Сколько участвуют?
Марк Стори-Смит
@ MarkStorey-Smith: доступ к базе данных в основном осуществляется с помощью обычных запросов через NHibernate (как из веб-приложения ASP.NET, так и из приложения Windows Forms). Насколько это актуально?
Джулиан
2
Если ваш доступ осуществлялся через хранимые процедуры, мы могли бы применить подход из частичной доступности / частичного восстановления, чтобы проверить, какие файловые группы находятся в сети. Честно говоря, на 12 ГБ действительно не стоит обходиться, просто сделать полное восстановление.
Марк Стори-Смит

Ответы:

10

То, что вы пытаетесь сделать, оставило бы базу данных в (транзакционно) несовместимом состоянии, следовательно, это невозможно.

Частичная доступность базы данных Whitepaper является полезным справочником и включает в себя пример того , как проверить , является ли конкретная таблица или файл в Интернете. Если бы ваш доступ к данным осуществлялся через хранимые процедуры, вы могли бы относительно легко включить эту проверку.

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

-- NB: SQLCMD script
:ON ERROR EXIT
:setvar DatabaseName "TestRename"
:setvar FilePath "D:\MSSQL\I3\Data\"

SET STATISTICS TIME OFF;
SET STATISTICS IO OFF;
SET NOCOUNT ON;
GO

USE master;
GO

IF EXISTS (SELECT name FROM sys.databases WHERE name = N'$(DatabaseName)')
  DROP DATABASE $(DatabaseName)
GO

CREATE DATABASE $(DatabaseName) 
ON PRIMARY 
  (
  NAME = N' $(DatabaseName)'
  , FILENAME = N'$(FilePath)$(DatabaseName).mdf'
  , SIZE = 5MB
  , MAXSIZE = UNLIMITED
  , FILEGROWTH = 1MB
  ) 
, FILEGROUP [FG1] DEFAULT
  ( 
  NAME = N' $(DatabaseName)_FG1_File1'
  , FILENAME = N'$(FilePath)$(DatabaseName)_FG1_File1.ndf'
  , SIZE = 1MB
  , MAXSIZE = UNLIMITED
  , FILEGROWTH = 1MB 
  ) 
, FILEGROUP [FG2] CONTAINS FILESTREAM
  ( 
  NAME = N'$(DatabaseName)_FG2'
  , FILENAME = N'$(FilePath)Filestream'
  )
LOG ON 
  ( 
  NAME = N'$(DatabaseName)_log'
  , FILENAME = N'$(FilePath)$(DatabaseName)_log.ldf'
  , SIZE = 1MB
  , MAXSIZE = UNLIMITED
  , FILEGROWTH = 1MB
  )
GO

USE $(DatabaseName);
GO

CREATE TABLE [dbo].[BinaryContent](
    [BinaryContentID] [int] IDENTITY(1,1) NOT NULL
    , [FileName] [varchar](50) NOT NULL
    , [BinaryContentRowGUID] [uniqueidentifier] ROWGUIDCOL UNIQUE DEFAULT (NEWSEQUENTIALID()) NOT NULL
  , [FileContentBinary] VARBINARY(max) FILESTREAM  NULL
) ON [PRIMARY] FILESTREAM_ON [FG2]
GO 

-- Insert test rows
INSERT
  dbo.BinaryContent
  (
  [FileName]
  , [FileContentBinary]
  )
VALUES
  (
  CAST(NEWID() AS VARCHAR(36))
  , CAST(REPLICATE(NEWID(), 100) AS VARBINARY)
  );
GO 100

USE master;
GO

-- Take FILESTREAM filegroup offline
ALTER DATABASE $(DatabaseName)
MODIFY FILE (NAME = '$(DatabaseName)_FG2', OFFLINE)
GO

USE $(DatabaseName);
GO

-- Rename table to make way for view
EXEC sp_rename 'dbo.BinaryContent', 'BinaryContentTable', 'OBJECT';
GO

-- Create view to return content from table but with NULL FileContentBinary
CREATE VIEW dbo.BinaryContent
AS

SELECT
  [BinaryContentID]
    , [FileName] 
    , [BinaryContentRowGUID]
  , [FileContentBinary] = NULL
FROM
  [dbo].[BinaryContentTable];
GO

-- Check results as expected
SELECT TOP 10
  *
FROM
  dbo.BinaryContent;
GO
Марк Стори-Смит
источник
5

Вы можете изолировать таблицу с помощью a FILESTREAMв отдельной базе данных и создать ссылку на нее в PRODUCTIONбазе данных, используя представление.

Это позволит вам делать то, что вы хотите, не прибегая к взломам.

боб
источник
Это был мой подход, но затем я столкнулся с проблемами поддержания ссылочной целостности между базами данных, поскольку триггеры, как правило, не поддерживаются в таблицах файлового
Джон Дж. Смит,