7 シャード・データベースへの移行
Oracle Shardingでは、データベースをOracle Sharding構成に移行するためのいくつかのツールおよびガイドラインが提供されています。
- シャーディング・アドバイザ・データベース移行ツールの使用
シャーディング・アドバイザは、ワークロードおよびデータベース・スキーマを分析して最も有効なOracle Sharding構成を推奨することにより、既存の非シャードのOracleデータベースからシャード・データベースへの移行を容易にします。 - シャード・データベースへのデータの移行
非シャード・データベースからOracle Shardingシャード・データベースにデータを移行する際には、ここで説明する方法を使用してください。
シャーディング・アドバイザ・データベース移行ツールの使用
シャーディング・アドバイザは、ワークロードおよびデータベース・スキーマを分析して最も有効なOracle Sharding構成を推奨することにより、既存の非シャードのOracleデータベースからシャード・データベースへの移行を容易にします。
この項には次のトピックがあります。
- シャーディング・アドバイザについて
シャーディング・アドバイザについて、必要な理由およびその仕組みについて説明します。 - シャード・アドバイザの実行
推奨されるOracle Sharding構成を取得するには、シャーディング・アドバイザのコマンドライン・ツールを既存の非シャードのOracle Databaseに対して実行します。 - 本番以外のシステムでのシャーディング・アドバイザの実行
稼働中の本番システムへの影響を最小限に抑えるには、本番システムとは別のサーバーに配置されたデータベース・スキーマおよびワークロードのコピーに対して、シャーディング・アドバイザを実行します。 - シャーディング・アドバイザの出力の確認
シャーディング・アドバイザは、問合せワークロードから抽出したシャーディングの候補となる各列の表ファミリを検出し、問合せ分類ルールとランキング・アルゴリズムに基づいて表ファミリをランク付けします。 - シャーディング・アドバイザの推奨構成の選択
シャード・データベースに選択する構成を決定する際に、考慮する必要があるデータベース・シャーディングのいくつかの特性があります。
親トピック: シャード・データベースへの移行
シャーディング・アドバイザについて
シャーディング・アドバイザの概要、必要な理由およびその仕組みについて説明します。
シャーディング・アドバイザによる分析は、次のことを行う場合に必要となる情報をユーザーに提供します。
- 問合せワークロードのパフォーマンスの最大化
- シャード間結合を必要とするマルチシャード操作の最小化
- 複合問合せの並列化の最大化(すべてのシャードに問合せ実行を分散します)
- 各シャードの重複データの量の最小化
シャーディング・アドバイザは、Oracle Sharding環境への移行を検討している10g以上のリリースの本番の非シャードのOracle Databaseに対して実行する、クライアント側のコマンドライン・ツールです。
シャーディング・アドバイザはスタンドアロン・ツールとしてインストールされ、認証されたOCI接続を使用してデータベースに接続します。
スキーマおよびその他の優先事項を理解するために、シャーディング・アドバイザは対話型ダイアログの一部としてユーザーにいくつかの質問を行います。
次に、シャーディング・アドバイザは、ソースとも呼ばれる既存の非シャード・データベースに接続し、そのスキーマおよび問合せワークロードを分析して、シャード・データベースの一連の代替設計を生成します。この設計には、効率的なシャーディング・キーの推奨、シャーディングする表、およびすべてのシャードで重複表にする表が含まれています。
シャーディング構成は問合せパフォーマンスの観点でランク付けされます。ランキングで優先される構成は、シャード間結合を必要としない単一のシャードの問合せおよびマルチシャード問合せを最大化し、シャード間結合を必要とするマルチシャード問合せを最小化する構成です。
要件に最適な設計を選択します。設計はアドバイザによってランク付けされるため、特定の優先事項がない場合は、最もランクの高い設計をデフォルトで選択できます。
ノート:
シャーディング・アドバイザ機能には制限があります。ソース・データベースは、Oracle Database 10g以上のリリースである必要があります。
稼働中の本番データベースに対してシャーディング・アドバイザを実行できない場合は、本番データベースからインポートされたスキーマとワークロードがある別のサーバーでシャーディング・アドバイザを実行できます。
シャーディング・アドバイザは、主キーと外部キーの関係に基づいて表ファミリを検出します。スキーマに主キーと外部キーの制約がない場合は、PARENT
句によるシャーディングをお薦めします。
現在、シャーディング・アドバイザは、ソース・データベースに外部キー制約がある場合、単一表ファミリのシステム管理のシャーディング(参照によるシャーディング)構成のみを推奨します。それ以外の場合、シャーディング・アドバイザはPARENT
句を使用したシャーディングを推奨します。
親トピック: シャーディング・アドバイザ・データベース移行ツールの使用
シャーディング・アドバイザの実行
推奨されるOracle Sharding構成を取得するには、シャーディング・アドバイザのコマンドライン・ツールを既存の非シャードのOracle Databaseに対して実行します。
次に示すように、コマンドラインからシャーディング・アドバイザを実行します。
$ gwsadv -u username -p password -c –w sch=(schema1,schema2
ここで、-u
および-p
は、シャーディング・アドバイザを実行するユーザーのユーザー名およびパスワードです。
ソースのGV$SQL_PLAN_STATISTICS_ALL
ビューから述語情報を取得するには、既存の問合せワークロードに対してシャーディング・アドバイザを初めて実行するときに、ワークロード取得パラメータ-c
を使用します。同じワークロードの後続の問合せで-c
を使用する必要はありません。
必須の-w
フラグは、シャーディング・アドバイザがシャーディング構成の生成およびランキングのために問合せワークロードを使用することを示します。
この場合、シャーディング・アドバイザを実行するスキーマのリストが、sch
パラメータで指定されています。シャーディング・アドバイザで使用できるその他のオプションについては、シャーディング・アドバイザの使用方法およびオプションで説明されています。
親トピック: シャーディング・アドバイザ・データベース移行ツールの使用
非本番システムでのシャーディング・アドバイザの実行
稼働中の本番システムへの影響を最小限に抑えるには、本番システムとは別のサーバーに配置されたデータベース・スキーマおよびワークロードのコピーに対して、シャーディング・アドバイザを実行します。
稼働中の本番システムと同様の結果を得るために、Oracle Data Pumpユーティリティを使用して本番データベースのスキーマおよびワークロードをエクスポートし、別のサーバーにコピーできます。その後、インポートされたスキーマに対してシャーディング・アドバイザを実行できます。
データベース・スキーマおよびシステム表のみをエクスポートします。実際のデータをエクスポートする必要はありません。
次の手順では、例としてHRスキーマを使用します。
ソース(本番)データベース・サーバーで次のステップを実行します。
ターゲット・データベース・サーバーで次のステップを実行します。
親トピック: シャーディング・アドバイザ・データベース移行ツールの使用
シャーディング・アドバイザの出力の確認
シャーディング・アドバイザは、問合せワークロードから抽出したシャーディングの候補となる各列の表ファミリを検出し、問合せ分類ルールとランキング・アルゴリズムに基づいて表ファミリをランク付けします。
シャーディング・アドバイザを実行しているユーザーが所有するシャーディング構成および関連情報を確認するには、ソース・データベースと同じスキーマに格納されている次の出力データベース表を問い合せることができます。
SHARDINGADVISOR_CONFIGURATIONS
には、ランク付けされたシャード構成内の表ごとに1つの行があり、各表の詳細が含まれています(シャード表または重複表のどちらにするか、およびシャードの場合、表ファミリ階層内のレベル、その親表、ルート表のシャーディング・キー、外部キー参照制約、シャードごとの推定サイズなど)。SHARDINGADVISOR_CONFIGDETAILS
には、ランク付けされたシャーディング構成ごとに1つの行があり、シャード表の数とシャードごとの累積サイズ、および重複表の数と累積サイズなどのランク付けされた各シャーディング構成の詳細が含まれています。また、ソース・データベースの現在のワークロードおよび見積りコストに基づく、本番で予想される単一のシャード問合せおよびマルチシャード問合せの数、およびシャード間結合を必要とするマルチシャード問合せの数も含まれています。SHARDINGADVISOR_QUERYTYPES
は、ワークロードの各問合せに関して、各シャーディング構成の問合せタイプをリストします。シャーディング構成に応じて、同じ問合せが異なる問合せタイプになることがあります。
シャーディング・アドバイザの出力は、通常のデータベース表に含まれているため、各種のSQL問合せを実行して様々な観点から出力を参照できます。
たとえば、シャーディング構成をランキング順序で表示するには、次のコマンドを実行します
SELECT rank, tableName as tname, tabletype as type,
tablelevel as tlevel, parent, shardby as shardBy,
shardingorreferencecols as cols, unenforceableconstraints,
sizeoftable
FROM SHARDINGADVISOR_CONFIGURATIONS
ORDER BY rank, tlevel, tname, parent;
シャーディング・アドバイザの出力表の詳細と問合せの例は、「シャーディング・アドバイザの出力表」および「シャーディング・アドバイザの出力を確認するためのSQLの例」を参照してください
親トピック: シャーディング・アドバイザ・データベース移行ツールの使用
シャーディング・アドバイザの推奨構成の選択
シャード・データベースに選択する構成を決定する際に、考慮する必要があるデータベース・シャーディングのいくつかの特性があります。
シャードの数を増やすと、シャード・データベースの可用性とスケーラビリティが向上します。
重複したデータを最小化すると、複数シャードを結合する必要があるマルチシャード問合せを最小限にするニーズと競合する可能性があります。通常、シャード・データベースの結合は関連するデータに対して実行されるため、同じシャード内に関連するデータを格納すると、このような結合の実行が劇的に速くなることがあります。
問合せワークロードに関して、推奨されるシャーディング構成の全体のコストは、ワークロードの各問合せタイプ(単一シャード、マルチシャード、シャード間結合を使用するマルチシャード)の数に基づいています。ここでは、クロスシャード結合を使用するマルチシャード問合せのコストが最も高く、単一シャード問合せのコストが最も低くなります。コスト情報は、シャーディング・アドバイザのSHARDINGADVISOR_CONFIGDETAILS
出力表のCOST
列にあります。
親トピック: シャーディング・アドバイザ・データベース移行ツールの使用
シャード・データベースへのデータの移行
非シャード・データベースからOracle Shardingシャード・データベースにデータを移行する際には、ここで説明する方法を使用してください。
ここに示すデータのロード方法は、シャード・データベースに移行する時点で、非シャードOracleデータベースを使用していることを想定しています。また、ここで提案する方法は、その他のデータベース・システムからのデータの移行や、新規のデータベース・ユーザーにも適用されます。
- シャード・データベースへのデータの移行について
Oracle Shardingソフトウェアのインストール後に、シャード・データベースを構成して作成すると、シャード・データベースにデータを移行できるようになります。 - シャード・データベースへのデータのロードに関する一般的なガイドライン
非シャード・データベースからシャード・データベースに移行するときには、非シャード表のデータをシャード表と重複表に移動する必要があります。非シャード表から重複表にデータを移動するときには複雑なことはありませんが、非シャード表からシャード表にデータを移動するときには特別な注意が必要です。 - スキーマの移行
既存のデータベースをシャード・データベースに移行する前に、シャード・データベースの編成方法を決定する必要があります。 - ソース・データベースの準備
シャード・データベース・スキーマへの移行を円滑に進めるために、ターゲットのシャード・データベースの表定義と一致するように、ソースの非シャード・データベースを変更できます。 - ターゲット・シャード・データベースの準備
シャード・データベースへのデータの移行を開始する前に、シャード・データベース・スキーマを設計に従って作成する必要があります。 - データの移行
ターゲット・シャード・データベースを作成してシャード表と重複表を定義したら、シャード・データベースへのデータの移行を開始できます。 - アプリケーションの移行
シャード・データベースのオペレーティング環境では、アプリケーションはシャードに直接アクセスできるようになります。この機能により、本当の線形スケーラビリティが得られますが、アプリケーション・コードへのわずかな変更という少しの手間がかかります。
親トピック: シャード・データベースへの移行
シャード・データベースへのデータの移行について
Oracle Shardingソフトウェアのインストール後に、シャード・データベースを構成して作成すると、シャード・データベースにデータを移行できるようになります。
次に、シャード・データベース環境に移行する際のステップの概要を示します。
- シャード・データベース・スキーマを設計して作成します。
- データを移行します。
- アプリケーションを移行します。
関連項目:
シャード・データベース・アプリケーションへの移行に関する制約について理解するには、既存のアプリケーションのシャーディングに対する適合性を参照してください。
親トピック: シャード・データベースへのデータの移行
シャード・データベースへのデータのロードに関する一般的なガイドライン
非シャード・データベースからシャード・データベースに移行するときには、非シャード表のデータをシャード表と重複表に移動する必要があります。非シャード表から重複表にデータを移動するときには複雑なことはありませんが、非シャード表からシャード表にデータを移動するときには特別な注意が必要です。
重複表へのデータのロード
重複表へのデータのロードは、既存のデータベース・ツール(データ・ポンプ、SQL LoaderまたはプレーンSQL)を使用して実行できます。データのロードには、シャード・カタログ(コーディネータ)データベース・ノードを使用する必要があります。つまり、シャード・カタログ・データベースには重複表のすべての内容が格納されることになります。重複表の内容はマテリアライズド・ビューを使用してデータベース・シャードに完全にレプリケートされるため、重複表のロードにかかる時間は同じデータを非シャード表にロードするよりも長くなる場合があります。
シャード表へのデータのロード
シャード表をロードする場合は、各データベース・シャードがデータ・セット全体のサブセット(シャード)を個別に収容するため、データを分割(シャッフル)してから各サブセットを特定のシャードにロードする必要があります。
データベース・シャードのすべてにサブセットのデータをロードするには、Oracle Data Pumpユーティリティを使用する必要があります。次の2つのオプションについて考えてみます。
- 上の図に示すように、シャーディング・コーディネータ(カタログ)ノード経由でデータをロードします。
- 次の図に示すように、データベース・シャードにデータを直接ロードします。
シャーディング・コーディネータを使用してシャード・データベースにデータをロードすると、シャーディング・コーディネータ(カタログ)ノードで実行される分割ロジックと、データをシャードにプッシュする追加のオーバーヘッドにより、非シャード表にデータ・セット全体をロードするよりも時間がかかります。
データベース・シャードに直接データをロードすると、各シャードが個別にロードされるためロード時間は大幅に短くなります。つまり、各シャードでデータ・ポンプを実行することで、データ・セット全体のうち最大のサブセットのシャードをロードするために必要な期間内でデータ・ロード操作を完了できます。一般に、このロード時間は、データ・セット全体を非シャード・データベースにロードする場合に必要な時間をシャード・データベース内のシャード数で除算することで概算できます。
Oracle Data Pumpユーティリティに依存してロード・データ・セットを個別のサブセットに分割するかわりに、アプリケーションに分割(シャッフル)ロジックを統合するオープンのソース・シャード分割ライブラリが使用できます。シャード分割ライブラリのソース・コードと使用例は、Oracle Sharding Tools Library (https://github.com/oracle/db-sharding/)で入手できます。このシャード分割ライブラリを基にして、OracleではOracle Cloudで使用するための汎用ストリーミング・ロード・ライブラリを開発しています。現時点では、このストリーミング・ロード・ライブラリは請求した場合にのみ利用できます。
親トピック: シャード・データベースへのデータの移行
スキーマの移行
既存のデータベースをシャード・データベースに移行する前に、シャード・データベースの編成方法を決定する必要があります。
シャードの数およびレプリケーション戦略を決定する必要があり、アプリケーション内でシャード表にする表および重複表にする表を決定する必要があります。シャード表については、シャーディング方法と、表ファミリでのシャード表間の親子関係を決定する必要があります。
シャード・データベース環境へのスキーマの移行については、サンプル・アプリケーションを使用して説明します。このアプリケーションはデータ・モデルによって定義され、いくつかの制約が適用されます。シャード・データベースへの移行がアプリケーションに与える影響については、サンプル・プログラム・コードを使用して分析します。次の図は、サンプル・アプリケーションのデータ・モデルを示しています
このデータ・モデルは、4つの表Customers、Orders、StockItemsおよびLineItemsで構成され、次の主キー制約が適用されます。
-
Customer.(CustNo)
-
Orders.(PONo)
-
StockItems.(StockNo)
-
LineItems.(LineNo, PONo)
このデータ・モデルでは、次の参照整合性制約が定義されます。
-
Customers.CustNo -> Orders.CustNo
-
Orders.PONo -> LineItems.PONo
-
StockItems.StockNo -> LineItems.StockNo
次のDDL文では、サンプル・アプリケーションのデータベース・スキーマ定義を作成します。
CREATE TABLE Customers (
CustNo NUMBER(3) NOT NULL,
CusName VARCHAR2(30) NOT NULL,
Street VARCHAR2(20) NOT NULL,
City VARCHAR2(20) NOT NULL,
State CHAR(2) NOT NULL,
Zip VARCHAR2(10) NOT NULL,
Phone VARCHAR2(12),
PRIMARY KEY (CustNo)
);
CREATE TABLE Orders (
PoNo NUMBER(5),
CustNo NUMBER(3) REFERENCES Customers,
OrderDate DATE,
ShipDate DATE,
ToStreet VARCHAR2(20),
ToCity VARCHAR2(20),
ToState CHAR(2),
ToZip VARCHAR2(10),
PRIMARY KEY (PoNo)
);
CREATE TABLE StockItems (
StockNo NUMBER(4) PRIMARY KEY
Description VARCHAR2(20),
Price NUMBER(6,2)
);
CREATE TABLE LineItems (
LineNo NUMBER(2),
PoNo NUMBER(5) REFERENCES Orders,
StockNo NUMBER(4) REFERENCES StockItems,
Quantity NUMBER(2),
Discount NUMBER(4,2),
PRIMARY KEY (LineNo, PoNo)
);
シャーディング・キー
シャーディングは、複数の独立したOracleデータベース間にデータを水平にパーティション化するデータベースのスケーリング手法です。データベース・リクエストは、シャーディング・キー列の値に基づいて適切なシャード・データベースにルーティングされます。シャーディング設計の目標は、単一のシャード操作が最大化されるようになり、シャード間操作が最小化または排除されるようになるシャーディング・キーを選択することです。
サンプル・アプリケーションのデータ・モデルで特定される主キーから外部キーへの機能的な依存関係に基づいて、次の表ファミリが形成されます。
-
Customers – 親表
-
Orders – 子表
-
Lineitems – 孫表
残りのStockItems表は単なる参照表で、在庫品目番号を在庫品目の説明と価格にマッピングします(StockNo -> (Description, Price))。
シャード・データベース定義には、参照パーティション化を使用する表ファミリのメンバーごとの次のDDL文と、StockItems参照表を定義する追加のDDL文が必要です。
CREATE SHARDED TABLE Customers (
CustNo NUMBER(3) NOT NULL,
CusName VARCHAR2(30) NOT NULL,
Street VARCHAR2(20) NOT NULL,
City VARCHAR2(20) NOT NULL,
State CHAR(2) NOT NULL,
Zip VARCHAR2(10) NOT NULL,
Phone VARCHAR2(12),
CONSTRAINT RootPK PRIMARY KEY (CustNo)
)
PARTITION BY CONSISTENT HASH (CustNo)
PARTITIONS AUTO
TABLESPACE SET ts1
;
CREATE SHARDED TABLE Orders (
PoNo NUMBER(5) NOT NULL,
CustNo NUMBER(3) NOT NULL,
OrderDate DATE,
ShipDate DATE,
ToStreet VARCHAR2(20),
ToCity VARCHAR2(20),
ToState CHAR(2),
ToZip VARCHAR2(10),
CONSTRAINT OrderPK PRIMARY KEY (CustNo, PoNo)
CONSTRAINT CustFK Foreign Key (CustNo) REFERENCES Cusomters (CustNo)
)
PARTITION BY REFERENCE (CustFK)
;
CREATE DUPLICATED TABLE StockItems (
StockNo NUMBER(4) PRIMARY KEY
Description VARCHAR2(20),
Price NUMBER(6,2)
);
CREATE SHARDED TABLE LineItems (
LineNo NUMBER(2) NOT NULL,
PoNo NUMBER(5) NOT NULL,
StockNo NUMBER(4) REFERENCES StockItems,
Quantity NUMBER(2),
Discount NUMBER(4,2),
CONSTRAINT LinePK PRIMARY KEY (CustNo, LineNo, PoNo)
CONSTRAINT LineFK FOREIGN KEY (CustNo, PoNo) REFERENCES Orders (CustNo, PoNo)
)
PARTITION BY REFERENCE (LineFK)
;
前述のシャード・データベースDDLと元の(非シャード)データベースの表を比較すると、次の構造的な表の変更があることがわかります(太字のイタリック体で示しています)。
-
表ファミリの表に対する
CREATE TABLE
文には、追加のSHARDED
キーワードが含まれています。 -
参照表の
CREATE TABLE
文には、追加のDUPLICATED
キーワードが含まれています。 -
表ファミリのすべての表には、シャーディング・キー列CustNoが主キーの先頭の列として格納されています。これはシャーディング固有の非正規化であり、先頭のコンポーネントとして直接の親キー(レベル・キーとも呼ばれます)が含まれるように、表ファミリ階層の各レベルで複合主キーを拡張しています。
-
シャード表は、シャーディング・キー列でパーティション化(
PARTITIONED BY
)されます。この例の場合は、ファミリのルート表がCONSISTENT HASH
でパーティション化されています。このパーティション化スキーマは、BY REFERENCE
(参照パーティション化)によって階層の下位レベルに伝播されます。CONSISTENT HASH
によるデータのパーティション化は、システム管理のシャーディング方法と呼ばれます(これは、ユーザー定義のシャーディングと対になる用語です)。 -
システム管理のシャーディングでは、シャード表に表領域セットが定義されます。表領域の最初のセットは、
SHARDED
表に使用されます。表領域セットは、1つ以上のシャード表と索引の論理的な記憶域単位として、シャード・データベースで使用されるものです。表領域セットは、シャード領域内のシャード間で分散される複数の表領域で構成されます。表領域は、データベースによって表領域セットとして自動的に作成されます。表領域の数は自動的に決定され、対応するシャード領域のチャンクの数と等しくなります。シャード表の
CREATE TABLESPACE SET tbs1;
この例では、Customers、OrdersおよびLineItemsは表領域セットのtbs1に配置されます。つまり、表ファミリの3つの表に対応するパーティションは、同じ表領域セットtbs1に格納されるということです(参照によるパーティション)。ただし、各表に個別の表領域セットを指定できます。
関連項目:
パーティション、表領域およびチャンク親トピック: シャード・データベースへのデータの移行
ソース・データベースの準備
シャード・データベース・スキーマへの移行を円滑に進めるために、ターゲットのシャード・データベースの表定義と一致するように、ソースの非シャード・データベースを変更できます。
理想としては、ターゲット・シャード・データベースの表定義(表名、列名およびそのデータ型)と、ソース・データベースの表定義を完全に同じにします。ただし、シャード・データベースへの移行の一環として、シャード・データベースで使用する表定義の変更が必要になることがあります。その場合は、新しいシャード・データベースの表定義と一致するように、ソースの非シャード・データベースを変更できます。変更の範囲によっては、アプリケーション・コードの変更が必要になることもあります。移行プロセスの前に、ターゲットのシャード・データベース・スキーマと一致するようにソースのデータベース・スキーマを変更しておくことで、元の非シャード・データベースから新しいシャード・データベースへの中断のない移行のための条件を整えます。このような準備は、停止時間が発生したとしても、停止時間を最小にするための前提条件になります。また、サンプル・アプリケーションで示すように、移行の準備はシームレスな簡単に元に戻せるプロセスです。ソース・データベースを準備することは非常に望ましい行動ですが、必須ではありません。なんらかの理由でソース・データベースのオペレーティング環境を変更できない場合は、このトピックをスキップしてもかまいません。
ここに示すステップは、前のトピックで定義したものと同じサンプル・スキーマに従います。
サンプル・データベースを移行するために、次の例に示すようにALTER TABLE
を使用して、ソース・データベースのLineItems表にシャーディング・キー列CustNoを追加する必要があります。
ALTER TABLE LineItems ADD (CustNo NUMBER(3));
この追加の列により、ソース表の行データ・レイアウトとターゲットのシャード表の目的とするレイアウトが同じになります。これで、この新しい列に一致するデータを設定する準備ができました。LineItems表の追加のシャーディング・キー列CustNoの値は、親子関係のOrdersとLineItemsの結合から導出する必要があります。
SELECT Orders.CustNo FROM Orders JOIN LineItems ON Orders.PONo = LineItems.PONo;
この例では、MERGE
文を使用してCustNo列に移入しています。標準SQLを使用して、同じ目標を達成することもできます。ここに示した例では、MERGE
文は次のようになります。
SQL> BEGIN
2 MERGE INTO LineItems l
3 USING Orders o
4 ON (l.PONo = o.PONo)
5 WHEN MATCHED THEN
6 UPDATE SET l.CustNo = o.CustNo;
7 END;
8 /
この時点で、CustNo列に対する参照整合性が維持されることがわかります。新しい列が正しく移入されるようにするには、MERGE
文の実行後に、次に示すようにNOT NULL
制約を追加する必要があります。
ALTER TABLE LineItems MODIFY CustNo NOT NULL;
前述のMERGE
文を実行すると、LineItems表の行レイアウトと行データが目的の状態になります。追加のCustNo列により、LineItems表が表ファミリのルート(Customers)と同じ方法でシャーディングされます。この変更は、実際の移行前の最後のアクションの1つとして使用することを検討してください。そうしない場合は、アプリケーション内で、この新しい列を維持管理する必要があります。その結果として、追加されたシャーディング・キー列の参照整合性を既存のデータベースで維持管理することも必要になります。一致するCustNo列の参照整合性制約は、次に示すようにLineItems表で定義します。
ALTER TABLE LineItems ADD CONSTRAINT LineFk FOREIGN KEY (CustNo, PONo) REFERENCES Orders (CustNo, PONo);
LineItems表の参照整合性を変更する前に、Orders表を参照する既存のFOREIGN KEY
制約を削除する必要があります。この操作を安全に完了するには、DROP CONSTRAINT
文と後続のADD CONSTRAINT
文をALTER TABLE LineItems READ ONLY;
とALTER TABLE LineItems READ WRITE;
で囲むか、制約の変更中は単に表をLOCK TABLE LineItems IN SHARE MODE
でロックします。
CustNo列はLineItems表の外部キー定義の一部として追加するため、Orders表の主キーの変更が必要になります。主キーを変更すると、索引の再構築が必要になるため、この作業の完了までには時間がかかる場合があります。この作業は、シャード・データベースに移行する前の一定期間、この新しいスキーマに対してアプリケーションを実行する予定がある場合にのみ意味があります。
次の例は、LineItems表にシャーディング・キーを追加した結果としてのLineItemsおよびOrdersスキーマの変更操作を示しています。LineItems表の既存の外部キー制約を削除して、Orders表の主キー制約を削除する前に、次に示すように、それぞれの制約名を取得しておく必要があります。
SQL> SELECT a.table_name, a.column_name, a.constraint_name
2 FROM ALL_CONS_COLUMNS A, ALL_CONSTRAINTS C
3 WHERE A.CONSTRAINT_NAME = C.CONSTRAINT_NAME
4 and a.table_name='LINEITEMS' and C.CONSTRAINT_TYPE = 'R';
LINEITEMS
PONO
SYS_C009087
LINEITEMS
STOCKNO
SYS_C009088
SQL> SELECT cols.table_name, cols.column_name, cols.constraint_name, cols.position
2 FROM all_constraints cons, all_cons_columns cols
3 WHERE cons.constraint_type = 'P'
4 AND cons.constraint_name = cols.constraint_name
5 AND cols.table_name = 'ORDERS'
6 ORDER BY cols.table_name, cols.position;
ORDERS
ORDER_ID
ORDER_PK
1
ORDERS
PONO
SYS_C009148
1
SQL> ALTER TABLE LineItems READ ONLY;
Table altered.
SQL> ALTER TABLE Orders READ ONLY;
Table altered.
SQL> ALTER TABLE LineItems DROP CONSTRAINT SYS_C009087;
Table altered.
SQL> ALTER TABLE ORDERS DROP CONSTRAINT SYS_C009148;
Table altered.
SQL> ALTER TABLE ORDERS ADD CONSTRAINT order_pk PRIMARY KEY (CustNo, PONo);
Table altered.
SQL> ALTER TABLE LineItems ADD CONSTRAINT LineFk FOREIGN KEY (CustNo, PONo) REFERENCES Orders (CustNo, PONo);
Table altered.
SQL> ALTER TABLE Orders READ WRITE;
Table altered.
SQL> ALTER TABLE LineItems READ WRITE;
Table altered.
同様に、次に示すように、先頭の列としてCustNoを含めることで完全なレベル・キーになるようにLineItems表のPRIMARY KEY
定義を拡張する必要があります。
ALTER TABLE LineItems ADD CONSTRAINT LinePK PRIMARY KEY (CustNo, PONo, LineNo);
繰り返しになりますが、新しい制約を導入する前に、既存のPRIMARY KEY
制約を削除する必要があります。データ整合性を維持するために、前述の2つのトランザクション分離戦略のいずれかを使用して、PRIMARY KEY
制約とFOREIGN KEY
制約を変更します。次の例では、LineItems表が制約の変更の実行中にロックされます。その後、COMMIT
によってロックが解放されます。
SQL> SELECT cols.table_name, cols.column_name, cols.constraint_name, cols.position
2 FROM all_constraints cons, all_cons_columns cols
3 WHERE cons.constraint_type = 'P'
4 AND cons.constraint_name = cols.constraint_name
5 AND cols.table_name = 'LINEITEMS'
6 ORDER BY cols.table_name, cols.position;
LINEITEMS
LINENO
SYS_C009086
1
LINEITEMS
PONO
SYS_C009086
2
SQL> LOCK TABLE LineItems IN SHARE MODE;
Table(s) Locked.
SQL> ALTER TABLE LINEITEMS DROP CONSTRAINT SYS_C009086;
Table altered.
SQL> ALTER TABLE LineItems ADD CONSTRAINT LinePK PRIMARY KEY (CustNo, PONo, LineNo);
Table altered.
SQL> COMMIT;
Commit complete.
参照整合性に関連する変更はオプションです。この変更案によって、ソース・データベースはターゲットのシャード・データベースとほとんど同じ状態になります。これにより、移行プロセスが容易になります。
参照整合性は、適用できないことや、作成することが望ましくないこともあります。その場合は、参照パーティション化を定義できません。そうした状況では、かわりにPARENT
句を使用できます。
最後に、LineItems表の追加のCustNo列は、SELECT * FROM LineItems
などの既存の問合せに影響する場合があります。この問題を回避するために、次に示すように、CustNo列を不可視に変更できます。
SQL> ALTER TABLE LineItems MODIFY CustNo INVISIBLE;
ソース・データベース表に対してこれらの変更を実施することで、既存のサンプル・データベースの移行のための準備が整います。
親トピック: シャード・データベースへのデータの移行
ターゲット・シャード・データベースの準備
シャード・データベースへのデータの移行を開始する前に、設計に応じてシャード・データベース・スキーマを作成する必要があります。
非シャード・データベース環境からシャード・データベース環境へのデータの移行は、次の2つの方法で実行できます。
- 2段階アプローチ: データ移行のためのより慎重な増分アプローチです。1つのシャードのみでシャード・データベースを作成します。シャード・データベースが1つのシャードに格納されているかぎり、アプリケーション・コードの変更なしに関連するごくわずかな変更で、アプリケーションやデータベース・メンテナンスの手順を使用できます。つまり、シャード・データベースは、シャード環境に移行する時点の非シャード・データベースと同じように動作するということです。その後で、シャーディングに対応するようにアプリケーションと操作手順を変更して、データベースを適切な数のシャードにスケール・アウトする作業を進めます。
- 1段階アプローチ: 最初に適切な数のシャードでシャード・データベースを作成します。この場合、アプリケーションと操作手順は、移行する時点でシャーディング操作に対応するように完全に準備する必要があります。
より慎重な2段階アプローチを使用すると、シャード・データベース環境への移行がスムーズになりますが、かなり長い時間がかかるようにもなります。単一のシャードに対してアプリケーションを実行すると、ダイレクト・ルーティングに向けてアプリケーションを徐々に変更するための時間が得られます。シャード・ディレクタを使用して適切なシャードに接続するように既存のアプリケーションを変更した後にのみ、残りのシャードをインスタンス化できます。
プロセスの最初のステップは、シャードが1つのみのシャード・データベースを作成することです。その後で、次の各項のいずれかで提案されているように既存のアプリケーションを変更します。最後のステップで、シャード・データベースを必要なシャード数までスケールアウトします。この方法では、スケール・アウトの前に、すべてのシャードでデータ・チャンクを分割およびリバランスする機会も得られます。
この2段階の移行方法には、長い時間がかかるだけでなく、多くの領域も必要になります。すべてのデータを1つのシャードに移行する場合は、制限なしでシャード表のデータを単一のシャードに直接ロードできます。複数のシャードにスケール・アウトした後で、シャード表を適切に複数のデータベース・シャードに直接ロードする場合は、データ・ポンプ・ユーティリティを必ず使用する必要があります。重複表はカタログ・データベースに存在しているため、重複表のロードには常にカタログ・データベースを使用することになります。
1段階のアプローチと2段階のアプローチのどちらを使用するにしても、シャード・データベースにデータをロードする前に、データのエクスポートとシャード・データベース・スキーマの作成が必要になります。次の例は、ソフトウェアのインストールが完了していて、シャード用に少なくとも1つのシャーディング・カタログと1つ以上のデータベースを含むシャード・データベース環境を作成していることを想定しています。新しいシャード・カタログを作成することも、既存のシャード・カタログを使用することもできます。移行プロセスを説明するために、この手順の例では次のデータベース・インスタンスを使用します。
-
orignode: 元の非シャード・データベース・インスタンス(SID=orig)をホストするサイト
-
catnode: シャード・カタログ・データベース・インスタンス(SID=ctlg)をホストするカタログ・ノード
-
shrdnodeN: データベース・シャード・インスタンス(SID=shrdN、Nは1、2など)をホストするシャード・ノード
-
gsmnode: シャード・ディレクタ(GSM)インスタンス(SID=gsm1)をホストするカタログ・ノード
移行の準備でソース・データベースを変更したかどうかに関係なく、移行プロセスでは、少なくともCREATE SHARDED TABLE
文とCREATE DUPLICATED TABLE
文が含まれているDDL定義に変更を加える必要があります。データベース・スキーマをターゲットのシャード・データベースに移行するには、ソース・データベースからDDL定義を抽出して、SHARDED表およびDUPLICATED表のメタデータを変更する必要があります。ソース・データベースからDDL文を抽出する際の便利な方法は、データ・ポンプ抽出ファイルを作成することです。その後で、次に示すように、データベース・エクスポート・ファイルに対してデータ・ポンプ・インポート・ユーティリティを使用します。
impdp uname/pwd@orignode directory=expdir dumpfile=sample.dmp sqlfile=sample_ddl.sql
この例では、impdp
コマンドは、実際にはダンプ・ファイルの内容をインポートしません。そのかわりに、sqlfile
パラメータによって、sample_ddl.sqlという名前のスクリプトの作成がトリガーされます。このスクリプトには、エクスポート・ダンプ・ファイル内のすべてのDDLが格納されています。このデータベース・エクスポート・ファイルは、次に示すようにして作成します。
expdp uname/pwd@orignode full=Y directory=expdir dumpfile=sample.dmp logfile=sample.log
全データベース・エクスポート・ファイルには、データベースのすべての内容(データとメタデータ)が含まれています。データベース全体のエクスポートには時間がかかることがあります。このステップを短時間で実行するために、この例に示すように、メタデータのみをエクスポートすることも、必要な表のセットが含まれる部分のみをエクスポートすることもできます。
expdp uname/pwd directory=DATA_PUMP_DIR dumpfile=sample_mdt.dmp logfile=sample_mdt.log INCLUDE=TABLE:\"IN \( \'CUSTOMERS\', \'ORDERS\', \'STOCKITEMS\', \'LINEITEMS\' \) \" CONSISTENT=Y CONTENT=METADATA_ONLY
このようにエクスポートをトリミングして、時間がかかることがあるデータ・ポンプ・プロセスなしに、データベース・メタデータの一貫したイメージを効率的に取得します。それでも、テキスト形式でDDL文を取得して、シャード・データベース・スキーマ設計で要求されるようにDDLを変更する必要があります。データベース全体をエクスポートする場合は、データをロードするための入力としても使用することになります。
データ・ポンプは、データを転送するための安全な方法を提供します。データベース管理者は、次に示すように、データベース・ユーザーにデータベース・エクスポート・ディレクトリへのアクセスに必要な権限を付与する必要があります。
CREATE OR REPLACE DIRECTORY expdir AS ‘/some/directory’;
GRANT READ, WRITE ON DIRECTORY expdir TO uname;
GRANT EXP_FULL_DATABASE TO uname;
全データベース・エクスポートでは、データベース管理者がユーザーuname
にEXP_FULL_DATABASE
ロールを付与する必要があります。表レベルのエクスポートには、追加のロールは必要ありません。データ・ポンプ・ユーティリティの詳細は、後述の関連項目に示したデータベース・ユーティリティのドキュメントを参照してください。
ターゲット(シャード)データベースの行レイアウトと一致するようにソース(非シャード)データベースを変更した場合は、全データベースまたは表レベルのエクスポートは必要ありません。データは、中間ダンプ・ファイルなしでそのまま効率的に転送できます。
シャード・データベース・スキーマを完成させたら、データベース管理者の資格証明を使用して、シャーディング・カタログ・データベース(ctlg)に対して準備したDDLを実行します。すべてのDDL文をSHARD DDL
設定が有効になっているセッションで実行して、すべてのDDL文がカタログ・データベース(ctlg)からシャード・データベース(shrd1、2、...、N)に伝播されるようにする必要があります。
ALTER SESSTION ENABLE SHARD DDL;
シャード表と重複表が定義されていると、シャード・データベースはデータ・ロードの準備が整っていることになります。データのロード前に、GDSCTL VALIDATE
コマンドを使用してシャーディング構成を検証することをお薦めします。
gdsctl validate
検証に成功すると、シャード・データベースはデータ・ロードの準備が整っていることになります。不整合やエラーが見つかった場合は、GDSCTLコマンドのSHOW DDL
およびRECOVER
を使用して問題を修正する必要があります。
データ・ポンプ・エクスポート・ユーティリティのファイルは、デフォルトで表ごとの一貫性が確保されます。エクスポート内のすべての表に同じ時点での一貫性を持たせる場合は、FLASHBACK_SCN
パラメータまたはFLASHBACK_TIME
パラメータを使用する必要があります。「特定」の時点での一貫性があるデータベース・エクスポート・ファイルを用意することをお薦めします。これは、非シャード・データベースからシャード・データベースへの中断のない移行を選択した場合、つまり、移行中にも継続的なデータベース操作を提供する場合は特に重要です。一貫性のあるデータベース・スナップショットを生成するエクスポート・コマンドは、次のようになります。
expdp uname/pwd@orignode full=Y directory=expdir dumpfile=sample.dmp logfile=sample.log CONSISTENT=Y FLASHBACK_TIME=SYSTIMESTAMP
一貫性のあるスナップショット・データベース・イメージには、追加のパラメータCONSISTENT
またはFLASHBACK_TIME
が必要です。このコマンドを実行すると、CONSISTENT
とFLASHBACK_TIME
のどちらのパラメータも意味が同じになることがわかります。次に示すように、タイムスタンプがシステム変更番号(SCN)に変換される点に注意してください。
SQL> SELECT TIMESTAMP_TO_SCN(SYSTIMESTAMP) FROM dual;
TIMESTAMP_TO_SCN(SYSTIMESTAMP)
------------------------------
1559981
FLASHBACK_TIME
ではなくFLASHBACK_SCN
を使用すると、現在のSCNは、次に示すようにV$DATABASEから選択することで取得できます。
SQL> SELECT current_scn FROM v$database;
CURRENT_SCN
-----------
1560005
または、次に示すように宣言することもできます。
SQL> SET SERVEROUTPUT ON
SQL> DECLARE SCN NUMBER;
2 BEGIN
3 SCN := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER;
4 DBMS_OUTPUT.PUT_LINE(SCN);
5 END;
6 /
1560598
データベース管理者に、DBMS_FLASHBACK
へのアクセス許可を求めることが必要になる場合があります。
データ・ポンプの実行は、PARALLEL
パラメータを使用すると高速化できます。次の例に示すように、このパラメータはDUMPFILE
パラメータの%Uワイルドカードとともに使用して、複数のダンプ・ファイルを作成できるようにします。
expdp uname/pwd@orignode full=Y directory=expdir dumpfile=samp_%U.dmp logfile=samp.log CONSISTENT=Y PARALLEL=3
前述のコマンドでは、4つのパラレル・ワーカーを使用して、接尾辞として_01、_02、_03が付いた4つのダンプ・ファイルを作成します。インポート時に同じワイルドカードを使用すると、複数の入力ファイルを参照できるようになります。前述の例では1つの出力ファイルsample.dmpが作成されましたが、並列エクスポートによって3つのダンプ・ファイルsamp_01.dmp、samp_02.dmpおよびsamp_03.dmpが作成される点に注目してください。また、並列エクスポートの経過時間は、シリアル実行(1つのダンプ・ファイル出力)の経過時間の1/3未満になります。
データの移行
ターゲット・シャード・データベースを作成してシャード表と重複表を定義したら、シャード・データベースへのデータの移行を開始できます。
ソースからシャード・データベースにデータを移行する前に、次に示すデータ・ロードの考慮事項を理解しておいてください。
- 複製表とシャード表の移行の相違点
重複表はシャード・カタログに存在していて、その表は使用可能なデータ・ロード・ユーティリティまたはプレーンSQLを使用して常にシャード・カタログ・データベースにロードされます。ただし、シャード表をロードするためのオプションは2つあります。シャード表は、シャーディング・コーディネータ(シャード・カタログ)を使用してロードすることも、データ・ポンプ・ユーティリティを使用してシャードに直接ロードすることもできます。
- コーディネータを使用したシャード表のロードまたはシャードへのデータの直接ロード
シャード・データベースにシャード表を直接ロードすると、同時に複数のシャードをロードできるため常に高速になります。
- 複数のシャードへの移行または1つのシャードへの移行
非シャード・データベースをシャードが1つのシャード・データベースに移行する場合は、アプリケーションとデータベースのメンテナンス手順に大幅な変更は必要ありません。データをシャードに分割する準備が整うまで、大幅な変更なしに現在の操作を継続できます。ただし、非シャード・データベースをシャードが複数あるシャード・データベースに移行する場合は、かなりの準備プロセスが必要になります。最も時間がかかる前提条件は、アプリケーション・ソース・コードの変更です。シャードが1つのシャード・データベースに移行する場合は、データベースを複数のシャードにスケーリングするための計画を立てる必要もあります。つまり、ある程度の時間をおいてから、複数のシャード全体にデータベース・チャンクを分散するということです。その間に、複数のシャードがあるシャード・データベースで実行するために必要なアプリケーションの変更などの作業を進めることができます。
- 停止時間のある移行または中断のない移行
非シャード・データベースからシャードが1つのみのシャード・データベースへの中断のない移行は、シャードが複数あるシャード・データベースへの移行よりも簡単です。その後の1つのシャードから複数のシャードへのスケール・アウトは、データベースの実行中に実施されます。
非シャード・データベースからマルチシャード・データベース・ターゲットに1段階で移行する場合は、その移行を最初にテスト環境で試してみて、テスト環境の移行に問題がないことを確認してからのみ、本番環境への移行を開始するようにしてください。
ターゲットの行データ・レイアウトがソース・データベースで準備されているかどうかに関係なく、効率的なデータ移行のための様々な方法があります。様々な条件(使用可能なディスク領域、リモート・ファイル・アクセス、ネットワーク・スループットなど)に最適な方法を選択してください。
シャード表へのデータの移行
移行時に可能なかぎりデータベースが使用できる状態を保つための対策を取る必要があり、停止時間についての計画を立てる必要があります。
移行前にソースの非シャード・データベースの表を変更して、表スキーマがターゲットのシャード・データベース・スキーマと一致するようにしておくと、この移行前のステップを実行しなかった場合よりもデータ移行のプロセスが円滑に進みます。このアプローチにより、ソース・データベースの行データ・レイアウトが、ターゲット・シャード・データベースの対応するレイアウトと同じになるため、データベースの内容を直接コピーできます。
- データベース表からデータをエクスポートします。
expdp uname/pwd@non_sharded_db directory=file_dir dumpfile=original_tables.dmp logfile=original_table.log INCLUDE=TABLE:\"IN \( \'CUSTOMERS\', \'ORDERS\', \'STOCKITEMS\', \'LINEITEMS\' \) \" CONSISTENT=Y CONTENT=DATA_ONLY
このデータ・ポンプ・エクスポートの例は、Oracle Shardingサンプル・アプリケーションで使用される表に制限されています。SHARDED表とDUPLICATED表は、すでにサンプルで作成されているため、表のコンテンツのみをエクスポートします(DATA_ONLY)。
- ターゲットのデータベース・ノードでエクスポート・ファイル(original_tables.dmp)にアクセスできるようにしてから、シャード・データベースへのデータのインポートを開始します。
このファイル(並列エクスポートの場合は複数のファイル)は、ターゲット・データベース・システムに移動することも、ネットワーク経由で共有することもできます。
- エクスポートが完了してアクセス可能になると、シャード・データベースへの内容のインポートを開始できます。
DUPLICATED
表(StockItems)は、シャード・カタログを使用してロードする必要があります。次に、インポート・コマンドの例を示します。impdp uname/pwd@catnode:1521/ctlg directory=data_pump_dir dumpfile=original_tables.dmp logfile=imp.log tables=StockItems content=DATA_ONLY
- シャードを直接ロードするか、シャード・カタログを使用してシャードをロードします。
SHARDED
表(Customers、OrdersおよびLineItems)をロードするための最適な方法は、各シャード(shrd1、2、...、N)でデータ・ポンプを直接実行することです。次に、最初のシャードでのインポート・コマンドの例を示します。impdp uname/pwd@shrdnode:1521/shrd1 directory=data_pump_dir dumpfile=original_tables.dmp logfile=imp.log tables=Customers, Orders, LineItems content=DATA_ONLY
または、シャード・カタログでデータ・ポンプを実行して、すべての表をロードすることもできます。次の例は、そのインポート・コマンドを示しています。
impdp uname/pwd@catnode:1521/ctlg directory=data_pump_dir dumpfile=original_tables.dmp logfile=imp.log tables=Customers, Orders, LineItems, StockItems content=DATA_ONLY
親トピック: シャード・データベースへのデータの移行
アプリケーションの移行
シャード・データベースのオペレーティング環境では、アプリケーションはシャードに直接アクセスできるようになります。この機能により、本当の線形スケーラビリティが得られますが、アプリケーション・コードへのわずかな変更という少しの手間がかかります。
次の各例では、アプリケーションの移行方法を示します。この例は、移行したデータベース・スキーマとデータに対応するサンプル・アプリケーションのスケルトンです。サンプル・アプリケーションの主要部分には、オーダー管理およびレポート機能を組み込みます(JavaクラスPOManagerに含めます)。このクラスの最初の静的メソッドでは、addCustomer()メソッドを使用してCustomers表に新しい行を導入します。次に示すように、この関数には、パラメータ・リストを使用して列値が渡されます。
import java.sql.*;
import java.io.*;
import oracle.jdbc.driver.*;
public class POManager {
public static void addCustomer (int custNo, String custName,
String street, String city, String state, String zipCode,
String phoneNo) throws SQLException {
String sql = "INSERT INTO Customers VALUES (?,?,?,?,?,?,?)";
try {
Connection conn =
DriverManager.getConnection("jdbc:default:connection:");
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, custNo);
pstmt.setString(2, custName);
pstmt.setString(3, street);
pstmt.setString(4, city);
pstmt.setString(5, state);
pstmt.setString(6, zipCode);
pstmt.setString(7, phoneNo);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
}
POManagerクラスの2番目の静的メソッドaddStockItem()では、StockItem表に行を追加します。次の例に示すように、列の値はパラメータ値として渡されます。
public static void addStockItem (int stockNo, String description,
float price) throws SQLException {
String sql = "INSERT INTO StockItems VALUES (?,?,?)";
try {
Connection conn =
DriverManager.getConnection("jdbc:default:connection:");
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, stockNo);
pstmt.setString(2, description);
pstmt.setFloat(3, price);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
}
POManagerクラスの3番目の静的メソッドenterOrder()では、Orders表に行を追加します。列値は、次に示すようにパラメータ・リストで指定します。
public static void enterOrder (int orderNo, int custNo,
String orderDate, String shipDate, String toStreet,
String toCity, String toState, String toZipCode)
throws SQLException {
String sql = "INSERT INTO Orders VALUES (?,?,?,?,?,?,?,?)";
try {
Connection conn =
DriverManager.getConnection("jdbc:default:connection:");
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, orderNo);
pstmt.setInt(2, custNo);
pstmt.setString(3, orderDate);
pstmt.setString(4, shipDate);
pstmt.setString(5, toStreet);
pstmt.setString(6, toCity);
pstmt.setString(7, toState);
pstmt.setString(8, toZipCode);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
}
その次のPOManagerクラスの静的メソッドaddLineItem()では、LineItems表に行を追加します。次の例に示すように、列値はパラメータ値として渡されます。
public static void addLineItem (int lineNo, int orderNo,
int stockNo, int quantity, float discount) throws SQLException {
String sql = "INSERT INTO LineItems VALUES (?,?,?,?,?)";
try {
Connection conn =
DriverManager.getConnection("jdbc:default:connection:");
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, lineNo);
pstmt.setInt(2, orderNo);
pstmt.setInt(3, stockNo);
pstmt.setInt(4, quantity);
pstmt.setFloat(5, discount);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
}
その次のPOManagerクラスの静的メソッドtotalOrders()では、Orders表のすべての注文の合計注文値を生成します。次に示すように、結果セットの関係はprintResult()メソッドを使用して出力されます。
public static void totalOrders () throws SQLException {
String sql =
"SELECT O.PONo, ROUND(SUM(S.Price * L.Quantity)) AS TOTAL " +
"FROM Orders O, LineItems L, StockItems S " +
"WHERE O.PONo = L.PONo AND L.StockNo = S.StockNo " +
"GROUP BY O.PONo";
try {
Connection conn =
DriverManager.getConnection("jdbc:default:connection:");
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rset = pstmt.executeQuery();
printResults(rset);
rset.close();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
}
次に示すヘルパー・メソッドprintResults()は、totalOrders()メソッドによって生成された結果セットの関係を出力するために使用されます。結果セットの関係への参照がパラメータとして渡されます。
static void printResults (ResultSet rset) throws SQLException {
String buffer = "";
try {
ResultSetMetaData meta = rset.getMetaData();
int cols = meta.getColumnCount(), rows = 0;
for (int i = 1; i <= cols; i++) {
int size = meta.getPrecision(i);
String label = meta.getColumnLabel(i);
if (label.length() > size) size = label.length();
while (label.length() < size) label += " ";
buffer = buffer + label + " ";
}
buffer = buffer + "\n";
while (rset.next()) {
rows++;
for (int i = 1; i <= cols; i++) {
int size = meta.getPrecision(i);
String label = meta.getColumnLabel(i);
String value = rset.getString(i);
if (label.length() > size) size = label.length();
while (value.length() < size) value += " ";
buffer = buffer + value + " ";
}
buffer = buffer + "\n";
}
if (rows == 0) buffer = "No data found!\n";
System.out.println(buffer);
} catch (SQLException e) {System.err.println(e.getMessage());}
}
次に示す静的メソッドcheckStockItem()は、指定された在庫品目のすべてのオーダー、顧客および明細品目詳細を取得します。在庫品目はパラメータとして渡されます。前述のヘルパー・メソッドprintResults()は、checkStockItem()メソッドによって生成された結果セットの関係を出力するために使用されます。
public static void checkStockItem (int stockNo)
throws SQLException {
String sql = "SELECT O.PONo, O.CustNo, L.StockNo, " +
"L.LineNo, L.Quantity, L.Discount " +
"FROM Orders O, LineItems L " +
"WHERE O.PONo = L.PONo AND L.StockNo = ?";
try {
Connection conn =
DriverManager.getConnection("jdbc:default:connection:");
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, stockNo);
ResultSet rset = pstmt.executeQuery();
printResults(rset);
rset.close();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
}
静的メソッドchangeQuantity()では、指定のオーダーおよび在庫品目の明細品目数量を更新します。次に示すように、特定のオーダー番号と在庫品目を入力パラメータとして指定します。
public static void changeQuantity (int newQty, int orderNo,
int stockNo) throws SQLException {
String sql = "UPDATE LineItems SET Quantity = ? " +
"WHERE PONo = ? AND StockNo = ?";
try {
Connection conn =
DriverManager.getConnection("jdbc:default:connection:");
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, newQty);
pstmt.setInt(2, orderNo);
pstmt.setInt(3, stockNo);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
}
最後の静的メソッドdeleteOrder()では、Orders表から特定のオーダーと関連するすべての明細品目を削除します。次の例に示すように、削除するオーダーは入力パラメータとして指定します。
public static void deleteOrder (int orderNo) throws SQLException {
String sql = "DELETE FROM LineItems WHERE PONo = ?";
try {
Connection conn =
DriverManager.getConnection("jdbc:default:connection:");
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, orderNo);
pstmt.executeUpdate();
sql = "DELETE FROM Orders WHERE PONo = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, orderNo);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
}
}
ここで、シャーディング用に変更したサンプル・アプリケーションのプログラム・コードを確認します。最初の注目点は、OracleShardingKey以降のシャーディングに関連するJavaパッケージの追加インポートです。
import java.sql.*;
import java.io.*;
import oracle.jdbc.driver.*;
//
// import sharding and related packages
//
import oracle.jdbc.OracleShardingKey;
import oracle.jdbc.OracleType;
import oracle.jdbc.pool.OracleDataSource;
//
// Sample App: order management and reporting
// modified for sharding
//
public class POManager
{
// Connection factory for the sharded database used by Sample App
private OracleDataSource ods;
//
// Construct POManager class using Sharded database connection properties.
// Use service name when connecting to sharded database, something like:
// "jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(HOST=myhost)(PORT=3216)”
// “(PROTOCOL=tcp))(CONNECT_DATA=(SERVICE_NAME=myservice)(REGION=east)))"
//
public POManager(String yourURL, String yourUser, String yourPwd)
throws SQLException
{
ods = new OracleDataSource();
ods.setURL(yourURL);
ods.setUser(yourUser);
ods.setPassword(yourPwd);
} // POManager
前述したように、POManagerクラスには、ConnectionオブジェクトのためのOracleDataSourceファクトリが含まれるようになりました。次の2つのメソッドは、接続ファクトリを使用して直接ルーティングとプロキシ・ルーティングの接続を作成する方法を示しています。getCatConn()メソッドは、シャーディング・カタログへの接続を返します。getShardConn()メソッドは、パラメータとして指定されたシャーディング・キーと一致するシャードへの接続を返します。
//
// Connect to the Sharding Catalog database.
//
public static Connection getCatConn() throws SQLException
{
Connection catConn = ods.getConnection();
} // getCatConn
//
// Connect to Shard database using sharding key.
//
public static Connection getShardConn(int custNo) throws SQLException
{
OracleShardingKey shardKey =
ods.createShardingKeyBuilder().subkey(custNo, JDBCType.NUMERIC).build();
OracleConnection shardConn =
ods.createConnectionBuilder().shardingKey(shardingKey);
return shardConn;
} // getShardConn
これらすべてのことに注意して、次に示すように、シャーディング環境用にgetShardConn()を使用してaddCustomer()メソッドをリライトします。
//
// Connect to Shard database to add a customer.
//
public static void addCustomer (int custNo, String custName,
String street, String city, String state, String zipCode,
String phoneNo) throws SQLException {
String sql = "INSERT INTO Customers VALUES (?,?,?,?,?,?,?)";
try {
Connection conn = getShardConn(custNo);
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, custNo);
pstmt.setString(2, custName);
pstmt.setString(3, street);
pstmt.setString(4, city);
pstmt.setString(5, state);
pstmt.setString(6, zipCode);
pstmt.setString(7, phoneNo);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
} // addCustomer
addStockItem()では、getCatConn()を使用してシャード・カタログ・データベースに接続し、重複表StockItemsに挿入します。
//
// Connect to Sharding Catalog to add a stock item.
//
public static void addStockItem (int stockNo, String description,
float price) throws SQLException {
String sql = "INSERT INTO StockItems VALUES (?,?,?)";
try {
Connection conn = getCatConn();
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, stockNo);
pstmt.setString(2, description);
pstmt.setFloat(3, price);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
} // addStockItem
Orders表は、Customers表の子です。Orders表に挿入するには、次の例に示すように、指定されたシャーディング・キーに基づいてシャード・データベースに接続します。
//
// Connect to Shard database to add an order for a customer.
//
public static void enterOrder (int orderNo, int custNo,
String orderDate, String shipDate, String toStreet,
String toCity, String toState, String toZipCode)
throws SQLException {
String sql = "INSERT INTO Orders VALUES (?,?,?,?,?,?,?,?)";
try {
Connection conn = getShardConn(custNo);
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, orderNo);
pstmt.setInt(2, custNo);
pstmt.setString(3, orderDate);
pstmt.setString(4, shipDate);
pstmt.setString(5, toStreet);
pstmt.setString(6, toCity);
pstmt.setString(7, toState);
pstmt.setString(8, toZipCode);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
} // enterOrder
表ファミリのルートのCustomersと、直接の子レベルのOrdersのルートについては、パラメータとしてシャーディング・キーを指定します。直接の子より下のレベルについては、完全なレベル・キーがないことがあるため、直接の親から取得する必要があります。次の例には、Order表のキー値orderNoに基づいて、Order表からシャーディング・キーcustNoを取得するヘルパー・メソッドのgetCustomerFromOrder()があります。それぞれの子表の行には、単一の親行が必要です。そのため、親を持たない子、または複数の親を持つ子に対して整合性違反の例外が発生します。
//
// Determine which shard order is in
//
static int getCustomerFromOrder(int orderNo) throws SQLException
{
String sql = "SELECT O.CustNo FROM Orders O " +
"WHERE O.PONo = ?";
int custNo;
int rsSize = 0;
Exception exception;
try {
Connection conn = getCatConn();
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, orderNo);
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rset = pstmt.executeQuery();
while(rset.next() && rsSize < 3) {
custNo = rs.getInt(“CustNo);
rsSize++;
}
rset.close();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
if (rsSize == 0) {
throw new
SQLIntegrityConstraintViolationException(
“No matching parent level key”);
If (rsSize == 2)
{
throw new
SQLIntegrityConstraintViolationException(
“More than one parent level key”);
}
return custNo;
} // getCustomerFromOrder
ノート:
コードでは、シャード・データベースに含まれるシャードの数を仮定しないでください。次の例は、リライトしたaddLineItem()メソッドを示しています。このメソッドは、元のPOManagerクラスにあったものですが、getCustomerFromOrder()ヘルパー・メソッドを使用するようになっています。特定のorderNoについて、一致するcustNo値のシャード・カタログを問い合せます。問合せのこの部分は、プロキシ・ルーティングを使用するすべてのシャードに伝播されます。このヘルパー関数は、1つのcustNo値を返します。この値に基づいて、シャードへの直接ルートを使用してLineItems表の行を挿入します。
//
// Get customer (parent) from the catalog, then insert into a shard!
//
public static void addLineItem (int lineNo, int orderNo,
int stockNo, int quantity, float discount) throws SQLException {
String sql = "INSERT INTO LineItems VALUES (?,?,?,?,?,?)";
try {
int custNo = getCustomerFromOrder(orderNo);
Connection conn = getShardConn(custNo);
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, custNo);
pstmt.setInt(2, lineNo);
pstmt.setInt(3, orderNo);
pstmt.setInt(4, stockNo);
pstmt.setInt(5, quantity);
pstmt.setFloat(6, discount);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
} // addLineItem
//
// You’ve got whole level key handy, insert into a shard directly.
//
public static void addLineItemWithinParent (int lineNo, int orderNo,
int custNo, int stockNo, int quantity, float discount)
throws SQLException
{
String sql = "INSERT INTO LineItems VALUES (?,?,?,?,?,?)";
try {
Connection conn = getShardConn(custNo);
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, custNo);
pstmt.setInt(2, lineNo);
pstmt.setInt(3, orderNo);
pstmt.setInt(4, stockNo);
pstmt.setInt(5, quantity);
pstmt.setFloat(6, discount);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
} // addLineItemWithinParent
ノート:
ほとんどの場合、完全なレベル・キー・コンテキストがアプリケーション・コンテキストで使用可能になります。そのため、LineItems表の先頭列custNoコンポジット・レベル・キーに基づいて、シャードに直接接続する追加のメソッドaddLineItemWithinParent()を導入しています。これにより、シャード・カタログへのラウンドトリップがなくなります。ここに示したヘルパー関数getCustomerFromOrder()と同様のコストの高いシャーディング・プログラミング手法は、可能なかぎり使用しないでください。集計問合せの大部分は、シャード・カタログ接続を使用して実行する必要があります。シャード・カタログ・データベースは、プロキシ・ルーティングを使用してシャードからの部分的な結果を収集します。最終集計は、シャードによって生成された部分的な結果に基づいて生成されます。そのため、次に示すように、元のPOManagerクラスに導入されたtotalOrders()メソッドは、シャード・カタログ・データベースに接続するようにリライトされています。
//
// xshard aggregate connects to the shard catalog
//
public static void totalOrders () throws SQLException {
String sql =
"SELECT O.PONo, ROUND(SUM(S.Price * L.Quantity)) AS TOTAL " +
"FROM Orders O, LineItems L, StockItems S " +
"WHERE O.PONo = L.PONo AND L.StockNo = S.StockNo " +
"GROUP BY O.PONo";
try {
Connection conn = getCatConn();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rset = pstmt.executeQuery();
printResults(rset);
rset.close();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
} // totalOrders
前の手順で導入したprintResults()ヘルパー関数は、データベース構造に依存しないため変更は不要です。
//
// helper function – no change required
//
static void printResults (ResultSet rset) throws SQLException {
String buffer = "";
try {
ResultSetMetaData meta = rset.getMetaData();
int cols = meta.getColumnCount(), rows = 0;
for (int i = 1; i <= cols; i++) {
int size = meta.getPrecision(i);
String label = meta.getColumnLabel(i);
if (label.length() > size) size = label.length();
while (label.length() < size) label += " ";
buffer = buffer + label + " ";
}
buffer = buffer + "\n";
while (rset.next()) {
rows++;
for (int i = 1; i <= cols; i++) {
int size = meta.getPrecision(i);
String label = meta.getColumnLabel(i);
String value = rset.getString(i);
if (label.length() > size) size = label.length();
while (value.length() < size) value += " ";
buffer = buffer + value + " ";
}
buffer = buffer + "\n";
}
if (rows == 0) buffer = "No data found!\n";
System.out.println(buffer);
} catch (SQLException e) {System.err.println(e.getMessage());}
} // printResults
StockItem表のキー列stockNoの値は、すべてのシャードのOrder表の行と一致する可能性があります。そのため、シャード・カタログに接続するように、元のPOManagerクラスに導入したcheckStockItem()メソッドを変更する必要があります。シャード・カタログ・データベースは、各シャードで実行されたローカル結合の結果として返されるすべての行の結合を返します。
//
// xshard query matching duplicated table
//
public static void checkStockItem (int stockNo)
throws SQLException {
String sql = "SELECT O.PONo, O.CustNo, L.StockNo, " +
"L.LineNo, L.Quantity, L.Discount " +
"FROM Orders O, LineItems L " +
"WHERE O.PONo = L.PONo AND L.StockNo = ?";
try {
Connection conn = getCatConn();
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, stockNo);
ResultSet rset = pstmt.executeQuery();
printResults(rset);
rset.close();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
} // checkStockItem
元のPOManagerクラスに導入したchangeQuantity()メソッドでは、孫表LineItemsを更新します。繰り返しになりますが、getCustomerFromOrder()ヘルパー・メソッドを使用してシャーディング・キーを取得し、アプリケーションが更新を実行する適切なシャードに接続できるようにします。addLineItem()メソッドの変更と同様に、custNo列の値はアプリケーション・コンテキストでも使用可能であると考えられます。そのため、changeQuantity()でchangeQuantifyWithinParent()を使用して、シャード・カタログへのラウンド・トリップを省くようにします。
//
// Get customer (parent) from the catalog then update a shard!
//
public static void changeQuantity (int newQty,
int orderNo, int stockNo)
throws SQLException
{
String sql = "UPDATE LineItems SET Quantity = ? " +
"WHERE CustNo = ? AND PONo = ? AND StockNo = ?";
try {
int custNo = getCustomerFromOrder(orderNo);
Connection conn = getShardConn(custNo);
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, newQty);
pstmt.setInt(2, custNo);
pstmt.setInt(3, orderNo);
pstmt.setInt(4, stockNo);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
} // changeQuantity
//
// You’ve got the full level key handy, update shard directly.
//
public static void changeQuantityWithinParent (int newQty,
int custNo, int orderNo, int stockNo)
throws SQLException
{
String sql = "UPDATE LineItems SET Quantity = ? " +
"WHERE CustNo = ? AND PONo = ? AND StockNo = ?";
try {
Connection conn = getShardConn(custNo);
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, newQty);
pstmt.setInt(2, custNo);
pstmt.setInt(3, orderNo);
pstmt.setInt(4, stockNo);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
} // changeQuantityWithinParent
最後のメソッドdeleteOrder()に対する変更は、addLineItem()およびchangeQuantity()に適用したものと同じガイドラインに従います。LineItems表から行を削除する前に、アプリケーションでは、まず、リクエストされたオーダーに対応するcustNoシャーディング・キー値についてシャード・カタログを参照する必要があります。シャーディング・キーを取得したら、シャードに接続して削除を実行します。繰り返しになりますが、アプリケーション・コンテキストでもシャーディング・キー値が使用可能であると考えて、deleteOrderWithinParent()メソッドを使用します。
//
// Get customer (parent) first from the catalog, delete in a shard!
//
public static void deleteOrder (int orderNo) throws SQLException
{
String sql = "DELETE FROM LineItems WHERE CustNo = ? AND PONo = ?";
try {
int custNo = getCustomerFromOrder(orderNo);
Connection conn = getShardConn(custNo);
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, orderNo);
pstmt.executeUpdate();
sql = "DELETE FROM Orders WHERE PONo = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, custNo);
pstmt.setInt(2, orderNo);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
} // deleteOrder
//
// You’ve got whole level key handy, delete in shard directly
//
public static void deleteOrderWithinParent (int custNo,
int orderNo)
throws SQLException
{
String sql = "DELETE FROM LineItems WHERE CustNo = ? AND PONo = ?";
try {
Connection conn = getShardConn(custNo);
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, orderNo);
pstmt.executeUpdate();
sql = "DELETE FROM Orders WHERE PONo = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, custNo);
pstmt.setInt(2, orderNo);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
} // deleteOrderWithinParent
} // POManager
親トピック: シャード・データベースへのデータの移行