ユーザーは、使用するソフトウェアの内容を気にしません。スムーズに、安全に、そして目立たないように機能するというだけです。開発者はそれを実現するために努力しており、解決しようとしている問題の1つは、データストアが現在のバージョンの製品に適した状態にあることを確認する方法です。ソフトウェアは進化し、そのデータモデルも時間の経過とともに変化する可能性があります。 設計ミス 。問題をさらに複雑にするために、さまざまなペースで製品の新しいバージョンに移行するテスト環境または顧客が多数存在する場合があります。ストアの構造と、光沢のある新しいバージョンを単一の観点から使用するために必要な操作を文書化するだけでは不十分です。
ペイパルのクレジットカード番号のハッキング
私はかつて、開発者によって直接、オンデマンドで更新された構造を持ついくつかのデータベースを使用してプロジェクトに参加しました。つまり、構造を最新バージョンに移行するために適用する必要のある変更を見つける明確な方法がなく、バージョン管理の概念もまったくありませんでした。これはDevOps以前の時代であり、今日では完全に混乱していると見なされます。特定のデータベースにすべての変更を適用するために使用されるツールを開発することにしました。移行があり、スキーマの変更を文書化します。これにより、偶発的な変更がなく、スキーマの状態が予測可能であると確信できました。
この記事では、リレーショナルデータベーススキーマの移行を適用する方法と、付随する問題を克服する方法について説明します。
まず、データベースの移行とは何ですか?この記事の文脈では、 移行 データベースに適用する必要のある一連の変更です。テーブル、列、またはインデックスの作成または削除は、移行の一般的な例です。スキーマの形状は、特に要件がまだあいまいなときに開発が開始された場合、時間の経過とともに劇的に変化する可能性があります。そのため、リリースに至るまでのいくつかのマイルストーンの過程で、データモデルは進化し、当初とは完全に異なったものになる可能性があります。移行は、ターゲット状態への単なるステップです。
開始するには、ツールボックスにあるものを調べて、すでにうまくいったことを再発明しないようにしましょう。
広く使用されているすべての言語には、データベースの移行を容易にするライブラリがあります。たとえば、Javaの場合、一般的なオプションは次のとおりです。 Liquibase そして フライウェイ 。例ではLiquibaseをさらに使用しますが、概念は他のソリューションにも適用され、Liquibaseに関連付けられていません。
一部のORMがスキーマを自動的にアップグレードして、マップされたクラスの構造と一致させるオプションをすでに提供しているのに、なぜわざわざ別のスキーマ移行ライブラリを使用するのでしょうか。実際には、このような自動移行は、テーブルや列の作成などの単純なスキーマ変更のみを行い、データベースオブジェクトの削除や名前の変更などの潜在的に破壊的なことはできません。したがって、移行ロジックを自分で説明する必要があり、データベースに何が起こるかを正確に把握しているため、通常は非自動(ただし自動化された)ソリューションの方が適しています。
また、手動の変更が間違った順序で適用されたり、必要な場合でもまったく適用されなかったりすると、一意で予測できないスキーマが生成される可能性があるため、自動スキーマ変更と手動スキーマ変更を混在させることも非常に悪い考えです。ツールを選択したら、それを使用してすべてのスキーマ移行を適用します。
一般的な移行には、シーケンス、テーブル、列、主キーと外部キー、インデックス、およびその他のデータベースオブジェクトの作成が含まれます。最も一般的なタイプの変更について、Liquibaseは何をすべきかを説明するための明確な宣言型要素を提供します。 Liquibaseや他の同様のツールでサポートされている些細な変更について読むのは退屈すぎるでしょう。チェンジセットがどのように見えるかを理解するために、テーブルを作成する次の例を検討してください(簡潔にするためにXML名前空間宣言は省略されています)。
createTable
ご覧のとおり、変更ログは変更セットのセットであり、変更セットは変更で構成されています。 UPDATE product SET code = 'new_' || code
のような単純な変更組み合わせて、より複雑な移行を実装できます。たとえば、すべての製品の製品コードを更新する必要があるとします。これは、次の変更で簡単に実現できます。
createTable
数え切れないほどの製品があると、パフォーマンスが低下します。移行をスピードアップするために、次の手順に書き直すことができます。
PRODUCT_TMP
を使用して製品の新しいテーブルを作成します。この段階では、できるだけ少ない制約を作成することをお勧めします。新しいテーブルにPRODUCT_TMP
という名前を付けましょう。INSERT INTO ... SELECT ...
sql
の形式のSQLを使用addNotNullConstraint
を使用する変化する。addUniqueConstraint
、addForeignKeyConstraint
、createIndex
)とインデックス(PRODUCT
)を作成します。PRODUCT_BAK
の名前を変更しますrenameTable
のようなテーブルに。 LiquibaseはPRODUCT_TMP
でそれを行うことができます。PRODUCT
〜renameTable
(ここでも、PRODUCT_BAK
を使用します)。dropTable
を削除します ...
で。もちろん、このような移行は回避することをお勧めしますが、必要なまれなケースの1つに遭遇した場合に備えて、それらを実装する方法を知っておくとよいでしょう。
XML、JSON、またはYAMLが変更を記述するタスクに対して奇妙すぎると考える場合は、 プレーンSQLを使用する データベースベンダー固有のすべての機能を利用します。また、実装することができます カスタムロジック プレーンJavaで。
Liquibaseが実際のデータベース固有のSQLの記述を免除する方法は、自信過剰につながる可能性がありますが、ターゲットデータベースの癖を忘れてはなりません。たとえば、外部キーを作成する場合、使用されている特定のデータベース管理システムに応じて、インデックスが作成される場合と作成されない場合があります。その結果、厄介な状況に陥る可能性があります。 Liquibaseを使用すると、変更セットを特定のタイプのデータベース(PostgreSQL、Oracle、MySQLなど)に対してのみ実行するように指定できます。これにより、ベンダー固有の構文と機能を使用して、異なるデータベースや他のチェンジセットに対して同じベンダーに依存しないチェンジセットを使用することが可能になります。次の変更セットは、Oracleデータベースを使用している場合にのみ実行されます。
IDX__Oracleに加えて、Liquibaseはいくつかをサポートしています 他のデータベース 箱から出して。
データベースオブジェクトの命名
作成するすべてのデータベースオブジェクトには名前を付ける必要があります。制約やインデックスなど、一部のタイプのオブジェクトの名前を明示的に指定する必要はありません。ただし、これらのオブジェクトに名前がないという意味ではありません。それらの名前はとにかくデータベースによって生成されます。この問題は、オブジェクトを参照してドロップまたは変更する必要がある場合に発生します。したがって、明示的な名前を付けることをお勧めします。しかし、どのような名前を付けるかについての規則はありますか?答えは短いです。一貫性を保つ。たとえば、次のようにインデックスに名前を付けることにした場合:CODE
、前述のIDX_PRODUCT_CODE
のインデックス列にはDATABASECHANGELOG
という名前を付ける必要があります。
cvvコードをバイパスする方法
命名規則は非常に物議を醸しているため、ここで包括的な説明を行うことは想定していません。一貫性を保ち、チームやプロジェクトの慣習を尊重するか、慣例がない場合はそれらを発明します。
チェンジセットの整理
最初に決定することは、チェンジセットをどこに保存するかです。基本的に2つのアプローチがあります。
- チェンジセットはアプリケーションコードと一緒に保管してください。 チェンジセットとアプリケーションコードを一緒にコミットしてレビューできるので、そうすると便利です。
- チェンジセットとアプリケーションコードを別々に保つ たとえば、個別のVCSリポジトリにあります。このアプローチは、データモデルが複数のアプリケーション間で共有される場合に適しており、すべての変更セットを専用のリポジトリに保存し、アプリケーションコードが存在する複数のリポジトリに分散しない方が便利です。
チェンジセットを保存する場所はどこでも、一般的に次のカテゴリに分類するのが妥当です。
設計定義の原則
- 実行中のシステムに影響を与えない独立した移行。 現在デプロイされているアプリケーションがまだそれらを認識していない場合は、通常、新しいテーブルやシーケンスなどを作成しても安全です。
- ストアの構造を変更するスキーマの変更 、たとえば、列とインデックスの追加または削除。これらの変更は、古いバージョンのアプリケーションがまだ使用されている間は適用しないでください。適用すると、スキーマの変更によりロックや奇妙な動作が発生する可能性があります。
- 少量のデータを挿入または更新するクイックマイグレーション。 複数のアプリケーションがデプロイされている場合、このカテゴリのチェンジセットは、データベースのパフォーマンスを低下させることなく同時に実行できます。
- 大量のデータを挿入または更新する移行が遅くなる可能性があります。 これらの変更は、他の同様の移行が実行されていないときに適用することをお勧めします。

