Proving the Restore, Part 9: Backups From a Different Database

Two backup files, same database name, same server name, LSNs that line up. They can still be from different databases.

Database names get reused. A database is restored under a new name, then renamed back. A refresh from production overwrites a test database, and last week’s backups from the old contents are still on the share. Names and dates won’t separate those; the header carries identifiers that will.

A woman archivist holds a magnifier over two open lockets lying on dark cloth, each containing an engraved crest, the two crests clearly different from one another.

FamilyGUID and BindingID

Two uniqueidentifier columns describe database identity, and the difference between them is what makes the check work.[1]

FamilyGUID is the ID of the original database when it was created. It stays the same when the database is restored.

BindingID is the binding ID for the database, and a new value is assigned when a database is restored.

So FamilyGUID tracks lineage across restores while BindingID changes at each one. Backups sharing a FamilyGUID descend from the same original CREATE DATABASE. Backups with different FamilyGUID values came from databases that were separately created, regardless of what they’re called now.

More than one distinct FamilyGUID in a set you intended to restore together means the set is mixed. Grouping makes that obvious:

One row is what you want. Two rows with the same DatabaseName and different FamilyGUID values is the case worth catching, because nothing else in the filename or the dates would have told you.

Recovery forks

Lineage answers whether backups descend from the same database. It doesn’t answer whether they descend along the same branch of that database’s history, and restores create branches.

Recover a database to a point in time, bring it online, and everything after that recovery point is a new branch. Log backups taken from the original timeline and log backups taken after the recovery both trace back to the same FamilyGUID, but they describe divergent histories. Restoring across the divergence isn’t valid.

Three columns describe this.[1]

Column Meaning
FirstRecoveryForkID ID for the starting recovery fork
RecoveryForkID ID for the ending recovery fork
ForkPointLSN The LSN of the fork point, when the two differ. NULL otherwise

For data backups, FirstRecoveryForkID equals RecoveryForkID. A log backup that spans a fork point has different values in the two, and ForkPointLSN gives the LSN where the branch happened.

Rows here mean the set spans at least one recovery fork. That doesn’t make the backups useless, but it does mean the restore path has to stay on one branch, and working out which branch you want is a decision rather than a calculation.

msdb records the same information as first_recovery_fork_guid and last_recovery_fork_guid on backupset, so the check works against instance history as well as against the files.[2]

Counting distinct RecoveryForkID values across a set gives a quicker signal for automated checks:

More than one is worth reporting.

Where this shows up

Restoring a copy alongside the original. Restore last night’s backup as Sales_test and back it up, and those backups share FamilyGUID with production while having their own BindingID. Lineage alone won’t separate them; the database name and the fork will.

A database refreshed from another environment. The target’s contents are replaced, so backups taken before and after the refresh have different FamilyGUID values under the same name. This is the case where checking lineage earns its keep, because both sets look plausible on disk.

A failed point-in-time recovery followed by a retry. Each recovery creates a fork. Backups taken between attempts belong to branches that were abandoned.

Adding it to the validation

Lineage and fork checks are cheap once the headers are in a table, and they catch a class of problem the LSN checks can’t see. A set with a broken log chain fails visibly. A set with two lineages and a clean chain within each looks fine until the restore rejects it.

Reasonable severities:

Finding Severity
More than one distinct FamilyGUID error, the set is mixed
More than one distinct RecoveryForkID warning, needs a decision about which branch
ForkPointLSN not NULL on any backup warning, the set spans a fork
DatabaseName differs within one FamilyGUID informational, expected after a restore under a new name

The last one is worth reporting rather than suppressing. Seeing that the lineage includes backups taken under a different name is often the first clue about where a set came from.

Next

Lineage, continuity, and a matching differential base tell you the set is coherent. Whether it reaches the moment you actually want is a separate question, and part 10 covers proving that before you start.

Have you had two databases with the same name and different lineage on one share? I’d like to hear how it was untangled. Bluesky or LinkedIn.

References

  1. RESTORE HEADERONLY (Transact-SQL) – Microsoft Learn. Documents FamilyGUID as the ID of the original database, which stays the same when the database is restored, BindingID as reassigned on restore, and the FirstRecoveryForkID, RecoveryForkID, and ForkPointLSN columns.
  2. backupset – Microsoft Learn. Records first_recovery_fork_guid and last_recovery_fork_guid, the msdb equivalents of the fork columns returned by the header.