Preskočiť na obsah
Skript

Generátor RESTORE príkazov z histórie záloh

Z msdb poskladá poradie RESTORE príkazov: posledný FULL, DIFF a reťaz LOG záloh až k zvolenému času (STOPAT). Kontroluje diery v reťazi logov. Príkazy iba vypíše, nespúšťa.

generator-restore-prikazov-z-historie-zaloh.sql 158 riadkov · T-SQL
Parametre skriptu doplň a kopíruj hotový skript
/* DeadlockCafe | Generátor RESTORE príkazov z histórie záloh
   Účel:  Z msdb poskladá poradie RESTORE príkazov: posledný FULL, DIFF a reťaz LOG záloh až k zvolenému času (STOPAT). Kontroluje diery v reťazi logov. Príkazy iba vypíše, nespúšťa.
   Režim: READ ONLY (príkazy len vypíše) | SQL Server 2016 – 2022, on-premises
*/
SET NOCOUNT ON;

DECLARE @DatabaseName sysname      = N'{{DatabaseName|Databáza, ktorej zálohy sa hľadajú v msdb}}';
DECLARE @StopAtText   nvarchar(40) = N'{{StopAt:teraz|Cieľový čas yyyy-mm-dd hh:mi:ss alebo teraz}}';
DECLARE @TargetName   sysname      = N'{{TargetName:*|Meno obnovenej databázy, * = pôvodné meno}}';

DECLARE @StopAt datetime = NULL;
IF @StopAtText <> N'teraz'
BEGIN
    SET @StopAt = TRY_CONVERT(datetime, @StopAtText, 120);
    IF @StopAt IS NULL
    BEGIN
        RAISERROR(N'StopAt: zadaj čas v tvare yyyy-mm-dd hh:mi:ss alebo slovo teraz.', 16, 1);
        RETURN;
    END;
END;
IF @TargetName = N'*' SET @TargetName = @DatabaseName;
DECLARE @Limit datetime = ISNULL(@StopAt, GETDATE());

-- 1) posledný FULL (bez COPY_ONLY) dokončený pred cieľovým časom
DECLARE @full_id int, @full_ckpt numeric(25,0), @full_last numeric(25,0);
SELECT TOP (1) @full_id = backup_set_id, @full_ckpt = checkpoint_lsn, @full_last = last_lsn
FROM msdb.dbo.backupset
WHERE database_name = @DatabaseName AND type = 'D' AND is_copy_only = 0
  AND backup_finish_date <= @Limit
ORDER BY backup_finish_date DESC, backup_set_id DESC;

IF @full_id IS NULL
BEGIN
    RAISERROR(N'V msdb nie je FULL záloha (bez COPY_ONLY) databázy %s dokončená pred cieľovým časom.', 16, 1, @DatabaseName);
    RETURN;
END;

-- 2) posledný DIFF postavený na tomto FULL (differential_base_lsn = checkpoint_lsn FULL zálohy)
DECLARE @diff_id int, @diff_last numeric(25,0);
SELECT TOP (1) @diff_id = backup_set_id, @diff_last = last_lsn
FROM msdb.dbo.backupset
WHERE database_name = @DatabaseName AND type = 'I'
  AND differential_base_lsn = @full_ckpt
  AND backup_finish_date <= @Limit
ORDER BY backup_finish_date DESC, backup_set_id DESC;

DECLARE @base_last numeric(25,0) = ISNULL(@diff_last, @full_last);

IF OBJECT_ID(N'tempdb..#plan') IS NOT NULL DROP TABLE #plan;
CREATE TABLE #plan
(
    step          int IDENTITY(1,1) PRIMARY KEY,
    backup_set_id int           NULL,
    backup_type   varchar(12)   NOT NULL,
    media_set_id  int           NULL,
    first_lsn     numeric(25,0) NULL,
    last_lsn      numeric(25,0) NULL,
    finished_at   datetime      NULL,
    note          nvarchar(400) NULL,
    command       nvarchar(max) NULL
);

INSERT INTO #plan (backup_set_id, backup_type, media_set_id, first_lsn, last_lsn, finished_at)
SELECT backup_set_id, CASE type WHEN 'D' THEN 'FULL' ELSE 'DIFF' END, media_set_id, first_lsn, last_lsn, backup_finish_date
FROM msdb.dbo.backupset
WHERE backup_set_id IN (@full_id, @diff_id)
ORDER BY backup_set_id;

