MSSQL 6.2 · 6부. 운영과 실전

백업과 복구

복구 모델을 선택하고 특정 시점으로 되돌립니다.

예상 학습 시간 22분 난이도 중급
준비

실습용 데이터베이스를 따로 만듭니다

이 단원은 데이터베이스를 실제로 지웠다가 되돌립니다. MssqlLab 을 복원해 버리면 앞 단원의 실습 상태가 사라지므로 작은 것을 따로 만들어 사용합니다.

SQL
CREATE DATABASE MssqlLabBak;
ALTER DATABASE MssqlLabBak SET RECOVERY FULL;
GO

USE MssqlLabBak;
GO
CREATE TABLE dbo.T_LOG (
    num      int IDENTITY PRIMARY KEY,
    memo     nvarchar(100),
    reg_date datetime2(0) NOT NULL DEFAULT SYSDATETIME()
);
INSERT INTO dbo.T_LOG (memo) VALUES (N'첫 번째'), (N'두 번째'), (N'세 번째');

백업 파일을 둘 폴더도 하나 만들어 두십시오. 아래에서는 C:\mssql-lab-backup\ 을 사용한 것으로 적습니다. SQL Server 서비스 계정이 쓸 수 있는 폴더여야 합니다.

개념 설명

복구 모델이 나머지를 정합니다

어디까지 되돌릴 수 있는가는 복구 모델이 정합니다. 백업 계획을 세우기 전에 이것부터 정해야 합니다.

모델로그로그 백업되돌릴 수 있는 지점
SIMPLE커밋되면 재사용됩니다할 수 없습니다마지막 백업 시점까지
FULL로그 백업 전까지 쌓입니다해야 합니다원하는 시각까지
BULK_LOGGED대량 작업만 줄여 기록합니다해야 합니다대량 작업 구간은 시점 지정 불가
SQL
ALTER DATABASE MssqlLabBak SET RECOVERY SIMPLE;
GO
BACKUP LOG MssqlLabBak TO DISK = 'C:\mssql-lab-backup\x.trn';
결과
메시지 4208, 수준 16, 상태 1 The statement BACKUP LOG is not allowed while the recovery model is SIMPLE. Use BACKUP DATABASE or change the recovery model using ALTER DATABASE.
SIMPLE 이면 로그 백업 자체가 막힙니다. 시점 복원도 따라서 못 합니다.

SIMPLE 은 편해 보이지만 "마지막 백업 이후는 전부 잃는다" 는 뜻입니다. 하루 한 번 백업한다면 최대 하루치입니다. 그것이 괜찮은 자료인지 업무 쪽과 먼저 정하십시오. 사고가 난 다음에 정할 수는 없습니다.

종류

백업 세 가지

SQL
-- 전체 — 그 시점의 데이터베이스 전부입니다.
BACKUP DATABASE MssqlLabBak
TO DISK = 'C:\mssql-lab-backup\Lab_full.bak'
WITH INIT, NAME = N'전체';

-- 차등 — 마지막 전체 백업 뒤에 바뀐 것만입니다.
BACKUP DATABASE MssqlLabBak
TO DISK = 'C:\mssql-lab-backup\Lab_diff.bak'
WITH INIT, DIFFERENTIAL, NAME = N'차등';

-- 로그 — 마지막 로그 백업 뒤의 기록입니다.
BACKUP LOG MssqlLabBak
TO DISK = 'C:\mssql-lab-backup\Lab_log1.trn'
WITH INIT, NAME = N'로그1';
결과 — 같은 데이터베이스입니다
종류크기담는 것
전체3.27 MB그 시점 전부
차등1.27 MB전체 백업 뒤 바뀐 것
로그0.07 MB마지막 로그 백업 뒤의 기록
차등은 전체 백업이 오래될수록 커집니다. 로그는 그 사이에 무슨 일이 있었느냐에 달렸습니다.
옵션
INIT그 파일의 기존 백업을 지우고 새로 씁니다
NOINIT뒤에 덧붙입니다(기본값)
CHECKSUM읽으면서 검사합니다. 붙이는 편이 낫습니다
COPY_ONLY백업 순서를 건드리지 않습니다
COMPRESSION압축합니다. 에디션에 따라 막힙니다
Express 에서 COMPRESSION 을 붙이면
메시지 1844, 수준 16, 상태 1 BACKUP DATABASE WITH COMPRESSION is not supported on Express Edition (64-bit).
에디션마다 되는 것이 다릅니다. 배포 대상 서버에서 확인하십시오.

COPY_ONLY 는 "지금 한 벌 떠 두고 싶다" 는 자리에 씁니다. 개발 서버로 옮기려고 백업을 뜰 때 그냥 뜨면 차등 백업의 기준점이 그리로 옮겨 가 정규 백업 순서가 흐트러집니다. COPY_ONLY 를 붙이면 그런 일이 없습니다.