これらの移行セットは、新しいバージョンのアプリケーションをデプロイする前に連続して実行する必要があります。システムが複数の個別のアプリケーションで構成されており、それらの一部が同じデータベースを使用している場合、このアプローチはさらに実用的になります。それ以外の場合は、実行中のアプリケーションに影響を与えずに適用できるチェンジセットのみを分離する価値があり、残りのチェンジセットは一緒に適用できます。
より単純なアプリケーションの場合、必要な移行のフルセットをアプリケーションの起動時に適用できます。この場合、すべてのチェンジセットは1つのカテゴリに分類され、アプリケーションが初期化されるたびに実行されます。
移行を適用するためにどの段階を選択した場合でも、複数のアプリケーションに同じデータベースを使用すると、移行が適用されているときにロックが発生する可能性があることに注意してください。 Liquibase(他の多くの同様のソリューションと同様)は、メタデータを記録するために2つの特別なテーブルを利用します:DATABASECHANGELOGLOCK
およびrunOnChange='true'
。前者は、適用されたチェンジセットに関する情報を格納するために使用され、後者は、同じデータベーススキーマ内での同時移行を防ぐために使用されます。したがって、何らかの理由で複数のアプリケーションが同じデータベーススキーマを使用する必要がある場合は、ロックを回避するために、メタデータテーブルにデフォルト以外の名前を使用することをお勧めします。
高レベルの構造が明確になったので、各カテゴリ内でチェンジセットを編成する方法を決定する必要があります。

