DB Link とは?
DB Link(データベースリンク)は、あるデータベースから別のリモートデータベースに透過的にアクセスするための機能です。リモート DB のテーブルを、あたかもローカルのオブジェクトのように SQL で参照できます。
| 特徴 | 説明 |
|---|---|
| 透過的アクセス | テーブル名の後ろに @リンク名 を付けるだけでリモート DB を参照できる |
| クロス DB JOIN | 異なる DB 間で JOIN やサブクエリをシームレスに実行できる |
| 分散システム | マイクロサービス間・本番/DWH 間など多様な連携シナリオで利用される |
-- テーブル名の後ろに「@リンク名」を付けるだけでリモート DB を参照
SELECT * FROM employees@remote_db;
DB Link の仕組み
クエリがどのような経路で実行されるかを理解することが、ボトルネック解析の第一歩です。
- ローカル DB が SQL を受け付け、DB Link を検出する
- ローカル DB からリモート DB へ接続確立リクエストを送信する
- リモート DB が接続要求を受け付け、SQL を実行し、結果セットを返す
- ローカル DB が受信した結果とローカル結果を結合し、アプリに返す
ポイント:ローカル DB とリモート DB 間のすべての通信は「ネットワーク」を経由する = 遅延・帯域・ラウンドトリップが発生します。
ボトルネックの全体像
DB Link が性能劣化を引き起こす主要な原因は 6 つに整理できます。
| # | 原因 | 概要 |
|---|---|---|
| 01 | ネットワーク遅延(RTT) | 通信のたびに往復時間が加算される |
| 02 | N+1 ラウンドトリップ | ループ内で繰り返しリモートアクセスが発生する |
| 03 | Fetch 遅延 | フェッチサイズ不足で往復回数が激増する |
| 04 | 大量データ転送 | 必要以上のデータをネット越しに転送する |
| 05 | 実行計画の誤り | 統計情報不足で非効率なプランを選択してしまう |
| 06 | 接続オーバーヘッド | 接続確立のコストが繰り返し発生する |
ボトルネック 01:ネットワーク遅延(RTT)
ローカル DB は SQL 解析後、リモート DB へリクエストを送信し、処理された結果が返ってくるまで待機します。この「往復時間(RTT: Round-Trip Time)」が毎回のクエリに加算されます。
- 同一 DC 内でも 1〜5 ms、別拠点では 10〜100 ms 以上になることも
- 複数の DB Link 呼び出しがあれば、その分だけ積み重なる
| 通信経路 | RTT の目安 |
|---|---|
| 同一 DC 内 | 1〜5 ms |
| 別拠点・広域ネットワーク | 10〜100 ms 以上 |
RTT が 10 ms の環境で 1,000 回の DB Link 呼び出しがあれば、それだけで 10 秒 の待機が発生します。
ボトルネック 02:N+1 ラウンドトリップ問題
最もよく発生する深刻なボトルネックです。ループ処理の中で DB Link を呼び出すと、N レコード分のネットワーク往復が発生します。
NG パターン:N+1 ラウンドトリップ
-- 注文1,000件に対して顧客情報を取得
FOR rec IN (SELECT order_id, cust_id FROM orders) LOOP
-- ここで DB Link へ 1,000 回アクセス!
SELECT name INTO v_name
FROM customers@remote_db
WHERE cust_id = rec.cust_id;
END LOOP;
-- 1,000回 x 10ms RTT = 計10秒の待機!
OK パターン:1 回の JOIN に集約
-- 1 回の SQL で JOIN して取得
SELECT o.order_id,
c.name
FROM orders o
JOIN customers@remote_db c
ON o.cust_id = c.cust_id;
-- 1回 x 10ms RTT = 0.01秒で完了!(約1,000倍速)
ループ内での DB Link 呼び出しは必ず JOIN またはバルク操作に置き換えること。最大 1,000 倍以上の差が出ます。
ボトルネック 03:Fetch 遅延
DB Link 越しに大量の行を取得する場合、1 回のネットワーク通信で取得する行数(フェッチサイズ)が小さすぎると、行ごとに往復が発生して深刻な遅延になります。
DB Link 使用時のデフォルト fetch_size は小さい場合が多く、見落としやすいボトルネックです。
フェッチサイズと所要時間の比較(10,000 行取得・RTT 10ms)
| フェッチサイズ | 往復回数 | 計算結果 |
|---|---|---|
| 1 行(デフォルト) | 10,000 回 | 100 秒 |
| 15 行 | 667 回 | 6.7 秒 |
| 100 行 | 100 回 | 1 秒 |
| 1,000 行 | 10 回 | 0.1 秒 |
フェッチサイズを 1,000 行以上に設定することで、最大 1,000 倍の改善が見込めます。
-- JDBC の場合
stmt.setFetchSize(1000);
-- Oracle SQL*Plus / sqlnet.ora の場合
SET ARRAYSIZE 1000
ボトルネック 04:大量データ転送
必要以上のデータをネットワーク越しに転送することで、帯域を無駄に消費します。
NG パターン:全列転送後にローカルでフィルタ
SELECT * FROM big_table@remote
WHERE local_date = :date
-- → 全列をネット越しに転送後、ローカルでフィルタ
-- 100万行 x 100列 = 数GBの無駄な転送が発生することも
OK パターン:必要な列・行のみ転送(プッシュダウン活用)
SELECT id, name FROM big_table@remote
WHERE date = :date
-- → 必要な列・行のみ転送
-- WHERE 条件がリモート側にプッシュダウンされ転送量が激減
ボトルネック 05:実行計画の誤り
ローカルのオプティマイザはリモートテーブルの統計情報を正確に把握できないため、非効率な実行計画を選択してしまうことがあります。
- リモート行数を過小 / 過大に見積もり、不適切な JOIN 方式(Nested Loop vs Hash Join)を選択する
- 統計情報が古いと、この問題がさらに悪化する
対策
-- リモートテーブルの統計情報を定期更新(Oracle)
EXEC DBMS_STATS.GATHER_DATABASE_STATS;
-- ヒント句で実行サイトを明示的に制御
SELECT /*+ DRIVING_SITE(c) */
o.order_id, c.name
FROM orders o
JOIN customers@remote_db c
ON o.cust_id = c.cust_id;
対策・改善方法まとめ
| # | 対策 | 具体的なアクション |
|---|---|---|
| 01 | ループ内アクセスを排除 | N+1 問題を JOIN / バルク操作に置き換える。FORALL / BULK COLLECT で SQL を 1 回に集約する |
| 02 | フェッチサイズを増やす | fetch_size / arraysize を 1,000 行以上に設定する。JDBC では stmt.setFetchSize(1000) |
| 03 | SELECT * を禁止 | 必要な列・行のみをリモートから取得する。WHERE 条件をリモート側にプッシュダウンさせる |
| 04 | マテリアライズドビュー活用 | 頻繁アクセスするリモートデータをローカルにキャッシュする。差分リフレッシュで同期コストを最小化する |
| 05 | 統計情報の定期更新 | リモートテーブルの統計を最新に保つ。ヒント句でオプティマイザをサポートする |
| 06 | アーキテクチャ見直し | MQ / API による非同期連携も検討する。データを統合して DB Link 依存を最小化する |
まとめ
| ボトルネック | ひと言まとめ |
|---|---|
| 01 RTT(ネットワーク遅延) | 毎回の DB Link 呼び出しに往復時間が加算。積み重なると深刻な遅延に。 |
| 02 N+1 問題が最多の原因 | ループ内アクセスは必ず JOIN / バルク操作に置き換える。最大 1,000 倍の差が出る。 |
| 03 Fetch 遅延を見落とすな | フェッチサイズ不足で 10,000 行が 100 秒かかることも。1,000 行/fetch に設定すると 0.1 秒に。 |
| 04 必要なデータのみ転送 | SELECT * は厳禁。列・行の絞り込みとプッシュダウンで転送量を最小化。 |
| 05 根本的な設計の見直し | 頻繁なアクセスは MV やレプリケーションへ。DB Link 依存を最小限に。 |