파일 안을 들여다봅니다

SQL
-- 무엇이 들어 있습니까
RESTORE HEADERONLY FROM DISK = 'C:\mssql-lab-backup\Lab_full.bak';

-- 어떤 파일들로 이루어져 있습니까
RESTORE FILELISTONLY FROM DISK = 'C:\mssql-lab-backup\Lab_full.bak';

-- 이 파일이 성한가
RESTORE VERIFYONLY FROM DISK = 'C:\mssql-lab-backup\Lab_full.bak';
HEADERONLY — 열이 많아 여섯만 옮겼습니다
BackupName종류DatabaseNameRecoveryModel마친 시각
전체DatabaseMssqlLabBakFULL2026-09-03 20:27:04
FILELISTONLY
LogicalNamePhysicalNameType
MssqlLabBakC:\…\MssqlLabBak.mdfD
MssqlLabBak_logC:\…\MssqlLabBak_log.ldfL
VERIFYONLY
The backup set on file 1 is valid.
다른 서버로 옮겨 복원할 때 FILELISTONLY 로 논리 이름을 먼저 확인합니다. MOVE 절에 그 이름을 적습니다.
실측

지운 것을 되돌립니다

실제로 사고를 내고 사고 직전으로 되돌려 봅니다. 지금까지 만들어 둔 것은 전체 백업(3행) · 로그1(5행) · 차등(6행)입니다.

SQL
USE MssqlLabBak;
GO
SELECT COUNT(*) FROM dbo.T_LOG;
SELECT CONVERT(varchar(23), SYSDATETIME(), 121) AS 사고직전시각;
GO

-- 사고가 납니다. WHERE 를 빠뜨렸다고 합시다.
DELETE FROM dbo.T_LOG;
SELECT COUNT(*) FROM dbo.T_LOG;
결과
사고 전 행수6
사고 직전 시각2026-09-03 20:27:04.573
사고 뒤0
시각을 밀리초까지 적어 두어야 합니다. 그 값이 복원의 기준이 됩니다.

가장 먼저 할 일은 지금 로그를 백업하는 것입니다. 사고 이후의 기록까지 떠 두어야 그 안의 어느 시점으로든 갈 수 있습니다. 이것을 하지 않으면 마지막 백업 이후가 통째로 사라집니다.

SQL
USE master;
GO
BACKUP LOG MssqlLabBak
TO DISK = 'C:\mssql-lab-backup\Lab_log2.trn'
WITH INIT, NAME = N'로그2 — 사고 뒤';

세 단계로 되돌립니다

SQL
USE master;
GO
ALTER DATABASE MssqlLabBak SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
GO

-- 1. 전체 백업을 올립니다. 아직 열지 않습니다.
RESTORE DATABASE MssqlLabBak
FROM DISK = 'C:\mssql-lab-backup\Lab_full.bak'
WITH NORECOVERY, REPLACE;
GO

-- 2. 차등을 얹습니다. 전체 백업 뒤의 변화가 한 번에 따라옵니다.
RESTORE DATABASE MssqlLabBak
FROM DISK = 'C:\mssql-lab-backup\Lab_diff.bak'
WITH NORECOVERY;
GO

-- 3. 로그를 그 시각까지만 적용하고 엽니다.
RESTORE LOG MssqlLabBak
FROM DISK = 'C:\mssql-lab-backup\Lab_log2.trn'
WITH RECOVERY, STOPAT = '2026-09-03 20:27:04.573';
GO

ALTER DATABASE MssqlLabBak SET MULTI_USER;
중간에 상태를 보면
name상태
MssqlLabBakRESTORING
마친 뒤
nummemo
1첫 번째
2두 번째
3세 번째
4네 번째
5다섯 번째
6여섯 번째
6행이 그대로 돌아왔습니다. DELETE 는 그 시각 뒤라 적용되지 않았습니다.
옵션
NORECOVERY더 얹을 것이 있습니다. 데이터베이스를 열지 않습니다
RECOVERY이것이 마지막입니다. 열어서 사용할 수 있게 합니다
REPLACE같은 이름이 이미 있어도 덮어씁니다
STOPAT그 시각까지만 적용합니다
MOVE파일 경로를 바꿉니다. 다른 서버로 옮길 때 씁니다

마지막 하나만 RECOVERY 입니다. 중간에 실수로 RECOVERY 를 붙이면 그 뒤를 더 얹지 못하고 처음부터 다시 해야 합니다.

운영 중인 것을 덮어쓰지 말고 다른 이름으로 복원하십시오. RESTORE DATABASE 복구용 … WITH MOVE … 로 옆에 올린 뒤 필요한 행만 옮겨 오는 것이 안전합니다. 사고 이후에 들어온 정상 자료까지 날리는 일이 없습니다.