特定のアプリケーション要件に大きく依存しますが、通常、次の点が妥当です。
- 製品のリリースごとにグループ化された変更ログを保持します。リリースごとに新しいディレクトリを作成し、対応する変更ログファイルをそのディレクトリに配置します。ルート変更ログを持ち、 含める リリースに対応する変更ログ。リリース変更ログには、このリリースを構成する他の変更ログを含めます。
- 変更ログファイルと変更セット識別子の命名規則を定め、もちろんそれに従ってください。
- 変更が多いチェンジセットは避けてください。単一の長いチェンジセットよりも複数のチェンジセットを優先します。
- ストアドプロシージャを使用していて、それらを更新する必要がある場合は、
createTable
の使用を検討してください。そのストアドプロシージャが追加されたチェンジセットの属性。それ以外の場合は、更新するたびに、新しいバージョンのストアドプロシージャを使用して新しいチェンジセットを作成する必要があります。要件はさまざまですが、そのような履歴を追跡しないことは許容されることがよくあります。 - 機能ブランチをマージする前に、冗長な変更を潰すことを検討してください。機能ブランチ(特に長寿命のブランチ)では、後のチェンジセットが前のチェンジセットで行われた変更を改良することがあります。たとえば、テーブルを作成してから、そのテーブルに列を追加することを決定できます。これらの列を最初の
context='test'
に追加する価値がありますこの機能ブランチがまだメインブランチにマージされていない場合は変更します。 - 同じ変更ログを使用して、テストデータベースを作成します。そうしようとすると、すべてのチェンジセットがテスト環境に適用できるわけではないこと、またはその特定のテスト環境に追加のチェンジセットが必要であることがすぐにわかる場合があります。 Liquibaseを使用すると、この問題は次の方法で簡単に解決できます。 コンテキスト 。
test
を追加するだけですテストでのみ実行する必要があるチェンジセットに属性を付け、rollback
でLiquibaseを初期化します。コンテキストが有効です。
ロールバック
他の同様のソリューションと同様に、Liquibaseはスキーマの「上」と「下」の移行をサポートしています。ただし、注意が必要です。移行を元に戻すのは簡単ではない可能性があり、必ずしも努力する価値があるとは限りません。アプリケーションの移行の取り消しをサポートすることにした場合は、一貫性を保ち、取り消す必要のあるすべての変更セットに対してそれを実行します。 Liquibaseでは、チェンジセットを元に戻すには、createTable
を追加します。ロールバックを実行するために必要な変更を含むタグ。次の例を考えてみましょう。
addColumn
Liquibaseは同じロールバックアクションを実行するため、明示的なロールバックはここでは冗長です。 Liquibaseは、サポートされているほとんどの種類の変更(createIndex
、DATABASECHANGELOG
、DATABASECHANGELOG
など)を自動的にロールバックできます。
過去を直す
完璧な人は誰もいませんし、私たちは皆間違いを犯します。それらのいくつかは、甘やかされて育った変更がすでに適用されている場合、発見が遅すぎる可能性があります。その日を救うために何ができるかを探りましょう。
データベースを手動で更新する
DATABASECHANGELOG
をいじる必要がありますおよびデータベースは次のようになります。
- 不良チェンジセットを修正して再実行する場合:
MD5SUM
から行を削除しますチェンジセットに対応します。 - チェンジセットによって導入されたすべての副作用を削除します。たとえば、テーブルが削除された場合はテーブルを復元します。
- 悪いチェンジセットを修正します。
- 移行を再度実行します。
- 悪いチェンジセットを修正したいが、それらを再度適用することをスキップしたい場合:
- 更新
NULL
MD5SUM
を設定するフィールド値をrunOnChange='true'
に不良チェンジセットに対応する行。 - データベースの誤りを手動で修正します。たとえば、間違ったタイプで追加された列がある場合は、クエリを発行してそのタイプを変更します。
- 悪いチェンジセットを修正します。
- 移行を再度実行します。 Liquibaseは新しいチェックサムを計算し、それを
context
に保存します。修正されたチェンジセットは再度実行されません。
明らかに、開発中にこれらのトリックを実行するのは簡単ですが、変更が複数のデータベースに適用されると、はるかに困難になります。
修正チェンジセットを書く
実際には、通常、このアプローチの方が適切です。元のチェンジセットを編集してみませんか?真実は、何を変更する必要があるかによって異なります。 Liquibaseは、各チェンジセットのチェックサムを計算し、以前に適用されたチェンジセットの少なくとも1つでチェックサムが新しい場合、新しい変更の適用を拒否します。この動作は、runOnChange
を指定することにより、チェンジセットごとにカスタマイズできます。属性。変更してもチェックサムは影響を受けません 前提条件 またはオプションのチェンジセット属性(context
、context='graveyard-changesets-never-run'
など)。
さて、あなたは疑問に思うかもしれません、あなたはどのようにして最終的に間違いでチェンジセットを修正するのですか?
不和チャットボットの作り方
- これらの変更を新しいスキーマに引き続き適用する場合は、修正チェンジセットを追加するだけです。たとえば、間違ったタイプで追加された列があった場合は、新しいチェンジセットでそのタイプを変更します。
- これらの悪いチェンジセットが存在しなかったふりをしたい場合は、次のようにします。
- チェンジセットを削除するか、
changeSetExecuted
を追加しますのように、そのようなコンテキストで移行を二度と適用しようとしないことを保証する値を持つ属性。 - 間違ったことを元に戻すか、修正する新しいチェンジセットを追加します。これらの変更は、悪い変更が適用された場合にのみ適用する必要があります。これは、
|_+_|
などの前提条件で実現できます。なぜそうしているのかを説明するコメントを追加することを忘れないでください。 - スキーマを正しい方法で変更する新しいチェンジセットを追加します。
ご覧のとおり、過去を修正することは可能ですが、必ずしも簡単ではない場合があります。
増大する痛みの緩和
アプリケーションが古くなると、その変更ログも大きくなり、パスに沿ってすべてのスキーマ変更が蓄積されます。これは仕様によるものであり、本質的に問題はありません。製品の各バージョンをリリースした後など、定期的に移行を破棄することで、長い変更ログを短くすることができます。場合によっては、新しいスキーマの初期化が速くなります。

