Restaurar banco de dados, excluindo dados FILESTREAM

20

Contexto
Estamos desenvolvendo um sistema com um grande banco de dados na parte inferior. É um banco de dados MS SQL em execução no SQL Server 2008 R2. O tamanho total do banco de dados é de cerca de 12 GB.

Desses, aproximadamente 8,5 GB estão em uma única tabela BinaryContent. Como o nome sugere, esta é uma tabela na qual armazenamos arquivos simples, de qualquer tipo, diretamente na tabela como um BLOB. Recentemente, testamos a possibilidade de mover todos esses arquivos para fora do banco de dados para o sistema de arquivos usando FILESTREAM.

Fizemos as modificações necessárias em nosso banco de dados sem problemas e nosso sistema ainda está funcionando bem após a migração. A BinaryContenttabela é mais ou menos assim:

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]

Com tudo residindo no PRIMARYgrupo de arquivos, exceto o campo FileBinaryContentque está em um grupo de arquivos separado FileStreamContentFG.

Cenário
Do ponto de vista do desenvolvedor, muitas vezes gostaríamos de uma cópia nova do banco de dados do nosso ambiente de produção, para poder trabalhar com os dados mais recentes. Nesses casos, raramente estamos interessados ​​nos arquivos armazenados no BinaryContent (agora usando FILESTREAM).

Temos isso quase funcionando como gostaríamos. Fazemos backup do banco de dados, sem o fluxo de arquivos como este:

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

E restaure-o assim:

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

Isso parece estar funcionando bem, e nosso sistema funciona desde que evitemos as peças que usam o FileBinaryContentcampo. Por exemplo, podemos executar a seguinte consulta sem problemas:

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

Naturalmente, se eu cancelar o comentário da linha acima, inclusive FileContentBinaryna consulta, recebo um erro:

Os dados do objeto grande (LOB) da tabela "dbo.BinaryContent" residem em um grupo de arquivos offline ("FileStreamContentFG") que não pode ser acessado.

Nossos alças do sistema de arquivos onde o conteúdo está definido para null, assim que eu gosto de fazer é algo como isto:

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

Mas isso, obviamente, me dá o mesmo erro acima. Neste ponto, eu estou preso.

Pergunta
Existe alguma maneira de restaurar o banco de dados sem precisar também restaurar tudo do FileStreamContentFGgrupo de arquivos? Atualizando os valores para null como estou tentando acima, ou o padrão para null quando o arquivo está faltando ou algo assim?

Ou talvez eu esteja abordando o problema da maneira errada?

Sou um desenvolvedor por natureza e não tem muito conhecimento como DBA, por isso, desculpe-me se estou negligenciando alguma coisa trivial aqui.

Julian
fonte
Você poderia fazer a restauração completa uma vez para ter alguns dados do FILEGROUP [BinaryContent] e, em seguida, fazer uma restauração do grupo de arquivos primário quando desejar atualizá-lo?
Jgardner04
@ jgardner04: isso não parece funcionar. O banco de dados termina em um estado inconsistente se eu fizer uma restauração completa, seguida de uma restauração do backup que contém apenas o grupo de arquivos Primário (mensagem de erro: "O banco de dados não pode ser recuperado porque o log não foi restaurado (...) o banco de dados não pôde ser colocado online porque são necessárias uma ou mais etapas RESTORE " ).
Julian
O seu acesso ao dbo.BinaryContent é sempre via procedimentos armazenados? Quantos estão envolvidos?
Mark-Storey-Smith #
@ MarkStorey-Smith: o banco de dados é acessado principalmente através de consultas regulares, através do NHibernate (tanto de um aplicativo Web do ASP.NET quanto de um aplicativo de formulários do Windows). Como isso é relevante?
Julian
2
Se o seu acesso foi feito através de procedimentos armazenados, podemos aplicar uma abordagem de disponibilidade parcial / restauração fragmentada para verificar quais grupos de arquivos estão online. Para ser sincero, com 12 GB não vale a pena trabalhar apenas para fazer a restauração completa.
Mark Storey-Smith

Respostas:

10

O que você está tentando fazer deixaria o banco de dados em um estado inconsistente (transacionalmente), portanto, não é possível.

O white paper Disponibilidade parcial do banco de dados é um guia de referência útil e inclui um exemplo de como verificar se uma tabela ou arquivo específico está online. Se o seu acesso a dados for realizado através de procedimentos armazenados, você poderá incorporar essa verificação com relativa facilidade.

Uma abordagem alternativa (mas um tanto hacky) que pode valer uma olhada no seu cenário seria ocultar a tabela e substituí-la por uma visualização.

-- 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
Mark Storey-Smith
fonte
5

Você pode isolar a tabela com a FILESTREAMem um banco de dados separado e criar uma referência a ela no PRODUCTIONbanco de dados usando uma visualização.

Isso permitiria que você fizesse o que deseja sem recorrer a hacks.

Prumo
fonte
Essa seria minha abordagem, mas me deparei com problemas de manutenção da integridade referencial entre os bancos de dados, pois geralmente não há suporte para acionadores com tabelas de fluxo de arquivos
John J Smith