계획

얼마나 자주 뜹니까

두 가지를 먼저 정합니다. 기술이 아니라 업무 쪽과 정할 일입니다.

정할 것묻는 방법정해지면
얼마나 잃어도 됩니까"몇 분치까지 다시 입력할 수 있습니까"로그 백업 주기가 정해집니다
얼마나 빨리 살려야 합니까"몇 시간까지 멈춰도 됩니까"전체·차등 주기가 정해집니다

15분치를 잃어도 된다면 로그 백업을 15분마다 뜨면 됩니다. 복원 시간을 줄이려면 차등을 중간에 끼웁니다 — 전체 하나에 로그 96개를 얹는 것보다 전체 · 차등 · 로그 몇 개가 훨씬 빠릅니다.

SQL
-- 흔한 계획입니다.
--   일요일 새벽   전체
--   그 밖의 새벽  차등
--   15분마다      로그

-- 마지막 백업이 언제였는지 늘 확인할 수 있게 해 두십시오.
SELECT d.name AS DB, d.recovery_model_desc AS 복구모델,
       MAX(CASE WHEN b.type = 'D' THEN b.backup_finish_date END) AS 마지막전체,
       MAX(CASE WHEN b.type = 'I' THEN b.backup_finish_date END) AS 마지막차등,
       MAX(CASE WHEN b.type = 'L' THEN b.backup_finish_date END) AS 마지막로그
FROM sys.databases d
    LEFT JOIN msdb.dbo.backupset b ON b.database_name = d.name
WHERE d.database_id > 4
GROUP BY d.name, d.recovery_model_desc;
결과
DB복구모델마지막전체마지막로그
MssqlLabBakFULL2026-09-03 20:27:042026-09-03 20:27:06
MssqlLabSIMPLENULLNULL
NULL 이면 한 번도 백업하지 않은 것입니다. 실습용이니 그렇지만, 운영에서 이 줄이 보이면 큰일입니다.

FULL 로 바꿔 두고 로그 백업을 뜨지 않는 것이 가장 흔한 사고입니다. 로그가 지워지지 않아 계속 커지다가 디스크를 다 채우고 데이터베이스가 멈춥니다. log_reuse_wait_descLOG_BACKUP 이면 그 상태입니다(5.7).

연습

직접 해보기

1. 백업 상태를 점검하는 문장 난이도 하

백업이 제때 돌고 있는지 한눈에 보는 문장을 만들어 보세요. 며칠째 백업이 없거나, FULL 인데 로그 백업이 없는 것을 찾아야 합니다.

SELECT d.name AS DB, d.recovery_model_desc AS 복구모델, d.log_reuse_wait_desc AS 로그대기, DATEDIFF(hour, MAX(CASE WHEN b.type = 'D' THEN b.backup_finish_date END), SYSDATETIME()) AS 전체백업_몇시간전, DATEDIFF(minute, MAX(CASE WHEN b.type = 'L' THEN b.backup_finish_date END), SYSDATETIME()) AS 로그백업_몇분전, CASE WHEN MAX(CASE WHEN b.type = 'D' THEN b.backup_finish_date END) IS NULL THEN N'전체 백업이 한 번도 없습니다' WHEN d.recovery_model_desc = 'FULL' AND MAX(CASE WHEN b.type = 'L' THEN b.backup_finish_date END) IS NULL THEN N'FULL 인데 로그 백업이 없습니다' WHEN d.log_reuse_wait_desc = 'LOG_BACKUP' THEN N'로그 백업을 기다리는 중입니다' WHEN DATEDIFF(hour, MAX(CASE WHEN b.type = 'D' THEN b.backup_finish_date END), SYSDATETIME()) > 24 THEN N'전체 백업이 하루 넘게 없습니다' ELSE N'정상' END AS 상태 FROM sys.databases d LEFT JOIN msdb.dbo.backupset b ON b.database_name = d.name WHERE d.database_id > 4 AND d.state_desc = 'ONLINE' GROUP BY d.name, d.recovery_model_desc, d.log_reuse_wait_desc ORDER BY d.name; -- 이것을 SQL Server 에이전트 작업으로 하루 한 번 돌리고 -- '정상' 이 아닌 줄이 있으면 메일을 보내게 해 두십시오. -- msdb.dbo.backupset 은 계속 쌓이므로 주기적으로 지웁니다. EXEC msdb.dbo.sp_delete_backuphistory @oldest_date = '2026-06-01';
2. 한 표만 잘못 지웠습니다 난이도 중

운영 중인 데이터베이스에서 Board.COMMENTS 의 어제치 1,000행을 실수로 지웠습니다. 그 뒤로도 글과 댓글이 계속 들어오고 있습니다. 어떻게 되살리겠습니까.

