데이터베이스 프로젝트
SSDT(.sqlproj)로 스크립트를 형상 관리에 둡니다.
지금 스키마가 어디에 있습니까
여기까지 만든 표·프로시저·뷰는 데이터베이스 안에만 있습니다. 그것이 문제입니다.
| 묻는 것 | 데이터베이스만 있으면 |
|---|---|
| 이 프로시저를 누가 언제 왜 고쳤습니까 | 알 수 없습니다 |
| 개발 서버와 운영 서버가 같습니까 | 하나씩 열어 봐야 합니다 |
| 지난달 상태로 되돌릴 수 있습니까 | 백업을 복원하는 수밖에 없습니다 |
| 새 서버에 똑같이 만들 수 있습니까 | 손으로 옮겨야 합니다 |
스키마도 코드입니다. C# 파일과 똑같이 파일로 두고 형상 관리에 넣어야 위 질문에 답할 수 있습니다.
SSDT 프로젝트
SSDT(SQL Server Data Tools) 는 데이터베이스를
Visual Studio 프로젝트로 다루게 해 줍니다. 확장자는
.sqlproj 입니다.
핵심은 개체 하나에 파일 하나입니다. 표
Board.POSTS 는
Board.POSTS.sql 에
CREATE TABLE 문 하나로 들어 있습니다.
ALTER TABLE 을 적지 않습니다.
MAYYANET/ ├─ MAYYANET.sqlproj -- 프로젝트 파일 ├─ MAYYANET.publish.xml -- 운영 게시 프로파일 ├─ TEST.publish.xml -- 시험 게시 프로파일 ├─ Schema Objects/ │ ├─ Database Level Objects/ │ │ └─ Security/Schemas/ -- Blog.sql · Member.sql … │ └─ Schemas/ │ ├─ Blog/ │ │ ├─ Tables/ -- Blog.POST.sql … │ │ └─ Programmability/ │ │ ├─ Stored Procedures/ │ │ │ ├─ 목록/ -- Blog.P_POST_LIST.sql … │ │ │ ├─ 조회/ │ │ │ ├─ 저장/ │ │ │ └─ 삭제/ │ │ └─ Sequence/ │ ├─ Member/ │ └─ … └─ Scripts/ ├─ Pre-Deployment/ -- 배포 전에 한 번 ├─ Post-Deployment/ -- 배포 뒤에 한 번 └─ Migration/ -- 손으로 한 번만 돌리는 것
| 무엇 | 개수 |
|---|---|
| 스키마 개체 파일 | 341 |
| 데이터베이스 수준 개체 | 8 |
| .sql 파일 전체 | 579 |
폴더 이름을 한글로 두어도 됩니다. 위 프로젝트는 프로시저를 목록 · 조회 · 저장 · 삭제로 나눠 두었습니다. 개체가 수백 개가 되면 이름만으로 찾기 어려워지므로 하는 일로 나누는 편이 낫습니다.
차이를 스스로 셈합니다
이것이 SSDT 의 핵심이자 가장 큰 차이점입니다. 배포할 때 "무엇을 바꿔라" 를 적지 않습니다. "이렇게 되어 있어야 한다" 만 적으면 도구가 지금 서버와 견주어 차이를 셈하고 필요한 문장을 만듭니다.
| 마이그레이션 방식 | SSDT (차이 기반) | |
|---|---|---|
| 적는 것 | 바꾸는 문장(ALTER) | 최종 모습(CREATE) |
| 파일이 쌓이는 방향 | 변경마다 새 파일 | 개체 파일 하나를 고침 |
| 지금 스키마를 보려면 | 전부 순서대로 읽어야 | 그 파일을 열면 됩니다 |
| 차이 문장 | 사람이 적습니다 | 도구가 만듭니다 |
| 되돌리기 | 되돌리는 스크립트를 적어 둠 | 파일을 되돌리고 다시 게시 |
-- Board.POSTS.sql — 열을 하나 더하고 싶다면 -- ALTER TABLE 을 적는 것이 아니라 이 CREATE 문을 고칩니다. CREATE TABLE Board.POSTS ( num int IDENTITY(1,1) NOT NULL, board_num int NOT NULL, title nvarchar(200) NOT NULL, view_count int NULL, -- ← 이 줄을 더합니다 CONSTRAINT PK_POSTS PRIMARY KEY CLUSTERED (num) );
덕분에 "지금 스키마가 무엇인가" 를 한 파일에서 볼 수 있습니다. 마이그레이션 방식은 변경 파일이 백 개 쌓이면 지금 모습을 알려고 백 개를 순서대로 읽어야 합니다.
차이 기반이라 조심할 것
도구가 만든 문장을 그대로 믿으면 안 됩니다. 6.3 에서 본 대로 비슷하게 생긴 변경이 값은 수백 배 다릅니다.
| 생기는 일 | 까닭 |
|---|---|
| 표를 통째로 다시 만듭니다 | 열 순서나 형식이 바뀌면 임시 표로 옮겼다 되돌립니다 |
| 자료를 잃을 수 있습니다 | 열을 지우거나 형식을 좁히면 그렇습니다 |
| 기본값을 채우느라 오래 걸립니다 | NOT NULL 열을 더하면 그렇습니다(6.3) |
그래서 배포 전에 스크립트를 만들어 읽어야 합니다. 게시 프로파일에 자료를 잃을 수 있으면 멈추도록 설정해 둘 수도 있습니다.
<!-- 자료를 잃을 수 있으면 배포하지 않습니다 --> <BlockOnPossibleDataLoss>True</BlockOnPossibleDataLoss> <!-- 프로젝트에 없는 개체를 지울지 --> <DropObjectsNotInSource>False</DropObjectsNotInSource> <!-- 배포 전 백업을 뜰지 --> <BackupDatabaseBeforeChanges>True</BackupDatabaseBeforeChanges> <!-- 한 트랜잭션으로 묶을지 (6.3 의 Sch-M 을 떠올리십시오) --> <IncludeTransactionalScripts>True</IncludeTransactionalScripts>
DropObjectsNotInSource 를 켤 때는
특히 조심하십시오. 프로젝트에 없는 것을 전부 지웁니다 — 누군가
운영에서 직접 만든 인덱스나 프로시저가 말없이 사라집니다.
반대로 끄면 지운 개체가 서버에 남습니다. 어느 쪽이든 배포
스크립트를 읽고 확인해야 합니다.
차이로 안 되는 것들
자료는 차이로 셈할 수 없습니다. 코드 목록을 채우거나 옛 자료를 옮기는 일은 사람이 적어야 합니다. 그래서 자리가 셋 있습니다.
| 자리 | 언제 돕니까 | 담는 것 |
|---|---|---|
| 배포 전(Pre-Deployment) | 차이 문장보다 먼저 | 미리 치워야 할 것 |
| 배포 후(Post-Deployment) | 차이 문장보다 나중 | 코드 목록·기본 자료 |
| 마이그레이션 | 사람이 한 번만 | 되돌릴 수 없는 자료 이동 |
PRINT N'마이그레이션을 시작합니다.'; GO -- 전화번호 입력의 국가번호 목록(멱등 — 이미 있으면 건너뜁니다). :r .\Common\Common.CODE_PHONECOUNTRY.sql -- 회원 탈퇴 사유 목록(멱등 — 이미 있으면 건너뜁니다). :r .\Common\Common.CODE_WITHDRAWREASON.sql -- 판매 제품·에디션·버전 첫 한 벌(멱등 — 이미 있으면 건너뜁니다). -- 그 뒤로는 관리자 화면이 원천이라 여기서 덮어쓰지 않습니다. :r .\Product\Product.PRODUCT.sql
-- 없으면 넣고 있으면 고칩니다(2.10 의 MERGE). MERGE Common.CODE_WITHDRAWREASON AS t USING (VALUES (1, N'서비스를 사용하지 않습니다'), (2, N'다른 서비스를 사용합니다'), (3, N'개인 정보가 걱정됩니다') ) AS s (num, name) ON t.num = s.num WHEN MATCHED AND t.name <> s.name THEN UPDATE SET name = s.name WHEN NOT MATCHED BY TARGET THEN INSERT (num, name) VALUES (s.num, s.name); -- 없을 때만 넣는 것이 나을 때도 있습니다. -- 관리자 화면에서 고칠 수 있는 자료라면 덮어쓰면 안 됩니다. IF NOT EXISTS (SELECT 1 FROM Product.PRODUCT) INSERT INTO Product.PRODUCT (…) VALUES (…);
배포 후 스크립트가 덮어써도 되는 자료인지 먼저 정하십시오.
코드 목록처럼 개발이 정하는 것은 MERGE 로
맞추고, 관리자 화면에서 고치는 자료는 처음 한 번만 넣고
그 뒤로는 건드리지 않습니다.
한 번만 돌려야 하는 것
이미 쌓인 자료를 고치는 일은 배포 스크립트에 넣지 않습니다. 따로 두고 사람이 한 번만 실행합니다.
2026.08.09_IDS_소셜통합.sql 2026.08.09_PHONE_E164.sql 2026.08.16_IBANK_CATE.sql 2026.08.19_EDITION_표기정리.sql 2026.08.19_LICENSE_키지문.sql 2026.08.19_LICENSE_폐기단계.sql
"한 번만" 이라도 멱등하게 적으십시오. 중간에 끊겨 다시 돌리는 일이 반드시 생깁니다(6.3). 위 파일도 "이미 채워진 행은 건드리지 않습니다" 라고 적혀 있습니다.
다른 데이터베이스를 가리킬 때
프로시저가 LOGS.Common.ADMIN 처럼
다른 데이터베이스를 가리키면, 프로젝트는 그 이름을 알지
못해 빌드 오류를 냅니다.
그 데이터베이스도 프로젝트로 두고 참조를 걸어 이름을 변수로 둡니다.
<ProjectReference Include="..\LOGS\LOGS.sqlproj"> <Name>LOGS</Name> <SuppressMissingDependenciesErrors>True</SuppressMissingDependenciesErrors> <DatabaseSqlCmdVariable>LOGS</DatabaseSqlCmdVariable> </ProjectReference> <SqlCmdVariable Include="LOGS"> <DefaultValue>LOGS</DefaultValue> </SqlCmdVariable>
변수로 두면 환경마다 다른 이름을 넣을 수 있습니다. 실제 게시 프로파일에는 이렇게 들어 있습니다.
| 프로파일 | TargetDatabaseName | $(LOGS) |
|---|---|---|
| MAYYANET.publish.xml | MAYYANET | LOGS |
| TEST.publish.xml | MAYYANET_TEST | LOGS_TEST |
같은 프로젝트로 운영과 시험에 게시할 수 있습니다. 프로파일만 갈아 끼우면 됩니다. 연결 문자열도 프로파일에 들어 있으니 운영 프로파일은 아무나 열지 못하게 두십시오.
시스템 개체(sys.databases 같은 것)를 가리키면
master.dacpac 을 참조로 걸어야 합니다. 위
프로젝트에도 ArtifactReference 로 들어 있습니다.
빌드하고 게시합니다
-- 빌드하면 .dacpac 하나가 나옵니다. 스키마 전체가 그 안에 들어 있습니다. msbuild MAYYANET.sqlproj /p:Configuration=Release -- 배포 스크립트만 만들어 봅니다. 실행하지는 않습니다. sqlpackage /Action:Script ^ /SourceFile:bin\Release\MAYYANET.dacpac ^ /Profile:TEST.publish.xml ^ /OutputPath:deploy.sql -- 읽어 보고 이상이 없으면 게시합니다. sqlpackage /Action:Publish ^ /SourceFile:bin\Release\MAYYANET.dacpac ^ /Profile:TEST.publish.xml -- 무엇이 다른지만 보고 싶을 때 sqlpackage /Action:DeployReport ^ /SourceFile:bin\Release\MAYYANET.dacpac ^ /Profile:TEST.publish.xml ^ /OutputPath:report.xml
반드시 /Action:Script 로 먼저 만들어
읽으십시오. 6.3 에서 본 위험한 변경이 섞여 있는지 그 자리에서
드러납니다. 읽지 않고 게시하는 것이 이 방식의 가장 큰
위험입니다.
차이 기반이라 "지금 서버가 어떤 상태인가" 에 따라 결과가 달라집니다. 개발 서버에서 만든 스크립트가 운영에서도 같으리라는 보장이 없습니다. 게시할 서버에서 스크립트를 다시 만들어 읽으십시오.
직접 해보기
MssqlLab 을 SSDT 프로젝트로 가져와 보세요.
이미 있는 데이터베이스에서 시작하는 방법을 적으십시오.
배포 스크립트를 만들어 읽어 보니 5천만 행짜리 표를 임시 표로 옮겼다 되돌리는 문장이 들어 있습니다. 무엇 때문이고 어떻게 하시겠습니까.
- 스키마도 코드입니다. 데이터베이스 안에만 있으면 누가 언제 왜 고쳤는지 알 수 없고, 되돌릴 수도 없습니다.
- SSDT 프로젝트(
.sqlproj)는 개체 하나에 파일 하나로 둡니다. 파일에는CREATE문 하나가 있고ALTER를 적지 않습니다. - 차이는 도구가 셈합니다. "이렇게 되어 있어야 한다" 만 적으면 지금 서버와 견주어 필요한 문장을 만듭니다.
- 그래서 지금 스키마를 한 파일에서 볼 수 있습니다. 마이그레이션 방식은 변경 파일을 순서대로 다 읽어야 합니다.
- 도구가 만든 문장을 그대로 믿지 마십시오. 열 순서 하나가 달라도 표를 통째로 다시 만들 수 있습니다(6.3).
- 자료는 차이로 셈할 수 없어 배포 전 · 배포 후 · 마이그레이션 세 자리에 사람이 적습니다.
- 배포 후 스크립트는 배포할 때마다 실행되므로 반드시 멱등해야 합니다.
MERGE로 맞추거나 없을 때만 넣습니다. - 다른 데이터베이스를 가리킬 때는 프로젝트 참조와 SqlCmd 변수로 둡니다. 환경마다 다른 이름을 게시 프로파일에서 넣습니다.
/Action:Script로 먼저 만들어 읽으십시오. "다시 만들 테이블" 과 "데이터 문제" 에 이름이 있으면 그대로 게시하면 안 됩니다.BlockOnPossibleDataLoss와DropObjectsNotInSource를 환경마다 다르게 두십시오. 운영에서는 막고, 시험에서는 열어 둘 수 있습니다.