SQLServerCentral Article

Filegroup Restoring and Database Consistency

,

In this article, we'll explore a less used feature of SQL Server: filegroup restore (know also as piecemeal restore or partial restore), feature that has an interesting consequence because database can become inconsistent without any warning and standard checks will not reveal any issue.

Filegroup restore - demo

We'll start my creating a new database having the filegroup [PRIMARY] plus two new filegroups: [SECONDARY01] and [SECONDARY02]:

CREATE DATABASE FGRestore
  ON 
  PRIMARY (NAME = N'FGRestore_PR', FILENAME = N'C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_PR.mdf'), 
  FILEGROUP [SECONDARY01] (NAME = N'FGRestore_S01', FILENAME = N'C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_S01.ndf'), 
  FILEGROUP [SECONDARY02] (NAME = N'FGRestore_S02', FILENAME = N'C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_S02.ndf')
  LOG ON (NAME = N'FGRestore_log', FILENAME = N'C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_log.ldf')
GO
ALTER DATABASE FGRestore SET RECOVERY FULL
GO

Next, we'll create two tables: dbo.TablePK on SECONDARY01 filegroup (having ID column as PRIMARY KEY) and dbo.TableFK on SECONDARY02 filegroup (having ID column as FOREIGN KEY referencing dbo.TablePK(ID) and no primary key) and then we'll insert few rows in both tables ( 1 & 2 into dbo.TablePK and also 1 into dbo.TableFK):

CREATE TABLE dbo.TablePK(
  ID INT NOT NULL PRIMARY KEY
  ) ON [SECONDARY01]
INSERT dbo.TablePK VALUES (1), (2)
GO
CREATE TABLE dbo.TableFK(
    ID INT NOT NULL REFERENCES dbo.TablePK(ID)) ON [SECONDARY02]
INSERT dbo.TableFK VALUES (1)
GO

After these steps, the status of current database is:

Let's consider this moment to be T and let's create a full backup:

BACKUP DATABASE FGRestore 
  TO DISK = 'FGRestore_FULL.bak'
  WITH INIT, FORMAT

Now, we'll insert few more rows into these table( 3 into dbo.TablePK and 2 & 3 into dbo.TableFK):

INSERT dbo.TablePK VALUES (3)
INSERT dbo.TableFK VALUES (2), (3)

Let's consider this moment to be T+1 and let's create a log backup thus:

BACKUP LOG FGRestore 
  TO DISK = 'FGRestore_LOG_01.trn'
  WITH INIT, FORMAT, NORECOVERY

Note: if we avoid this step, the next step (RESTORE DATABASE ... WITH PARTIAL, RECOVERY) should be executed using WITH REPLACE option (RESTORE DATABASE ... WITH REPLACE, PARTIAL, RECOVERY).

Because, we have used BACKUP LOG ... WITH NORECOVERY the status of the database changed to Restoring. Also the status of all data (type_desc = ROWS) files become Restoring:

SELECT name, physical_name, type_desc, state_desc FROM sys.master_files mf WHERE mf.database_id = DB_ID('FGRestore')
name physical_name type_desc state_desc
------------- ------------------------------------------------------ --------- ----------
FGRestore_PR C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_PR.mdf ROWS RESTORING
FGRestore_log C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_log.ldf LOG ONLINE
FGRestore_S01 C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_S01.ndf ROWS RESTORING
FGRestore_S02 C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_S02.ndf ROWS RESTORING

the next step is to restore only the filegroup SECONDARY01 (used by dbo.TablePK) from a full database backup (moment T) thus:

RESTORE DATABASE FGRestore 
  FILEGROUP = 'SECONDARY01'
  FROM DISK = 'FGRestore_FULL.bak'
  WITH PARTIAL, RECOVERY

This command will restore SECONDARY01 filegroup but also wil restore - by default - the PRIMARY filegroup. Because we have used WITH RECOVERY, SQL Server will bring online these filegroups and their associated files. Now, the status of every database file is as follow:

name physical_name state_desc
------------- ------------------------------------------------------ ----------------
FGRestore_PR C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_PR.mdf ONLINE
FGRestore_log C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_log.ldf ONLINE
FGRestore_S01 C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_S01.ndf ONLINE
FGRestore_S02 C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_S02.ndf RECOVERY_PENDING

and because there is one more filegroup having non-ONLINE status (SECONDARY02 - FGRstore_S02) we'll bring it online with following RESTORE command:

RESTORE DATABASE FGRestore 
  FILEGROUP = 'SECONDARY02'
  WITH RECOVERY

After this final step, all database files are online:

name physical_name type_desc state_desc
------------- ------------------------------------------------------ --------- ----------
FGRestore_PR C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_PR.mdf ROWS ONLINE
FGRestore_log C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_log.ldf LOG ONLINE
FGRestore_S01 C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_S01.ndf ROWS ONLINE
FGRestore_S02 C:\DB\SQL\MSSQL13.SQL2016\MSSQL\DATA\FGRestore_S02.ndf ROWS ONLINE

and the status of both tables is

Now it comes the most interesting part:

  • Both RESTORE commands were executed successfully.

  • All files are ONLINE and we are able to read data from both tables.

  • Filegroup SECONDARY01 (dbo.TablePK) was restored at moment T but filegroup SECONDARY02 (dbo.TableFK) is still at moment T+1.

  • Because of FG restore, dbo.TablePK contains only two rows (1 & 2) while dbo.TableFK contains all rows (1, 2 & 3). In this this, we can see that referential integrity (foreign key constraint) is in the air because the values 3 from dbo.TableFK (table with foreign key) is missing from parent table (dbo.TableFK). The end result is database inconsistency.

SELECT *, 'TablePK' AS TableName FROM dbo.TablePK
SELECT *, 'TableFK' AS TableName FROM dbo.TableFK
GO

ID          TableName
----------- ---------
1           TablePK
2           TablePK
(2 row(s) affected)
ID           TableName
-----------  ---------
1            TableFK
2            TableFK
3 TableFK --> This foreign key values doesn't have an associated value within primary key (dbo.TablePK.ID)
(3 row(s) affected)

If we are checking current database with DBCC CHECKDB everything seems to be fine:

DBCC CHECKDB() WITH ALL_ERRORMSGS, NO_INFOMSGS
/*
Command(s) completed successfully.
*/

The only way to detect this database inconsistency is to use with following command:

  DBCC CHECKCONSTRAINTS()
/*
Table            Constraint                   Where
---------------- ---------------------------- ---------
[dbo].[TableFK]  [FK__TableFK__ID__24927208]  [ID] = '3'
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
  */

Another interesting aspect is that foreign key constraint remains enabled and trusted after this filegroup restore:

SELECT name, is_not_trusted, is_disabled FROM sys.foreign_keys x
/*
name                      is_not_trusted is_disabled
------------------------- -------------- -----------
FK__TableFK__ID__24927208 0              0
*/

Final note

After restoring filegroups is recommended to check constraints in order to find possible database inconsistency generated by missing values (rows) from primary key. This feature is available only in Enterprise and Developer edition. 

Rate

5 (4)

You rated this post out of 5. Change rating

Share

Share

Rate

5 (4)

You rated this post out of 5. Change rating