데이터베이스 B의 스키마를 A로 동기화해야 하는 상황이 생겼다. DB를 직접 건드리기 전에 Docker로 MSSQL 환경을 구성하고, 'visual studio', 'vs code'에서 Schema Compare 기능을 테스트해보기로 했다.
version: '3.8'
services:
mssql-a:
image: mcr.microsoft.com/mssql/server:2022-latest
container_name: mssql-a
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "YourStrong!Pass123"
ports:
- "1433:1433" # Target (동기화 대상)
mssql-b:
image: mcr.microsoft.com/mssql/server:2022-latest
container_name: mssql-b
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "YourStrong!Pass123"
ports:
- "1434:1433" # Source (동기화 원본)
두 컨테이너가 올라오면 각각 독립된 MSSQL 서버로 동작한다.
| 역할 | 컨테이너 | 포트 | 설명 |
|---|---|---|---|
| Source (B) | mssql-b | 1434 | 최신 스키마 (동기화 원본) |
| Target (A) | mssql-a | 1433 | 이전 스키마 (동기화 대상) |
스키마 비교에서 발생할 수 있는 주요 케이스를 포함하도록 설계했다.
-- 컬럼이 추가/변경된 테이블
CREATE TABLE Users (
Id INT PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(100) NOT NULL, -- A는 NVARCHAR(50)
Email NVARCHAR(200) NOT NULL,
Phone NVARCHAR(20) NULL, -- A에 없음
Status NVARCHAR(20) DEFAULT 'ACTIVE', -- A에 없음
CreatedAt DATETIME2 DEFAULT GETDATE(), -- A에 없음
UpdatedAt DATETIME2 NULL -- A에 없음
);
-- B에만 존재하는 신규 테이블
CREATE TABLE Orders ( ... );
CREATE TABLE OrderItems ( ... );
CREATE TABLE AuditLog ( ... );
-- B에만 존재하는 뷰, 프로시저, 인덱스
CREATE VIEW vw_OrderSummary AS ...
CREATE PROCEDURE sp_GetUserOrders AS ...
CREATE INDEX IX_Users_Email ON Users(Email);
-- 컬럼이 적은 이전 버전
CREATE TABLE Users (
Id INT PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(50) NOT NULL, -- B는 100
Email NVARCHAR(200) NOT NULL
-- Phone, Status, CreatedAt, UpdatedAt 없음
);
-- A에만 존재하는 레거시 테이블
CREATE TABLE LegacyConfig (
Id INT PRIMARY KEY IDENTITY(1,1),
ConfigKey NVARCHAR(100) NOT NULL,
ConfigValue NVARCHAR(500) NULL
);
| 구분 | 내용 | 비교 결과 |
|---|---|---|
| Users.Name | NVARCHAR(50) → NVARCHAR(100) | 변경 |
| Users.Phone 외 4개 컬럼 | A에 없음 | 추가 |
| Orders, OrderItems, AuditLog | A에 없음 | 신규 테이블 |
| vw_OrderSummary | A에 없음 | 신규 뷰 |
| sp_GetUserOrders | A에 없음 | 신규 프로시저 |
| 인덱스 5개 | A에 없음 | 신규 인덱스 |
| LegacyConfig | B에 없음 | 삭제 대상 |
Schema Compare 기능을 사용하려면 Visual Studio에 SQL Server Data Tools (SSDT)가 설치되어 있어야 한다.
Visual Studio 2022를 실행한다.

상단 메뉴에서 도구(T) 탭을 클릭한다. 여기서 SQL Server 하위 메뉴가 보이지 않는다면 SSDT가 설치되지 않은 것이다.

Visual Studio Installer에서 데이터 스토리지 및 처리 워크로드를 선택하고, 오른쪽 설치 세부 정보에서 SQL Server Data Tools가 체크되어 있는지 확인한다.

도구(T) → SQL Server(Q) → 새 스키마 비교(N) 를 선택한다.

스키마 비교 화면이 열리면 왼쪽의 원본 선택 드롭다운을 클릭하고 원본 선택... 을 클릭한다.

원본 스키마 선택 대화상자에서 데이터베이스(D) 를 선택하고 연결 선택... 버튼을 클릭한다.

연결 대화상자에서 다음과 같이 입력한다.

