DB Link とは?

DB Link(データベースリンク)は、あるデータベースから別のリモートデータベースに透過的にアクセスするための機能です。リモート DB のテーブルを、あたかもローカルのオブジェクトのように SQL で参照できます。

特徴説明
透過的アクセステーブル名の後ろに @リンク名 を付けるだけでリモート DB を参照できる
クロス DB JOIN異なる DB 間で JOIN やサブクエリをシームレスに実行できる
分散システムマイクロサービス間・本番/DWH 間など多様な連携シナリオで利用される
-- テーブル名の後ろに「@リンク名」を付けるだけでリモート DB を参照
SELECT * FROM employees@remote_db;

DB Link の仕組み

クエリがどのような経路で実行されるかを理解することが、ボトルネック解析の第一歩です。

  1. ローカル DB が SQL を受け付け、DB Link を検出する
  2. ローカル DB からリモート DB へ接続確立リクエストを送信する
  3. リモート DB が接続要求を受け付け、SQL を実行し、結果セットを返す
  4. ローカル DB が受信した結果とローカル結果を結合し、アプリに返す
ポイント:ローカル DB とリモート DB 間のすべての通信は「ネットワーク」を経由する = 遅延・帯域・ラウンドトリップが発生します。

ボトルネックの全体像

DB Link が性能劣化を引き起こす主要な原因は 6 つに整理できます。

#原因概要
01ネットワーク遅延(RTT)通信のたびに往復時間が加算される
02N+1 ラウンドトリップループ内で繰り返しリモートアクセスが発生する
03Fetch 遅延フェッチサイズ不足で往復回数が激増する
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 依存を最小限に。