운영 데이터베이스를 시점 복원하면 그 뒤에 들어온 정상 자료가 사라집니다. 복원할 곳이 꼭 원래 자리여야 합니까.
-- 하지 말 것 -- 운영 데이터베이스를 사고 직전으로 시점 복원 -- → 그 뒤에 들어온 정상 글과 댓글이 전부 사라집니다. -- 한 표를 살리려고 나머지를 죽이는 일입니다. -- 1. 먼저 지금 로그를 백업합니다. 무엇을 하든 이것이 첫걸음입니다. BACKUP LOG MAYYANET TO DISK = 'D:\backup\emergency.trn' WITH INIT; -- 2. 옆에 다른 이름으로 복원합니다. 운영은 그대로 돕니다. RESTORE DATABASE MAYYANET_recover FROM DISK = 'D:\backup\full.bak' WITH NORECOVERY, REPLACE, MOVE 'MAYYANET' TO 'D:\data\MAYYANET_recover.mdf', MOVE 'MAYYANET_log' TO 'D:\data\MAYYANET_recover_log.ldf'; RESTORE DATABASE MAYYANET_recover FROM DISK = 'D:\backup\diff.bak' WITH NORECOVERY; RESTORE LOG MAYYANET_recover FROM DISK = 'D:\backup\emergency.trn' WITH RECOVERY, STOPAT = '2026-09-03 14:29:00'; -- 사고 직전 -- 논리 이름(MAYYANET · MAYYANET_log)은 이렇게 확인합니다. -- RESTORE FILELISTONLY FROM DISK = 'D:\backup\full.bak'; -- 3. 필요한 행만 옮겨 옵니다. SET IDENTITY_INSERT MAYYANET.Board.COMMENTS ON; INSERT INTO MAYYANET.Board.COMMENTS (num, post_num, user_num, content, reg_date) SELECT r.num, r.post_num, r.user_num, r.content, r.reg_date FROM MAYYANET_recover.Board.COMMENTS r WHERE NOT EXISTS (SELECT 1 FROM MAYYANET.Board.COMMENTS c WHERE c.num = r.num); SET IDENTITY_INSERT MAYYANET.Board.COMMENTS OFF; -- NOT EXISTS 로 걸러야 그 사이에 새로 들어온 것과 부딪히지 않습니다. -- 번호가 겹치면 IDENTITY 값을 다시 잡아야 할 수도 있습니다. -- DBCC CHECKIDENT('Board.COMMENTS', RESEED); -- 4. 확인하고 복구용 데이터베이스를 지웁니다. DROP DATABASE MAYYANET_recover; -- 미리 해 둘 것 -- 이런 일이 실제로 되는지 평소에 한 번 해 보십시오. -- "백업은 뜨고 있는데 복원은 해 본 적이 없다" 가 가장 위험합니다. -- 복원 연습을 하면 걸리는 시간도 알게 됩니다.

실습에서 만든 것을 지웁니다.
ALTER DATABASE MssqlLabBak SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE MssqlLabBak;
백업 파일을 둔 폴더도 함께 정리하십시오.

요약
  • 복구 모델이 나머지를 정합니다. SIMPLE 이면 로그 백업 자체가 막히고(4208) 시점 복원도 못 합니다. 마지막 백업 이후는 전부 잃습니다.
  • 백업은 셋입니다 — 전체(그 시점 전부) · 차등(전체 백업 뒤 바뀐 것) · 로그(마지막 로그 백업 뒤의 기록).
  • 같은 데이터베이스에서 3.27MB · 1.27MB · 0.07MB 였습니다. 차등은 전체 백업이 오래될수록 커집니다.
  • 개발용으로 한 벌 뜰 때는 COPY_ONLY 를 붙이십시오. 차등의 기준점이 옮겨 가지 않습니다.
  • 사고가 나면 가장 먼저 지금 로그를 백업합니다. 그것이 있어야 사고 직전으로 갈 수 있습니다.
  • 복원은 전체 → 차등 → 로그 순이고, 마지막 하나만 RECOVERY 입니다. 중간에 붙이면 처음부터 다시 해야 합니다.
  • STOPAT 으로 그 시각까지만 적용합니다. 6행이 0행이 되었다가 6행으로 돌아왔습니다.
  • 운영을 덮어쓰지 말고 다른 이름으로 복원해 필요한 행만 옮기십시오. 사고 뒤에 들어온 정상 자료를 지키는 방법입니다.
  • FULL 로 바꿔 두고 로그 백업을 뜨지 않는 것이 가장 흔한 사고입니다. 로그가 디스크를 채우고 데이터베이스가 멈춥니다.
  • msdb.dbo.backupset 으로 마지막 백업이 언제였는지 늘 볼 수 있게 해 두십시오. 복원은 평소에 한 번 해 봐야 합니다.