| 항목 | 값 |
|---|---|
| 서버 이름 | localhost,1434 |
| 인증 | SQL Server 인증 |
| 사용자 이름 | sa |
| 암호 | YourStrong!Pass123 |
| 데이터베이스 이름 | SchemaTestDB |
| 암호화 | 선택 사항(False) |
| 서버 인증서 신뢰 | True |
암호화와 서버 인증서 신뢰 설정이 필요한 이유:
Docker MSSQL 2022는 자체 서명 인증서(self-signed)를 사용한다. 공인 기관이 발급한 인증서가 아니므로, 기본값(암호화=필수)으로 연결하면 인증서 검증에 실패한다. 로컬 테스트 환경에서는 암호화를 선택 사항(False)로 변경하거나, 서버 인증서 신뢰를 True로 설정하면 된다.
Source 연결이 완료되면 오른쪽 대상 선택도 동일한 방식으로 설정한다. 서버 이름만 localhost,1433으로 변경하면 된다.

양쪽 연결이 완료되면 상단의 비교 버튼을 클릭한다. 비교 결과가 세 가지 카테고리로 표시된다.

| 카테고리 | 내용 |
|---|---|
| 삭제 | B에 없고 A에만 있는 객체 (LegacyConfig) |
| 변경 | 양쪽 모두 존재하지만 정의가 다른 객체 (Users) |
| 추가 | B에 있고 A에 없는 객체 (Orders, AuditLog 등) |
하단 패널에서 선택한 객체의 스키마 차이를 좌우로 비교하여 확인할 수 있다.
비교 결과를 확인한 후 상단의 스크립트 생성 버튼을 클릭하면, SSDT가 B의 스키마를 A에 적용하기 위한 SQL 스크립트를 자동으로 생성한다.

생성된 스크립트에는 다음과 같은 내용이 포함된다:
-- [dbo].[LegacyConfig] 테이블을 삭제하고 있습니다.
-- 테이블에 데이터가 들어 있으면 배포가 중단됩니다.
IF EXISTS (select top 1 1 from [dbo].[LegacyConfig])
RAISERROR (N'행이 발견되었습니다. 데이터가 손실될 수 있으므로
스키마 업데이트가 종료됩니다.', 16, 127) WITH NOWAIT
스크립트를 실행하면 즉시 오류가 발생한다.

메시지 50000, 수준 16, 상태 127, 줄 48
행이 발견되었습니다. 데이터가 손실될 수 있으므로 스키마 업데이트가 종료됩니다.
** 일괄 처리를 실행하는 동안 오류가 발생했습니다. 종료하고 있습니다.
SSDT의 안전장치가 작동한 것이다. LegacyConfig 테이블에 데이터(2건)가 존재하는 상태에서 테이블을 삭제하면 데이터가 영구적으로 손실된다. SSDT는 이런 상황을 감지하고 스크립트 실행을 자동으로 중단한다.
이것은 버그가 아니라 의도된 동작이다. 운영 환경에서 실수로 데이터가 있는 테이블을 날리는 사고를 방지해준다.
Schema Compare 화면으로 돌아가서 삭제 카테고리의 LegacyConfig 테이블 체크박스를 해제한다. 이렇게 하면 해당 테이블은 동기화 대상에서 제외된다.

B에 없다고 A의 테이블을 무조건 삭제하는 것이 아니라, 삭제 여부를 판단해야 한다.
체크박스를 해제한 상태에서 다시 스크립트 생성 → 실행하면 성공한다.

하단 메시지 탭에서 실행된 작업을 확인할 수 있다:
테이블 [dbo].[Members]을(를) 변경하는 중...
인덱스 [dbo].[Members].[IX_Members_Grade]을(를) 만드는 중...
인덱스 [dbo].[Members].[IX_Members_LoginId]을(를) 만드는 중...
테이블 [dbo].[Payments]을(를) 변경하는 중...
테이블 [dbo].[Products]을(를) 변경하는 중...
테이블 [dbo].[Users]을(를) 변경하는 중...
인덱스 [dbo].[Users].[IX_Users_Email]을(를) 만드는 중...
테이블 [dbo].[AuditLog]을(를) 만드는 중...
테이블 [dbo].[Orders]을(를) 만드는 중...
...
동기화가 완료된 후 다시 비교 버튼을 클릭하면 결과가 달라져 있다.

| 카테고리 | 동기화 전 | 동기화 후 |
|---|---|---|
| 삭제 | LegacyConfig | LegacyConfig (체크 해제 상태 유지) |
| 변경 | Users | 없음 |
| 추가 | Orders, OrderItems, AuditLog, 뷰, 프로시저 | 없음 |
변경과 추가 항목이 모두 사라졌다. LegacyConfig만 남아있는데 이는 의도적으로 제외한 것이므로 정상이다.
스키마 비교 화면에서 톱니바퀴 아이콘을 클릭하면 비교 옵션을 설정할 수 있다.
Visual Studio 없이도 VS Code만으로 스키마 비교가 가능하다. Microsoft의 공식 SQL Server (mssql) 확장이 Schema Compare를 정식 지원하고 있다.