押しつぶしは必ずしも些細なことではなく、多くの利点をもたらさずに回帰を引き起こす可能性があります。もう1つの優れたオプションは、シードデータベースを使用して、すべての変更セットの実行を回避することです。データベースをできるだけ早く準備する必要がある場合は、テストデータがある場合でも、テスト環境に適しています。これは、チェンジセットの押しつぶしの一形態と考えることができます。ある時点で(たとえば、別のバージョンをリリースした後)、スキーマのダンプを作成します。ダンプを復元した後、通常どおり移行を適用します。ダンプを作成する前に古い変更がすでに適用されているため、新しい変更のみが適用されます。したがって、それらはダンプから復元されました。

結論
Liquibaseの機能を深く掘り下げることを意図的に避けて、一般的に進化するスキーマに焦点を当てた、短くて要点のある記事を提供しました。うまくいけば、データベーススキーマ移行の自動化されたアプリケーションによってどのようなメリットと問題がもたらされ、すべてがどの程度適合するかが明確になります。 DevOpsカルチャー 。良いアイデアでさえ教義に変えないことが重要です。要件はさまざまです。 データベースエンジニア 、私たちの決定は、インターネット上の誰かからの推奨事項に従うだけでなく、製品を前進させることを促進する必要があります。
基本を理解する
データベースのスキーマの意味は何ですか?
データベーススキーマは、データがデータベース内でどのように編成されているかを記述します。
データベースとスキーマの違いは何ですか?
スキーマはデータベースの一部です。データベースは通常、1つ以上のスキーマで構成されています。また、DBMSをデータベースと呼ぶことは非常に一般的です。通常、データコンテナについて話しているのか、そのコンテナを管理するシステムについて話しているのかは、コンテキストから明らかです。
スキーマの例は何ですか?
ツアー管理アプリケーションを構築している場合、そのデータベーススキーマには、航空会社、フライト、都市などのエンティティが含まれます。ユーザー定義のスキーマに加えて、DBMSには通常、設定とメタデータのクエリに使用できる「情報スキーマ」があります。
民間不動産投資ファンド
さまざまな種類のデータベースは何ですか?
リレーショナルデータベースの他に、オブジェクトデータベース、ドキュメント指向データベース、階層型データベースがあります。
クエリの実行を高速化する方法はありますか?
遅いクエリを再構築するか、スキーマを変更することで、クエリを最適化できます。通常、DBMSは実行プランを提供し、クエリの速度を低下させている原因を推測するのに役立ちます(たとえば、インデックスを使用しない、サブクエリでデータを選択しすぎるなど)。 PostgreSQLでは、EXPLAINまたはEXPLAIN ANALYZEを使用して、何が問題なのかを理解できます。
PostgreSQLのスキーマは何ですか?
PostgreSQLでは、同じクラスター内に複数のデータベースがある場合があります。スキーマはデータベースの構造要素です。これは、テーブル、ビュー、プロシージャなどのコンテナです。スキーマはデータベース内のディレクトリと見なすことができますが、スキーマに他のスキーマを含めることはできません。