-- 3) LOG zálohy za FULL / DIFF; so StopAt len po prvú, ktorá cieľový čas pokrýva
;WITH logs AS
(
    SELECT backup_set_id, media_set_id, first_lsn, last_lsn, backup_finish_date,
           ROW_NUMBER() OVER (ORDER BY first_lsn, backup_set_id) AS rn
    FROM msdb.dbo.backupset
    WHERE database_name = @DatabaseName AND type = 'L' AND is_copy_only = 0
      AND last_lsn > @base_last
)
INSERT INTO #plan (backup_set_id, backup_type, media_set_id, first_lsn, last_lsn, finished_at)
SELECT l.backup_set_id, 'LOG', l.media_set_id, l.first_lsn, l.last_lsn, l.backup_finish_date
FROM logs AS l
WHERE @StopAt IS NULL
   OR l.rn <= ISNULL((SELECT MIN(x.rn) FROM logs AS x WHERE x.backup_finish_date >= @StopAt), 2147483647)
ORDER BY l.rn;

-- 4) nadväznosť LSN: first_lsn každej LOG zálohy musí byť <= last_lsn predchádzajúcej
;WITH c AS
(
    SELECT step, backup_type, first_lsn,
           LAG(last_lsn)    OVER (ORDER BY step) AS prev_last_lsn,
           LAG(backup_type) OVER (ORDER BY step) AS prev_type
    FROM #plan
)
UPDATE p
SET note = CASE
               WHEN c.first_lsn > c.prev_last_lsn
                   THEN N'DIERA v reťazi: chýba LOG záloha s LSN ' + CONVERT(nvarchar(30), c.prev_last_lsn)
                        + N' až ' + CONVERT(nvarchar(30), c.first_lsn) + N' – za týmto bodom sa obnoviť nedá'
               WHEN c.prev_type = 'LOG' AND c.first_lsn < c.prev_last_lsn
                   THEN N'Prekryv s predchádzajúcou LOG zálohou (duplicitný záznam v msdb?) – over ručne'
           END
FROM #plan AS p
JOIN c ON c.step = p.step
WHERE c.backup_type = 'LOG';

IF @StopAt IS NOT NULL AND NOT EXISTS (SELECT 1 FROM #plan WHERE backup_type = 'LOG' AND finished_at >= @StopAt)
    INSERT INTO #plan (backup_type, note)
    VALUES ('VAROVANIE', N'Žiadna LOG záloha v msdb nepokrýva StopAt – treba novší log backup alebo tail-log backup.');

-- 5) príkazy (viac súborov na zálohu = viac DISK; pri inom mene cieľa MOVE so suffixom)
DECLARE @stop nvarchar(60) = CASE WHEN @StopAt IS NULL THEN N''
                                  ELSE N', STOPAT = N''' + CONVERT(nvarchar(19), @StopAt, 126) + N'''' END;

UPDATE p
SET command = CASE p.backup_type WHEN 'LOG' THEN N'RESTORE LOG ' ELSE N'RESTORE DATABASE ' END
            + QUOTENAME(@TargetName) + N' FROM ' + dev.list
            + N' WITH NORECOVERY'
            + CASE WHEN p.backup_type = 'FULL' THEN N', STATS = 10' + mv.list ELSE N'' END
            + CASE WHEN p.backup_type = 'LOG' THEN @stop ELSE N'' END
            + N';'
FROM #plan AS p
CROSS APPLY (SELECT STUFF((SELECT N', ' + CASE WHEN mf.device_type = 9 THEN N'URL' ELSE N'DISK' END
                                 + N' = N''' + REPLACE(mf.physical_device_name, N'''', N'''''') + N''''
                          FROM msdb.dbo.backupmediafamily AS mf
                          WHERE mf.media_set_id = p.media_set_id AND mf.mirror = 0
                          ORDER BY mf.family_sequence_number
                          FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 2, N'') AS list) AS dev
