SqlPackageやSQL Database ProjectsからDACPACを発行した際、BlockOnPossibleDataLossが原因で処理が止まっても、すぐにFalseへ変更してはいけません。
安全な進め方は、開発・UAT・本番で発行プロファイルを分離し、UATと本番ではTrueを維持することです。Falseを使うのは、原則としてデータを破棄できるローカル開発環境に限定します。型変更、精度の縮小、列の削除などが必要な場合は、保護機能の無効化ではなく、データの退避・検証・変換を含む移行手順を別途設計します。
BlockOnPossibleDataLossの既定値はTrueです。データ型の変換や精度の縮小など、データ損失につながる可能性があるスキーマ変更を検出すると、発行前の検証段階で処理を停止します。対象テーブルが空でも停止する点に注意が必要です。反対にFalseへ変更しても、既存値を新しいデータ型へ変換できなければ、実行中に失敗する可能性があります。([Microsoft Learn][1])
BlockOnPossibleDataLossで発行が止まる理由
SqlPackageは、DACPACに定義された理想のスキーマと、発行先データベースの現在のスキーマを比較します。その差分から配置計画を作成し、変更によってデータが失われる可能性がある場合に、BlockOnPossibleDataLoss=Trueが発行を停止します。([Microsoft for Developers][2])
代表的な変更は次のとおりです。
| スキーマ変更 | 想定される問題 |
|---|---|
| テーブルや列の削除 | 保存されているデータそのものが失われる |
INTからTINYINTへの変更 | 0未満または255を超える値を格納できない |
| 文字列の最大長を縮小 | 新しい最大長を超える文字列が切り捨てられる可能性がある |
DECIMALの精度や小数点以下桁数を縮小 | 桁あふれや丸めが発生する可能性がある |
| キャストが必要な型変更 | 既存値を新しい型へ変換できない可能性がある |
重要なのは、停止した時点で「実際にデータが失われることが確定した」とは限らないことです。SqlPackageは、スキーマ差分から損失の可能性がある変更を検出して停止します。
したがって、エラーを消すことよりも、どのオブジェクトのどの変更が警告対象なのかを特定することが先です。
テーブルが空でも発行が止まるのは仕様
BlockOnPossibleDataLoss=Trueでは、発行先にデータが存在するかどうかにかかわらず、損失の可能性があるスキーマ変更を検出すると処理が停止します。Microsoft Learnにも、対象データベースにデータが含まれていない場合でも、既定値のTrueでは処理を終了すると明記されています。([Microsoft Learn][1])
そのため、次のような状況でも止まることがあります。
- 作成したばかりのローカルデータベース
- 対象テーブルの行数が0件
- テストデータを削除した直後
- 実際には変換可能な値しか保存されていないテーブル
「テーブルが空だからエラーではない」と判断して、すべての環境でFalseにするのは危険です。同じ発行設定がUATや本番でも使われると、データが存在する環境で保護が働かなくなるためです。
一方、接続先が間違いなくローカル環境であり、データをいつでも作り直せる場合は、ローカル専用の発行プロファイルでFalseを使う余地があります。
Falseにすれば必ず発行できるわけではない
BlockOnPossibleDataLoss=Falseは、データ変換を成功させる設定ではありません。発行前の検証で処理を止めないようにする設定です。
既存値を新しい型に変換できなければ、配置計画の実行中に失敗します。たとえば、INT列をTINYINTへ変更するときに「300」が保存されていれば、Falseにして検証を通過させても、その値はTINYINTへ変換できません。([Microsoft Learn][1])
つまり、次の2つは別の問題です。
| 設定・処理 | 役割 |
|---|---|
BlockOnPossibleDataLoss=False | データ損失の可能性を理由とした事前停止を無効化する |
| データ移行処理 | 既存データを新しい型や構造へ安全に変換する |
Falseはデータ移行処理の代わりにはなりません。
本番を危険にしない基本方針
環境ごとに発行プロファイルを分け、保護レベルを明示的に管理します。同じDatabase Projectと同じDACPACであっても、開発用、UAT用、本番用に異なる発行プロファイルを作成できます。([Microsoft for Developers][2])
推奨方針は次のとおりです。
| 環境 | BlockOnPossibleDataLoss | 判断基準 |
|---|---|---|
| 個人のローカル開発DB | 必要に応じてFalse | データを破棄・再作成できる |
| 共有開発環境 | 原則True | 他の開発者が必要とするデータが存在する |
| UAT・ステージング | True | 本番相当のデータ構造で変更を確認する |
| 本番 | True | 一般的な発行操作で破壊的変更を通さない |
ローカル開発では速度を優先できますが、本番へ近づくほど変更内容を慎重に扱う必要があります。Microsoftの解説でも、ローカル開発用プロファイルでは保護を無効にし、UATや本番用プロファイルでは有効にする使い分けが示されています。([Microsoft for Developers][2])
開発用と本番用の発行プロファイルを分ける
発行プロファイルは、接続先や配置オプション、SQLCMD変数などを保存する.publish.xmlファイルです。複数のプロファイルをSQL Database Projectへ追加し、環境ごとに選択できます。([Microsoft Learn][3])
ローカル開発用プロファイルの例
次の例では、ローカル開発用としてBlockOnPossibleDataLossを無効化しています。
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetDatabaseName>SampleDb_Local</TargetDatabaseName>
<BlockOnPossibleDataLoss>False</BlockOnPossibleDataLoss>
<ProfileVersionNumber>1</ProfileVersionNumber>
</PropertyGroup>
</Project>
このプロファイルを利用できるのは、次の条件を満たす場合です。
- 接続先がローカル開発環境である
- 保存されているデータを破棄できる
- 必要ならデータベース全体を再作成できる
- UATや本番では同じプロファイルを使用しない
UAT・本番用プロファイルの例
UATと本番では、明示的にTrueを設定しておくと、設定意図が分かりやすくなります。
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetDatabaseName>SampleDb</TargetDatabaseName>
<BlockOnPossibleDataLoss>True</BlockOnPossibleDataLoss>
<ProfileVersionNumber>1</ProfileVersionNumber>
</PropertyGroup>
</Project>
SqlPackageでは、/Profile:または短縮形の/pr:で発行プロファイルを指定できます。
SqlPackage /Action:Publish /SourceFile:"Sample.Database.dacpac" /Profile:"Production.publish.xml"
コマンドラインで指定したプロパティは、発行プロファイル内の値を上書きします。そのため、本番プロファイルがTrueでも、共通のCI/CDジョブに/p:BlockOnPossibleDataLoss=Falseが残っていれば、保護が無効化されます。([Microsoft Learn][3])
CI/CDでは、次の点も確認してください。
Falseを共通の環境変数にしない- 開発と本番で別のジョブまたはテンプレートを使用する
.publish.xmlをソース管理し、変更をレビュー対象にする- 本番ジョブで
Falseを検出したら失敗させる - 接続先データベース名と使用プロファイルをログへ出力する
発行を再実行する前に確認する手順
対象環境と発行プロファイルを確認する
最初に確認すべきなのは、スキーマ変更そのものではなく、接続先と設定です。
次の項目を確認します。
- 発行先のサーバー名
- 発行先のデータベース名
- 使用しているDACPAC
- 選択している発行プロファイル
- コマンドラインで上書きしているプロパティ
- CI/CD変数から追加されるオプション
特に、開発用プロファイルを本番へ誤用していないか確認してください。
変更される列・テーブルを特定する
次に、Database Projectと発行先の差分を確認します。
SqlPackageには、実際に発行せず、配置計画をスクリプトとして生成するScriptアクションがあります。生成されたスクリプトを確認すれば、どのオブジェクトが変更される予定なのかを把握できます。([Microsoft Learn][4])
SqlPackage /Action:Script /SourceFile:"Sample.Database.dacpac" /TargetConnectionString:"接続文字列" /Profile:"Uat.publish.xml" /OutputPath:"deployment.sql"
まずはBlockOnPossibleDataLoss=Trueのままスクリプト生成を試します。
ここでも同じ検証エラーで停止する場合は、「保護を外して本番へ進める」という意味ではありません。Database Projectで変更したテーブル定義と、発行先の現在の定義を比較し、型、桁数、削除対象を特定します。
既存データが変換可能か確認する
型変更前には、実データを対象に変換可能性を確認します。
INTからTINYINTへ変更する場合
TINYINTへ格納できない値を抽出します。
SELECT
Id,
TargetValue
FROM dbo.SampleTable
WHERE TargetValue IS NOT NULL
AND (
TargetValue < 0
OR TargetValue > 255
);
1件でも返された場合は、そのまま型を変更できません。値の補正、除外、別列への退避などが必要です。
NVARCHARの長さを縮小する場合
NVARCHAR(100)からNVARCHAR(30)へ変更する場合は、30文字分を超えるデータを確認します。
SELECT
Id,
TargetText,
DATALENGTH(TargetText) AS DataLengthBytes
FROM dbo.SampleTable
WHERE DATALENGTH(TargetText) > 60;
NVARCHARは通常1単位あたり2バイトを使用するため、この例では60バイトを超える値を確認しています。
対象データを単純に切り捨てるのか、別の列やテーブルへ保存するのかを、業務要件に基づいて決めます。
キャスト可能性を確認する場合
文字列から数値への変更などでは、TRY_CONVERTを使って変換できない値を抽出できます。
SELECT
Id,
OldValue
FROM dbo.SampleTable
WHERE OldValue IS NOT NULL
AND TRY_CONVERT(decimal(10, 2), OldValue) IS NULL;
ただし、TRY_CONVERTが成功しても、桁数の縮小や小数点以下の丸めによって値が変わる可能性があります。変換結果との比較も行います。
SELECT
Id,
Amount,
TRY_CONVERT(decimal(10, 2), Amount) AS ConvertedAmount
FROM dbo.SampleTable
WHERE TRY_CONVERT(decimal(10, 2), Amount) IS NULL
OR Amount <> TRY_CONVERT(decimal(10, 2), Amount);
「変換できるか」と「値が変化しないか」は別々に確認する必要があります。
型変更は段階的なデータ移行として設計する
本番データが存在する列を縮小・変換するときは、列定義を直接書き換えるだけでは不十分です。
安全性を高めるには、次のような段階的な移行を検討します。
- 新しいデータ型の列を追加する
- 変換できないデータを抽出する
- データを補正または退避する
- 新しい列へデータを移行する
- 件数、NULL、範囲、業務上の整合性を確認する
- アプリケーションの参照先を新しい列へ切り替える
- 一定期間、旧列を保持する
- 旧列の削除を別の承認済み作業として実施する
この方法では、追加、データ移行、アプリケーション切替、削除を一度に実行しません。問題が起きたときに、どの工程で異常が発生したのかを判断しやすくなります。
同じ列名を維持しなければならない場合は、一時列への移行、テーブルの再構築、切替時の停止時間などを含めて設計します。単にBlockOnPossibleDataLoss=Falseを指定するだけでは、移行中の整合性やロールバック方法は確保できません。
列やテーブルを削除する場合の確認事項
列やテーブルの削除で停止した場合は、行数だけでなく、利用状況も確認します。
削除前に確認すべき項目は次のとおりです。
| 確認項目 | 確認内容 |
|---|---|
| データの保存要件 | 法令、監査、業務上の保存期間が残っていないか |
| アプリケーション | 現行アプリが列やテーブルを参照していないか |
| バッチ処理 | 夜間処理やETLが参照していないか |
| 帳票・BI | レポートや分析ツールが利用していないか |
| ストアドプロシージャ | 動的SQLを含めて依存していないか |
| ロールバック | 削除後に復元する手順と所要時間が明確か |
| バックアップ | 必要な時点へ復元可能か |
「現在の行数が0件」という理由だけで削除してはいけません。今後データが追加されるテーブルや、外部処理が一時的に使用するテーブルである可能性もあります。
本番では移行処理とDACPAC発行を分ける
本番で破壊的な変更が必要な場合は、次のように分離すると管理しやすくなります。
データ移行工程
個別にレビューしたSQLや移行プログラムで、次の処理を行います。
- 対象データの退避
- 不正値や範囲外データの抽出
- 新しい形式への変換
- 移行前後の件数確認
- 業務上の整合性確認
- 失敗時の復旧
スキーマ同期工程
データ移行と破壊的変更が完了し、発行先の状態がDatabase Projectと整合することを確認してから、BlockOnPossibleDataLoss=Trueの本番プロファイルで残りの変更を発行します。
この流れであれば、本番用プロファイルへ恒久的にFalseを保存する必要がありません。
SSMSの設定とSqlPackageの設定は別物
SSMSのテーブルデザイナーには、テーブルの再作成を必要とする変更を保存させないための「Prevent saving changes that require table re-creation」という設定があります。
この設定と、SqlPackageのBlockOnPossibleDataLossを混同してはいけません。
| 項目 | SqlPackage/SQL Database Projects | SSMSテーブルデザイナー |
|---|---|---|
| 設定 | BlockOnPossibleDataLoss | Prevent saving changes that require table re-creation |
| 主な場面 | DACPACの発行 | テーブルデザイナーでの保存 |
| 設定単位 | 発行プロファイルごとに分離可能 | SSMS全体の設定 |
| 環境分離 | 開発・UAT・本番で分けられる | サーバーやDBごとには分かれない |
| 主なリスク | 誤ったプロファイルやCLI上書き | 無効化したまま本番へ接続する |
SSMS側の設定は、特定のサーバーやデータベースだけに適用されるものではありません。ローカル開発のために無効化すると、その後UATや本番へ接続した場合も無効のままです。([Microsoft for Developers][2])
また、SSMSの設定を変更しても、SqlPackageの発行プロファイルにあるBlockOnPossibleDataLossは変更されません。逆に、発行プロファイルでFalseを設定しても、SSMSテーブルデザイナーの設定は変更されません。
片方の設定手順を、もう片方のエラー対処として使わないようにしてください。
よくある危険な対処
すべての発行プロファイルをFalseにする
開発時の停止を避けるために、UATや本番を含むすべてのプロファイルをFalseにすると、本番で誤った変更を止められなくなります。
環境ごとにプロファイルを分離してください。
空テーブルなので確認せずFalseにする
現在空であっても、接続先の取り違えや別環境での再利用が起こる可能性があります。
少なくとも、対象サーバー、データベース名、行数、利用中のプロファイルを確認します。
Falseをデータ変換機能だと考える
Falseは、値を補正したり、切り捨て前のデータを退避したりしません。
変換、退避、検証は別の処理として実装します。
SSMSの設定を変更して解決しようとする
SqlPackageの発行が止まっている場合、SSMSテーブルデザイナーの設定を変更しても解決しません。
使用している発行プロファイルまたはSqlPackageのプロパティを確認します。
本番プロファイルはTrueなので安全だと思い込む
コマンドラインの/p:プロパティは、発行プロファイルの値を上書きできます。
CI/CDのテンプレート、変数グループ、共通スクリプトにBlockOnPossibleDataLoss=Falseが残っていないか確認してください。
発行前の最終チェックリスト
本番へ進む前に、次の項目を確認します。
- 発行先のサーバー名とデータベース名が正しい
- 本番専用の発行プロファイルを使用している
BlockOnPossibleDataLoss=Trueになっている- コマンドラインから
Falseで上書きしていない - 変更対象の列、テーブル、データ型を特定した
- 既存値が新しい型へ変換できるか確認した
- 文字数超過、桁あふれ、丸めを確認した
- 削除対象データの保存要件を確認した
- データ移行とDACPAC発行を分離した
- バックアップと復旧手順を確認した
- 本番相当の環境で移行手順を確認した
- SSMSの全体設定と発行プロファイルを混同していない
BlockOnPossibleDataLossは解除するエラーではなく確認すべき警告
BlockOnPossibleDataLossで発行が止まったときは、Falseへ変更する前に、変更対象と既存データを確認してください。
破棄できるローカル開発環境では、専用の発行プロファイルでFalseを使う選択肢があります。一方、UATや本番ではTrueを維持し、型変更、精度縮小、列削除などは個別のデータ移行として設計するのが基本です。
まず行うべきことは、発行先とプロファイルを確認し、変更される列・テーブルを特定することです。そのうえで変換できない値や失われる値を抽出し、必要な退避・補正・段階移行を設計してください。
[1]: https://learn.microsoft.com/en-us/sql/tools/sqlpackage/sqlpackage-publish?view=sql-server-ver17 “SqlPackage Publish – SQL Server | Microsoft Learn”
[2]: https://devblogs.microsoft.com/azure-sql/blockonpossibledataloss/ “Your best friend: BlockOnPossibleDataLoss=True – Azure SQL Dev Corner”
[3]: https://learn.microsoft.com/ja-jp/sql/tools/sql-database-projects/concepts/publish-profiles?view=sql-server-ver17&utm_source=chatgpt.com “発行プロファイルの概要 – SQL Server | Microsoft Learn”
[4]: https://learn.microsoft.com/en-us/sql/tools/sqlpackage/sqlpackage?view=sql-server-ver17 “SqlPackage – SQL Server | Microsoft Learn”

コメント