CROSS APPLY (SELECT CASE WHEN @TargetName = @DatabaseName THEN N''
                         ELSE ISNULL((SELECT NCHAR(13) + NCHAR(10) + N'    , MOVE N''' + REPLACE(bf.logical_name, N'''', N'''''')
                                             + N''' TO N''' + REPLACE(
                                                 CASE WHEN CHARINDEX(N'.', REVERSE(bf.physical_name)) > 0
                                                      THEN LEFT(bf.physical_name, LEN(bf.physical_name) - CHARINDEX(N'.', REVERSE(bf.physical_name)))
                                                           + N'_' + @TargetName
                                                           + RIGHT(bf.physical_name, CHARINDEX(N'.', REVERSE(bf.physical_name)))
                                                      ELSE bf.physical_name + N'_' + @TargetName END, N'''', N'''''') + N''''
                                      FROM msdb.dbo.backupfile AS bf
                                      WHERE bf.backup_set_id = p.backup_set_id AND bf.is_present = 1
                                      ORDER BY bf.file_type, bf.file_number
                                      FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), N'')
                    END AS list) AS mv
WHERE p.backup_type IN ('FULL', 'DIFF', 'LOG');

INSERT INTO #plan (backup_type, command)
VALUES ('RECOVERY', N'RESTORE DATABASE ' + QUOTENAME(@TargetName) + N' WITH RECOVERY;');

-- 6) výstup
SELECT @DatabaseName AS source_database,
       @TargetName   AS target_database,
       @StopAt       AS stop_at,
       (SELECT COUNT(*) FROM #plan WHERE backup_type = 'LOG')    AS log_backups,
       (SELECT COUNT(*) FROM #plan WHERE note LIKE N'DIERA%')    AS chain_gaps,
       CASE WHEN DB_ID(@TargetName) IS NOT NULL
            THEN N'Cieľová databáza existuje – RESTORE by ju prepísal (treba REPLACE alebo tail-log backup). Zváž obnovu pod iným menom.'
            ELSE N'' END AS warning;

SELECT step, backup_type, finished_at, first_lsn, last_lsn, note, command
FROM #plan
ORDER BY step;
## Kedy použiť

Pri obnove databázy k bodu v čase, pri teste obnovy alebo keď potrebuješ rýchlo vedieť, **ktoré súbory** a v akom poradí treba obnoviť.

## Ako čítať výsledok

Prvá sada je súhrn: zdrojová a cieľová DB, cieľový čas, počet LOG záloh, počet dier a varovanie, ak by RESTORE prepísal existujúcu databázu. Druhá sada je plán: `step`, typ zálohy, čas dokončenia, LSN a stĺpec `command` – príkazy skopíruj **v poradí** do nového okna. Riadok s `note` = DIERA znamená, že od tohto bodu ďalej sa obnoviť nedá (chýba LOG záloha). Ak je `TargetName` iné ako pôvodné meno, FULL príkaz dostane `MOVE` na súbory s príponou `_TargetName` – cesty pred spustením skontroluj.

> [!WARNING]
> Skript vychádza len z **msdb tohto servera**: zálohy zo sekundárnej AG repliky, z iného servera alebo zmazané súbory tu nevidí. COPY_ONLY zálohy sa ignorujú. Časy v msdb sú lokálny čas servera. Záznam v msdb nie je dôkaz, že súbor ešte existuje a je čitateľný – pred produkčnou obnovou over súbory (`RESTORE HEADERONLY` / `VERIFYONLY`). Zálohy na pásku alebo cez virtual device (tretie strany) treba obnovovať ich nástrojom.

**Oprávnenia:** Čítanie `msdb.dbo.backupset`, `backupmediafamily` a `backupfile` (napr. `db_datareader` v msdb). Samotný RESTORE potom vyžaduje `CREATE DATABASE` (nová DB) alebo `sysadmin` / `dbcreator` / vlastníka DB.  
**Režim:** READ ONLY (príkazy len vypíše) · **Verzia:** SQL Server 2016 – 2022

## Súvisiace recepty

- [História záloh – typ, veľkosť, kompresia, súbor](script.php?id=31&s=historia-zaloh-typ-velkost-kompresia-subor)
- [Posledné zálohy každej databázy](script.php?id=3&s=posledne-zalohy-kazdej-databazy)
- [Priebeh BACKUP / RESTORE / DBCC](script.php?id=21&s=priebeh-backup-restore-dbcc)
- [Obnova databázy k bodu v čase](script.php?id=60&s=obnova-databazy-k-bodu-v-case-point-in-time-restore)

## Zdroje

- [Restore a database to a point in time (full recovery model)](https://learn.microsoft.com/en-us/sql/relational-databases/backup-restore/restore-a-sql-server-database-to-a-point-in-time-full-recovery-model)
- [Tail-log backups](https://learn.microsoft.com/en-us/sql/relational-databases/backup-restore/tail-log-backups-sql-server)
- [backupset (msdb)](https://learn.microsoft.com/en-us/sql/relational-databases/system-tables/backupset-transact-sql)
- [backupmediafamily (msdb)](https://learn.microsoft.com/en-us/sql/relational-databases/system-tables/backupmediafamily-transact-sql)
- [backupfile (msdb)](https://learn.microsoft.com/en-us/sql/relational-databases/system-tables/backupfile-transact-sql)

_Skript je staticky overený proti dokumentácii Microsoft Learn (názvy DMV, stĺpcov a hodnôt), nie spustený na živom serveri. Pred použitím na PROD ho vyskúšaj na TEST._