朝の業務開始直後だけ、アプリケーションの DB 接続が数十件まとめて ORA-03135 で失敗する。
リトライすると成功し、日中は一度も発生しない。
アラートログを開いても、障害時刻の周辺には何も出ていない…。
長い無通信の直後に ORA-03135 や ORA-03113 が出ます。
このエラーの代表的な原因は、経路上のファイアウォール(以下 FW)のアイドルタイムアウトによる接続の切断です。
そして、このとき DB サーバのアラートログに時間を置いて記録されるのが、Fatal NI connect error 12170(TNS-12535: TNS:operation timed out)です。
今回は、FW 役の VM を間に挟んだ検証環境でこの切断を実機再現し、FW 側の情報(設定・カウンタ)を見ずに原因を特定するところまでの手順を、実機ログとともに整理します。
あわせて、エラー番号(ORA-03135 / ORA-03113)の分かれ方、アラートログに TNS-12535 が記録される仕組み、恒久対処 SQLNET.EXPIRE_TIME(DCD)が効く条件も実測で確認します。
切り分けの手順だけ知りたい場合は「5. 切り分け①」から、対処だけ知りたい場合は「8. 対処」から、アラートログの TNS-12535 を調べに来た場合は「9. DB サーバ側のエラー」から読めます。
1. 症状
放置していた接続で次の SQL を実行したときに、ORA-03135 が返ります。
SQL> select sysdate from dual;
select sysdate from dual
*
行1でエラーが発生しました。:
ORA-03135: 接続が失われました
プロセスID: 347941
セッションID: 272、シリアル番号: 15938
ヘルプ: https://docs.oracle.com/error-help/db/ora-03135/現場では、例えば次のような形で現れます。
FW のリプレースや設定変更をした後の朝、業務開始直後だけ、接続プール経由の初回処理が数十件まとめて失敗します。
リトライすると成功し、以降、日中は一度も発生しません。
ところが翌朝、また業務開始直後だけ同じエラーが出ます。
一方で、夜間バッチは正常に終わっており、ping・tnsping も正常、DB のアラートログにも目立った異常はありません。
NW チームに確認しても、「FW のエラーカウンタ・ドロップカウンタはゼロ。FW は問題ない」という回答です。
どこにも異常の記録が残っていないのに、朝だけ失敗が繰り返される、という状況です。
同じ状況で、エラー番号が ORA-03113(通信チャネルで end-of-file が検出されました)になることもあります。
どちらも「確立済みの接続が失われた」ことを示す同種のエラーで、原因の調べ方は同じです。
エラー番号が分かれる理由は実測で確認できたので、後述の「ORA-03113 と ORA-03135 の違い」の章で整理します。
※先頭のゼロを省いて ORA-3135 / ORA-3113 と表記されることもありますが、同じエラーを指します。
また、調査の入口がクライアント側のエラーではなく、DB サーバのアラートログの場合もあります。
心当たりのない時刻に Fatal NI connect error 12170.(TNS-12535: TNS:operation timed out)が記録されている、という形です。
これも同じ事象を DB サーバ側で確認できる情報で、「DB サーバ側のエラー」の章で実測とともに説明します。
2. 原因の仕組み: ステートフル FW のアイドルタイムアウト
なお本記事では、クライアントと DB サーバの間の TCP のつながりを「接続」、その接続の上で DB サーバ側に作られる状態(サーバプロセスなど)を「セッション」と呼び分けます。
現場では「セッションが切れる」と言うことも多いですが、後半で「接続は切れたのにセッションは残る」という状態が話の核心になるため、区別して使います。
ステートフル FW(この検査方式はステートフルインスペクションとも呼ばれます)は、通過を許可した TCP 接続を状態テーブル(ステートテーブル)に記録し、以降のパケットを「記録済みの接続に属するか」で判定します。
このエントリにはアイドルタイムアウトがあり、一定時間パケットが流れなかった接続のエントリは削除されます。
問題は、この削除がクライアントにも DB サーバにも知らされないことです。
「1. 症状」に挙げた朝の例に当てはめると、よくある事象の進み方は次のようになります。
| 時刻(例) | 機器 | 起きること |
|---|---|---|
| 前日 18:00 | クライアント・FW・DB サーバ | 接続プールが DB サーバへの接続を確立し、日中の業務の SQL に使っている。FW はこの接続をステートエントリとして記録する。18:00 の SQL を最後に無通信になる |
| 前日 19:00 | FW | ステートエントリの無通信時間がアイドルタイムアウト(この例では 60 分とする)を超える |
| 前日 19:00 | FW | ステートエントリを削除する。このときクライアントにも DB サーバにも何も送られない |
| 前日 19:00 以降 | クライアント・DB サーバ | 切断を知らされていないため、接続をまだ有効(ESTABLISHED)と認識したまま(この誤認は、DB サーバは次の行の検出まで・クライアントは翌朝のエラーまで続く) |
| 前日 20:00 すぎ | DB サーバ | OS が無通信の接続へ自動送信する生存確認パケット(TCP keepalive・デフォルトでは最後の通信の約 2 時間後に開始)が FW に破棄されて応答が返らず、ここで初めて接続の切断を検出する。アラートログに Fatal NI connect error 12170(TNS-12535) が記録される(「DB サーバ側のエラー」の章で実測)。記録は朝のエラーより前に出る |
| 翌朝 8:30 | クライアント | 業務を開始したアプリケーションがこの接続で最初の SQL を実行し、DB サーバ宛てにパケットを送信する |
| 翌朝 8:30 | FW | そのパケットを「記録にない接続」のものとして破棄する。DB サーバには届かない |
| 翌朝 8:30〜 | クライアント | TCP が応答のない再送を繰り返し、打ち切った時点でエラーが確定する(ORA-03113 / ORA-03135) |
| エラー直後 | クライアント | 接続プールがエラーになった接続を破棄して張り直す。リトライは新規接続なので成功する |
表の時刻(前日 18:00・翌朝 8:30 など)と、アイドルタイムアウト 60 分・生存確認の開始まで約 2 時間という値は、この例に合わせた場面設定です。実際の値は環境・製品によって異なります。
接続プール以外でも、長時間無通信になる確立済みの接続であれば同じことが起きます。
この仕組みが分かると、「1. 症状」に挙げた特徴の残りも説明がつきます。
まず、失敗するかどうかを決めているのは時刻ではなく、その接続の直前の無通信時間です。
朝に数十件まとめて出るように見えるのは、夜間に放置された接続の「放置明け最初の実行」が業務開始の時間帯に集中するためで、どこかの瞬間に全接続が一斉に切断されるわけではありません(切断自体は、夜間のうちに接続ごとのタイムアウト超過時点で順次起きています)。
日中に発生しないのは、接続の使い回しの間隔がアイドルタイムアウト未満に収まり、エントリが維持され続けるためです。
夜間バッチが正常なのは、バッチが開始時に新規接続を張るためで、無通信のまま放置された古い接続だけが失敗します。
そして FW のカウンタがゼロのままなのは、アイドルタイムアウトによるエントリ削除が多くの製品で正常処理として扱われ、エラーにもドロップにも計上されないためです。
つまり、「カウンタがゼロ」は FW を原因から除外する根拠になりません。
そこで本記事では、FW の管理画面やカウンタを見ずに、クライアント側と DB サーバ側で取れる情報だけで原因を特定するところまでを実機で確認します。
3. 検証環境と再現の設計
クライアントと DB サーバの間に FW 役の Linux VM を1台挟んだ、次の3台構成で再現します。
クライアント(ap-node) FW役(fw-node) DBサーバ(db-node)
192.168.30.12 192.168.30.104 | 10.0.10.104 10.0.10.11
└──────────────────────┘ conntrack └──────────────────────┘
(established 300秒)| 役割 | ホスト | ソフトウェア |
|---|---|---|
| クライアント(AP サーバ相当) | ap-node | SQL*Plus 23.26(Oracle Linux 9) |
| FW 役 | fw-node | Oracle Linux 8(conntrack + iptables) |
| DB サーバ | db-node | Oracle Database 19c(19.28 RU)・Oracle Linux 8 |
FW 役 VM は、クライアントと DB サーバの間のパケットを中継するルータとして動かします。
その上で使うのが、Linux カーネルの接続追跡機能 conntrack です。
conntrack は、この VM を通過する接続を1本ずつ「確立済み(ESTABLISHED)」などの状態付きで記録する仕組みで、「2. 原因の仕組み」で説明したステートフル FW の状態テーブルにあたります。
iptables がこの記録を参照して「記録にある接続のパケットだけを通す」判定を行うため、実 FW と同じステートフル動作になります。
クライアント(192.168.30.0/24)と DB サーバ(10.0.10.0/24)は、あえて別の IP セグメント(ネットワーク帯)に置いています。
同じセグメントに置いた2台はルータを経由せず直接通信してしまい、パケットが FW 役を通らないためです。
セグメントを分けることで「FW を通過しないと相手に届かない」配置になり、セグメント境界に FW が立つ実環境と同じ構図になります。
さらに両端のホストには、相手の1台だけを宛先にしたスタティックルート(/32)を追加し、通信経路が既定のゲートウェイではなく必ず FW 役 VM を通るように固定しています。
FW 役の設定は次のとおりです(root で実行)。
# sysctl -w net.ipv4.ip_forward=1
# iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# iptables -A FORWARD -p icmp -j ACCEPT
# iptables -A FORWARD -p tcp --syn -d 10.0.10.11 --dport 1521 -j ACCEPT
# iptables -P FORWARD DROP
# sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=300
# sysctl -w net.netfilter.nf_conntrack_tcp_loose=0この設定のポイントは次のとおりです。
- FORWARD のポリシーを DROP にする: エントリを失った接続のパケットは、RST も ICMP も返らず破棄されます。「通知のない切断」の再現に必要な設定です
- established のタイムアウトを 300 秒(5分)に短縮する: Oracle Linux 8 のデフォルトは 432000 秒(5日)です。実 FW のデフォルト値は製品により 30〜60 分程度ですが、今回は検証のため 300 秒にしています(値が違っても失効の仕組みは同じです)
- nf_conntrack_tcp_loose=0 にする: デフォルトの 1 のままだと、エントリを失った後でも通信途中のパケットから conntrack が状態を再作成してしまい、接続の切断が再現できません。ステートを厳格に扱う実 FW の動作に合わせるための設定です
クライアント側にも、時間短縮の設定を1つ入れています(root@ap-node で実行)。
# sysctl -w net.ipv4.tcp_retries2=5net.ipv4.tcp_retries2 は、TCP が応答のない再送を打ち切るまでの回数を決める sysctl パラメータです。
再送の間隔は失敗するたびに約2倍ずつ伸びていくため、回数のわずかな差が時間では大きな差になります。
デフォルトの 15 回のままだと、切断された接続への送信がエラーになるまで 15〜20 分程度かかるため、5 回に減らして十数秒で確定するようにしています(実際に間隔が倍々に伸びていく様子は「2地点 tcpdump」の章で実測します)。
このように、本検証では検証時間を短縮するために実務と異なる値を意図的に設定しています。
実務の値との対応は次のとおりで、桁は違っても境界条件の考え方は同じです。
| 項目 | 本検証 | 実務の目安 |
|---|---|---|
| FW アイドルタイムアウト | 5 分(300 秒) | 30〜60 分程度(製品・設定による) |
| クライアントの tcp_retries2(TCP 再送の打ち切り回数) | 5(エラー確定まで十数秒) | 15(Linux デフォルト・エラー確定まで 15〜20 分程度) |
| SQLNET.EXPIRE_TIME | 1 分 | 10 分など(FW タイムアウトより短い値) |
確認方法は全手順で共通です。
新規接続を張り、set time on で時刻を表示させたまま host sleep <秒> で N 分間無通信で放置し、次の SELECT が成功するかを確認します。
host は SQL*Plus の中から OS コマンドを実行するコマンドで、host sleep 360 なら OS の sleep コマンドが 360 秒待ちます。
待っている間、SQL*Plus は DB への接続に1パケットも送りません。これで「指定した秒数ちょうどの無通信」を意図的に作れます。
$ sqlplus system/<パスワード>@10.0.10.11:1521/v11c19u
SQL> set time on
SQL> select instance_name from v$instance;
SQL> select sysdate from dual;
SQL> host sleep 360
SQL> select sysdate from dual;最後の3行(SELECT → sleep → SELECT)はまとめて貼り付けて実行します。
こうすると「最後の SQL → 直後に sleep 開始 → sleep 終了と同時に次の SELECT」が機械的につながり、無通信時間が sleep の秒数どおりに固定されます。
無通信時間の証拠は、set time on が表示するプロンプトの時刻の差(最後の SQL → 次の SELECT)です。
4. 実機再現: 放置した接続が次の SQL でエラーになる
まず、対策なしの状態で接続の切断を再現します(Before)。
手順は「3. 検証環境と再現の設計」の共通手順どおり、接続を張り、最後の SQL から FW のタイムアウト(300 秒)を超える 6 分(360 秒)放置し、次の SELECT がどうなるかを確認します。
この回は、クライアントの SQL*Plus・FW 役 VM の状態テーブル・両端の tcpdump を同時に観測しています(パケットは後述の「2地点 tcpdump」の章で見ます)。
起きたことを時系列で追います。
| 時刻 | 機器 | 起きたこと |
|---|---|---|
| 17:52:41 | クライアント | 最後の SQL(select sysdate・即応答)。直後に sleep が始まり、ここから無通信 |
| 17:55:29 | FW | ステートエントリの残り 131 秒を観測(経過 168 秒) |
| 17:57:39 | FW | 残り 1 秒 |
| 17:57:41 ごろ | FW | 無通信が 300 秒に達し、エントリが期限切れで削除される(計算値。クライアントには何も通知されない) |
| 17:57:43 | FW | エントリが消えているのを確認 |
| 17:58:41 | クライアント | 放置明けの SELECT が実行される(無通信ちょうど 6 分 00 秒)。応答がないまま待たされる |
| 17:58:54 | クライアント | ORA-03113 が確定(SELECT から 13 秒) |
以下、それぞれの時点の実ログです。
17:52:41 クライアント(oracle@ap-node): 最後の SQL の直後から放置開始
17:52:41 SQL> host sleep 360プロンプトの 17:52:41 が、最後の SQL(select sysdate)が応答を返した時刻です。
その直後に sleep が始まり、この接続は無通信になります。
17:55:29〜17:57:39 FW(root@fw-node): エントリの残り秒数が減っていく
残り秒数は、/proc/net/nf_conntrack の各エントリの ESTABLISHED の直前の数値に表示されます。
コマンド末尾の echo "exit=$?" は、直前の grep の終了コード(マッチあり=0・マッチなし=1)を表示させるためのものです。
$? が終了コードの本体で、exit= はあとからログを読むときのためのただの文字列ラベルです(シェルの exit コマンドとは無関係です)。
エントリが消えると grep の出力は空になるため、そのままでは「出力が無い」のがエントリ削除のせいか取得ミスのせいか記録上区別できません。exit=1 を残すことで「grep は実行されたがマッチ0件」を記録そのもので示せます。
クライアントが sleep で接続を放置している間、FW 側ではこの確認を繰り返していました。そのうち2回分を示します。
まず1回目、放置開始から約3分後の観測です。
# date && grep "dport=1521" /proc/net/nf_conntrack ; echo "exit=$?"
2026年 8月 14日 金曜日 17:55:29 JST
ipv4 2 tcp 6 131 ESTABLISHED src=192.168.30.12 dst=10.0.10.11 sport=17630 dport=1521 src=10.0.10.11 dst=192.168.30.12 sport=1521 dport=17630 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 zone=0 use=2
exit=0その約2分後の2回目、タイムアウト直前の観測です。
# date && grep "dport=1521" /proc/net/nf_conntrack ; echo "exit=$?"
2026年 8月 14日 金曜日 17:57:39 JST
ipv4 2 tcp 6 1 ESTABLISHED src=192.168.30.12 dst=10.0.10.11 sport=17630 dport=1521 src=10.0.10.11 dst=192.168.30.12 sport=1521 dport=17630 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 zone=0 use=2
exit=01回目(17:55:29)は経過 168 秒で残り 131 秒、2回目(17:57:39)は残り 1 秒です。
放置中はこの確認を計8回行っており、観測値はすべて「300 − 経過秒数」と一致していました(秒の端数により最大1秒の差)。
この値は通信があるたびに 300 に戻り、無通信の間は減り続けます。
17:57:43 FW(root@fw-node): エントリは消えた
残り 1 秒の観測から 4 秒後に確認すると、エントリは消えています。
# date && grep "dport=1521" /proc/net/nf_conntrack ; echo "exit=$?"
2026年 8月 14日 金曜日 17:57:43 JST
exit=1grep が何もマッチしない(exit=1)=エントリが削除済み、という状態です。
この削除はエラーとしてではなく期限切れの通常処理として扱われるため、どのカウンタにも計上されません。
これが「カウンタゼロ」の正体です。
17:58:41 クライアント(oracle@ap-node): 放置明けの SELECT が ORA-03113
エントリが消えた後、sleep の終了と同時に、先に入力しておいた SELECT が実行されます。
17:58:41 SQL> select sysdate from dual;
select sysdate from dual
*
行1でエラーが発生しました。:
ORA-03113: 通信チャネルでend-of-fileが検出されました
プロセスID: 346895
セッションID: 268、シリアル番号: 52144
ヘルプ: https://docs.oracle.com/error-help/db/ora-03113/
17:58:54 SQL>SELECT は即座には失敗せず、応答のないパケットを TCP が再送し続け、13 秒後にエラーが確定しました(エラー後のプロンプトが 17:58:54)。
再送の中身は後述の「2地点 tcpdump」の章で確認します。
なお、この 13 秒は「3. 検証環境と再現の設計」で再送打ち切りを短縮した状態(tcp_retries2=5)の値です。
tcp_retries2 がデフォルト(15 回)の場合は、ここが 15〜20 分程度のハングになります。
実務ではこの FW の状態テーブルの中身を見せてもらえないことが多いので、次章からは FW 側の情報を使わずに、クライアントと DB サーバで取れる情報だけで同じ結論に到達します。
なお、記事タイトルの ORA-03135 ではなく ORA-03113 が出ています。
これは FW 役を「応答を返さずに破棄する(DROP)」設定にしているためで、エラー番号が分かれる仕組みは「ORA-03113 と ORA-03135 の違い」の章で実測とともに説明します。
5. 切り分け①: 放置時間を変えて、タイムアウトの境界を特定する
接続の切断が「無通信時間」に依存するなら、共通手順の sleep の秒数(=放置する時間)を変えながら同じ確認を繰り返せば、切断が始まる境界の時間を特定できます。
「この放置時間なら接続が維持される」「この放置時間なら切断される」という両側の基準点を先に取り、その間を詰めていきます。
接続が切断される側の基準点は、「4. 実機再現」で実測済みです(無通信 6 分 00 秒で ORA-03113)。
接続が維持される側の基準点から、放置する時間を変えて実測した結果です(毎回新規接続で実施)。
| 放置時間(sleep の秒数) | この確認の役割 | 結果 |
|---|---|---|
| 2分00秒 | 接続が維持される側の基準点 | 接続維持(SELECT が即返る) |
| 5分00秒 | 基準点の間を詰める | 接続維持(SELECT が即返る) |
| 6分00秒 | 接続が切断される側の基準点(「4. 実機再現」の回) | 接続切断(ORA-03113) |
結論は「5分の放置では接続が維持され、6分の放置で切断される」です。
この2点から、経路上のどこかに 5〜6 分のアイドルタイムアウトを持つ機器があると確定でき、FW 役の設定値 300 秒(5分)と一致します。
この手法の注意点が1つあります。
5分00秒のテストは設定値ちょうどの境界の真上で、今回は「接続が維持される」側に倒れましたが、エントリの失効処理とパケットの到着の前後関係しだいで結果が揺れる位置です。
判定は境界を明確に挟む2点(今回なら5分と6分)で取り、境界ちょうどの1回の結果で確定しないようにします。
実務では FW のタイムアウトは 30〜60 分程度が多いため、境界は例えば「61 分は接続維持・62 分で切断」のような形になります。
1回のテストに1時間かかるため、接続が維持される側の基準点(例: 10 分)と、接続が切断される側の基準点(例: 翌朝まで放置)を取ってから間を詰めると効率的です。
この数字が取れれば、NW チームへの依頼を「FW を調べてほしい」ではなく「無通信 61 分は接続維持・62 分で切断という実測がある。新旧 FW の TCP アイドルタイムアウト値を比較してほしい」と、確認箇所を具体的に指定する形にできます。
6. 切り分け②: 2地点 tcpdump で破棄の場所を確定する
前章の実測で「接続がいつ切断されるか」は分かりましたが、「パケットがどこで破棄されているか」はまだ推定です。
これを確定するのが、クライアント側と DB サーバ側の2地点で同時に取る tcpdump です。
「4. 実機再現」の回では、放置の前からこの2地点でパケットを取り続けていました。
クライアント側(root@ap-node)で実行していたコマンドです。
# tcpdump -nn -tttt -i <クライアント側のNIC名> host 10.0.10.11 and port 1521DB サーバ側(root@db-node)でも同時に実行していました。
# tcpdump -nn -tttt -i <DBサーバ側のNIC名> host 192.168.30.12 and port 1521指定しているオプションの意味は次のとおりです。
- -nn: IP アドレスとポート番号を数字のまま表示します(ホスト名・サービス名への逆引きをしない)。表示が正確になり、逆引きの待ちで表示が遅れることもなくなります
- -tttt: タイムスタンプを「年月日 時:分:秒.マイクロ秒」の形式で表示します。2地点のログを時刻で突き合わせるこの検証では必須のオプションです
- -i <NIC名>: パケットを取得するネットワークインターフェースを指定します
- host <相手のIP> and port 1521: 相手ホストとの 1521 番ポートの通信だけに絞り込むフィルタです。取得量をこの接続の分だけに抑えられます(通信量の多い本番環境で対象を絞るときも、この書き方がそのまま使えます)
放置明けの SELECT(17:58:41)の瞬間、クライアント側にはこう記録されていました。
2026-08-14 17:58:41.036191 IP 192.168.30.12.17630 > 10.0.10.11.1521: Flags [P.], seq 4278:4848, ack 5027, win 476, options [nop,nop,TS val 4240143932 ecr 1042576164], length 570
2026-08-14 17:58:41.245038 IP 192.168.30.12.17630 > 10.0.10.11.1521: Flags [P.], seq 4278:4848, ack 5027, win 476, options [nop,nop,TS val 4240144141 ecr 1042576164], length 570
2026-08-14 17:58:41.453045 IP 192.168.30.12.17630 > 10.0.10.11.1521: Flags [P.], seq 4278:4848, ack 5027, win 476, options [nop,nop,TS val 4240144349 ecr 1042576164], length 570
2026-08-14 17:58:41.861032 IP 192.168.30.12.17630 > 10.0.10.11.1521: Flags [P.], seq 4278:4848, ack 5027, win 476, options [nop,nop,TS val 4240144757 ecr 1042576164], length 570
2026-08-14 17:58:42.693022 IP 192.168.30.12.17630 > 10.0.10.11.1521: Flags [P.], seq 4278:4848, ack 5027, win 476, options [nop,nop,TS val 4240145589 ecr 1042576164], length 570
2026-08-14 17:58:44.357046 IP 192.168.30.12.17630 > 10.0.10.11.1521: Flags [P.], seq 4278:4848, ack 5027, win 476, options [nop,nop,TS val 4240147253 ecr 1042576164], length 570
2026-08-14 17:58:47.621028 IP 192.168.30.12.17630 > 10.0.10.11.1521: Flags [P.], seq 4278:4848, ack 5027, win 476, options [nop,nop,TS val 4240150517 ecr 1042576164], length 570クライアントが再送を続けていたこの 17:58:41〜17:58:47 の間、DB サーバ側には1パケットも記録されていません。
DB サーバ側にあるこの接続の最後の記録は放置前(17:52:41.062308 の ACK)で、以降、17:58 台を含めて記録はゼロでした。
この2つの対比から読み取れることを整理します。
- クライアント側の 7 行は、すべて同一のシーケンス番号(seq 4278:4848)・同一の長さ(570 バイト)です。新しい送信ではなく、同じデータの再送であることの証明です
- 再送間隔は 0.2 → 0.2 → 0.4 → 0.8 → 1.7 → 3.3 秒と倍々に伸びており、TCP の再送タイマー(RTO)の動きそのものです。この「倍々に伸びる間隔」はデータ再送の特徴で、後の章に出てくる生存確認のパケット(プローブと呼びます。DCD・keepalive が送るもの)が一定間隔で送られるのと対照的です。間隔のパターンから、tcpdump のパケットがどちらの種類かを見分けられます
- クライアント側パケットの TSecr(相手から最後に受け取ったタイムスタンプ)が 7 行とも放置前の値(1042576164)のまま変わっていません。放置以降、DB サーバから1バイトも受信していないことの傍証です
- そして DB サーバ側には届いていない。送信は 7 回、到達は 0 回です
送ったのに届いていない。
つまり、破棄が起きているのはクライアントと DB サーバの「間」の区間だと、クライアント側と DB サーバ側の記録だけで確定できます。
前章で特定した「5〜6 分のアイドルタイムアウト」と合わせれば、FW のカウンタや設定を1度も見ることなく、「この経路の中間機器が、無通信 5〜6 分の確立済み接続のパケットを破棄している」ところまで特定できたことになります。
7. ORA-03113 と ORA-03135 の違い: FW の応答の仕方でエラー番号が変わる
ここまでの再現では、事例で挙げた ORA-03135 ではなく ORA-03113 が出ていました。
この差がどこから来るのかを、FW の応答の仕方(DROP か REJECT か)だけを変え、ほかの条件はすべて同じにした比較実験で確認します。
FW がステート外パケットを扱う方法には、大きく2種類あります。
- 応答を返さずに破棄する(DROP): ここまでの再現で使った設定。クライアントには何も返らない
- RST を返す(REJECT): 破棄と同時に、TCP リセット(RST)パケットを送信元へ返す。RST は「その接続は存在しない」と相手に伝える TCP の強制終了の通知で、受け取った側は接続をその場で放棄する。実 FW にもこの動作の製品・設定が存在する
クライアント・DB サーバ・放置時間(6分)はそのままに、FW 役の設定だけを「ステート外 TCP に RST を返す」に変更します(root@fw-node で実行)。
# iptables -A FORWARD -p tcp -j REJECT --reject-with tcp-resetこの状態で、同じように 6 分放置して SELECT を実行した結果です。
18:27:51 SQL> host sleep 360
18:33:51 SQL> select sysdate from dual;
select sysdate from dual
*
行1でエラーが発生しました。:
ORA-03135: 接続が失われました
プロセスID: 347941
セッションID: 272、シリアル番号: 15938
ヘルプ: https://docs.oracle.com/error-help/db/ora-03135/
18:33:51 SQL>エラー番号が ORA-03135 に変わり、しかも待ち時間ゼロ(SELECT 実行とエラー確定が同じ 18:33:51 の秒内)で確定しました。
DROP のときにあった再送のハングがありません。
このときのクライアント側 tcpdump です。
2026-08-14 18:33:51.277580 IP 192.168.30.12.50460 > 10.0.10.11.1521: Flags [P.], seq 4278:4848, ack 5027, win 476, options [nop,nop,TS val 4242254173 ecr 1044686407], length 570
2026-08-14 18:33:51.277846 IP 10.0.10.11.1521 > 192.168.30.12.50460: Flags [R], seq 2670744188, win 0, length 0SELECT の送信から 0.3 ミリ秒で RST(Flags [R])が返り、再送は1回も起きていません。
なお、この RST の送信元は 10.0.10.11(DB サーバ)に見えますが、同時に取っていた DB サーバ側の tcpdump には SELECT も RST も記録されていません。
つまりこの RST は、DB サーバの IP アドレスを送信元にして FW が生成したものだと、パケットの記録から確認できます。
実測結果を並べると、エラー番号と待ち時間の対応はこうなります。
| FW のステート外パケットへの動作 | クライアントの見え方 | エラー(実測) | 切断の検出のされ方 |
|---|---|---|---|
| 応答なしで破棄(DROP) | 数十秒〜数十分のハングの後にエラー | ORA-03113 | 再送を打ち切って自側で確定(送信タイムアウト) |
| RST を返す(REJECT) | 待ち時間ゼロで即エラー | ORA-03135 | 相手からの切断通知(RST)を受信 |
同一のクライアント・同一の放置時間で、FW の応答の仕方だけを変えたらエラー番号が変わった、という実測です。
少なくとも本検証の構成では、エラー番号を分けたのはクライアントの種類や接続方式ではなく、接続の切断をどう検出したかでした。
この対応は逆向きにも使えます。
「ORA-03135 が待ち時間なしで出る」なら経路上の何かが RST を返しており、「ORA-03113 が長いハングの後に出る」なら応答なしで破棄されている、と障害の起き方から中間機器の動作を推定できます。
冒頭に挙げた症状の例(FW リプレース後の朝に ORA-03135)であれば、新 FW はステート外パケットに RST を返すタイプだった可能性が高い、と読めるわけです。
いずれにせよ ORA-03135 と ORA-03113 は同種のエラーで、切り分けの手順(放置時間の境界の特定・2地点 tcpdump)は共通です。
8. 対処: SQLNET.EXPIRE_TIME(DCD)が効く条件を実測で確認する
原因が確定したら対処です。
恒久対処の第一候補は、DB サーバ側の sqlnet.ora に設定する SQLNET.EXPIRE_TIME(DCD: Dead Connection Detection)です。
指定した分数の間隔で DB サーバ側からクライアントへ極小のパケット(プローブ)を送る機能で、このプローブが FW のアイドルタイマーを定期的にリセットするため、無通信の接続でもステートエントリが失効しなくなります。
設定は、DB サーバの $ORACLE_HOME/network/admin/sqlnet.ora に1行追加するだけです(oracle@db-node で実行)。
DB・リスナーの再起動は不要ですが、設定後の新規接続から有効(既存の接続には効きません)という点に注意してください。
$ cd $ORACLE_HOME/network/admin
$ cp -p sqlnet.ora sqlnet.ora.20260814
$ echo "SQLNET.EXPIRE_TIME=10" >> sqlnet.ora
$ diff sqlnet.ora sqlnet.ora.20260814
6d5
< SQLNET.EXPIRE_TIME=10効かない場合がある: EXPIRE_TIME は FW タイムアウトより短くないと働かない
まず、よく紹介される値 EXPIRE_TIME=10(10分)をそのまま入れて、これまでと同じく 6 分放置して確認しました。
結果は、設定前と同じく、接続は ORA-03113 で切断です。
18:03:29 SQL> host sleep 360
18:09:29 SQL> select sysdate from dual;
select sysdate from dual
*
行1でエラーが発生しました。:
ORA-03113: 通信チャネルでend-of-fileが検出されました
プロセスID: 347445
セッションID: 270、シリアル番号: 20716
ヘルプ: https://docs.oracle.com/error-help/db/ora-03113/
18:09:42 SQL>理由は単純で、最初のプローブは接続から約 10 分後に送られる予定なのに対し、FW のエントリは 5 分で失効するからです。
実際、放置中もクライアント側で tcpdump を取得し続けていましたが、6 分の放置の間に DB サーバからのプローブは1パケットも観測されませんでした。
「DCD を設定したか」ではなく、「EXPIRE_TIME が FW のアイドルタイムアウトより短いか」が成立条件です。
本検証の FW は 5 分なので、値を 1 分に変更します。
$ sed -i 's/^SQLNET.EXPIRE_TIME=10/SQLNET.EXPIRE_TIME=1/' sqlnet.ora
$ diff sqlnet.ora sqlnet.ora.20260814
6d5
< SQLNET.EXPIRE_TIME=1After: 同じ 6 分の放置で接続が切断されなくなった
EXPIRE_TIME=1 の状態で、Before と同一の手順で 6 分放置の確認を実行します。
18:17:58 SQL> host sleep 360
18:23:58 SQL> select sysdate from dual;
SYSDATE
-------------------
2026-08-14 18:23:58
18:23:58 SQL>SELECT が即返り、接続は維持されました。
同じ手順で結果だけが反転した、Before/After の対比です。
このとき何が起きていたかは、放置中の2地点 tcpdump に記録されています。
2026-08-14 18:18:58.707823 IP 10.0.10.11.1521 > 192.168.30.12.44192: Flags [.], ack 4277, win 501, options [nop,nop,TS val 1044153851 ecr 4241301381], length 0
2026-08-14 18:18:58.707845 IP 192.168.30.12.44192 > 10.0.10.11.1521: Flags [.], ack 5027, win 474, options [nop,nop,TS val 4241361603 ecr 1044093587], length 0
2026-08-14 18:20:01.683874 IP 10.0.10.11.1521 > 192.168.30.12.44192: Flags [.], ack 4277, win 501, options [nop,nop,TS val 1044216827 ecr 4241361603], length 0
2026-08-14 18:20:01.683896 IP 192.168.30.12.44192 > 10.0.10.11.1521: Flags [.], ack 5027, win 474, options [nop,nop,TS val 4241424579 ecr 1044093587], length 0
2026-08-14 18:21:03.123875 IP 10.0.10.11.1521 > 192.168.30.12.44192: Flags [.], ack 4277, win 501, options [nop,nop,TS val 1044278267 ecr 4241424579], length 0
2026-08-14 18:21:03.123897 IP 192.168.30.12.44192 > 10.0.10.11.1521: Flags [.], ack 5027, win 474, options [nop,nop,TS val 4241486019 ecr 1044093587], length 0無通信のはずの放置中に、DB サーバ→クライアント方向の length 0 の ACK と、それへのクライアントの応答が、約 1 分間隔(実測 61〜63 秒)で流れ続けています。
これが DCD のプローブで、クライアント側・DB サーバ側の両方の tcpdump に記録されている=FW を毎分通過してアイドルタイマーをリセットしている、というのが After の仕組みの実体です。
FW の状態テーブル側でも、プローブが通過するたびに残り秒数が 300 近くまで戻る様子が観測できました。
まず、プローブ通過の直前の観測です。
# date && grep "dport=1521" /proc/net/nf_conntrack ; echo "exit=$?"
2026年 8月 14日 金曜日 18:19:58 JST
ipv4 2 tcp 6 240 ESTABLISHED src=192.168.30.12 dst=10.0.10.11 sport=44192 dport=1521 src=10.0.10.11 dst=192.168.30.12 sport=1521 dport=44192 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 zone=0 use=2
exit=0その5秒後、プローブ(18:20:01)が通過した直後の観測です。
# date && grep "dport=1521" /proc/net/nf_conntrack ; echo "exit=$?"
2026年 8月 14日 金曜日 18:20:03 JST
ipv4 2 tcp 6 298 ESTABLISHED src=192.168.30.12 dst=10.0.10.11 sport=44192 dport=1521 src=10.0.10.11 dst=192.168.30.12 sport=1521 dport=44192 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 zone=0 use=2
exit=018:19:58 に 240 秒まで減っていた残り秒数が、18:20:01 のプローブ通過の直後(18:20:03)には 298 秒に戻っています。
Before で消えていったカウントダウンを、DCD のプローブが毎分巻き戻している形です。
プローブの形が「データなし(length 0)の ACK」であることから、本検証環境(19c)の DCD は TNS レベルのデータ送信ではなく TCP keepalive として実装されていることも実測で確認できました。
補足: EXPIRE_TIME=10 も無意味だったわけではない
DCD の本来の役割が分かる観測が、同じ tcpdump に残っていました。
先ほど「効かなかった」EXPIRE_TIME=10 の接続(クライアント側はエラーで接続が閉じた後)に対して、DB サーバは接続から約 10 分後にプローブを送り始めていました。
DB サーバ側 tcpdump の記録です。
2026-08-14 18:13:29.299601 IP 10.0.10.11.1521 > 192.168.30.12.27642: Flags [.], ack 4277, win 501, options [nop,nop,TS val 1043824443 ecr 4240432150], length 0
2026-08-14 18:13:35.635610 IP 10.0.10.11.1521 > 192.168.30.12.27642: Flags [.], ack 4277, win 501, options [nop,nop,TS val 1043830779 ecr 4240432150], length 0
(約6秒間隔の同様のプローブが計11本続く・応答なし)
2026-08-14 18:14:30.931607 IP 10.0.10.11.1521 > 192.168.30.12.27642: Flags [R.], seq 5027, ack 4277, win 501, options [nop,nop,TS val 1043886075 ecr 4240432150], length 0応答のないプローブを約 6 秒間隔で繰り返した後、約 1 分後に DB サーバから RST を送信して、DB サーバ側に残っていたセッションを終了させています。
DCD の本来の役割はこちらで、「応答のなくなった接続を検出して DB 側のリソースを解放する」機能です。
EXPIRE_TIME=10 は、クライアント側で接続が切断されるのは防げませんでしたが、切断済みセッションの後始末という本来の仕事は果たしていました。
つまり DCD には「①応答のない接続の検出と後始末(本来の役割)」「②プローブが FW のタイマーを更新して接続の切断自体を防ぐ(今回の使い方)」の2つの働きがあり、②に使うときだけ「FW タイムアウトより短く」という条件が付く、と整理できます。
対処の選択肢と使い分け
| 対処 | 位置づけ | 注意点・条件 |
|---|---|---|
| DB 側: SQLNET.EXPIRE_TIME(DCD) | 第一候補。DB 側の設定1か所で全 AP サーバに効く(アプリ改修不要) | 値は FW タイムアウトより短くする。設定後の新規接続から有効 |
| AP 側: 接続記述子に (ENABLE=BROKEN) + OS の TCP keepalive 短縮 | DB 側に手を入れられない場合の代替 | 2つセットで1つの対処(下の補足参照)。AP ホスト全台への設定が必要 |
| FW 側: 当該ルールのアイドルタイムアウト延長 | 根本対処 | 他チーム管轄・セキュリティ方針との調整が必要。接続の切断が始まる放置時間の境界の実測値と tcpdump の記録を添えて依頼する |
| 暫定: 業務開始前のウォームアップ・プールの借用時検証 | 恒久対処が入るまでのつなぎ(業務がエラーに当たるのを防ぐ。切断自体は防がない) | ウォームアップ=業務開始前に全プール接続で軽い SQL を1回実行し、切断済みの接続を先に張り直させておく。借用時検証(validation query)=プールが接続を貸し出す直前に確認クエリで生死を確かめる |
表の2行に補足があります。
- DCD の波及範囲: sqlnet.ora は ORACLE_HOME 単位の設定のため、同じ ORACLE_HOME に同居する全 DB・全接続にプローブ送信が波及します
- (ENABLE=BROKEN) の中身: tnsnames.ora の接続記述子(DESCRIPTION 内)に書く設定で、その接続のクライアント側ソケットの TCP keepalive を有効にします。プローブの間隔は OS 設定に従うため、net.ipv4.tcp_keepalive_time(デフォルト 7200 秒)を FW タイムアウト未満へ短縮する設定とセットで初めて対処になります。この短縮は OS 上の他の TCP 接続にも効きます
第一候補の DCD にも、導入判断の材料としてトレードオフが3つあります。
- FW のステートを占有し続ける: プローブが FW のアイドルタイマーを毎回リセットするということは、無通信の接続のステートエントリが失効しなくなるということです(先ほどの conntrack の巻き戻しの実測そのもの)。FW のアイドルタイムアウトは本来「使われない接続のリソース回収」という FW 側の設計の一部なので、多数の接続で常用する場合は NW チームと整合を取っておくのが安全です
- 一時的な通信断で切断と判定されうる: 本検証の実測では、応答のないプローブは約6秒間隔で送られ、約1分で切断と判定されました。裏を返すと、無通信中に1分を超える通信断(機器の再起動・経路切替など)が重なった場合、生きているセッションでも切断と判定されることになります。検出が速いことと誤判定の窓が短いことは表裏の関係です
- わずかなオーバーヘッド: Oracle のマニュアル(Net Services Reference)にも、プローブによる追加トラフィックがネットワーク性能に影響しうること、OS によっては追加処理が発生することが制限事項として記載されています。19c の実装は OS の keepalive のため DB プロセスの負荷はほぼありませんが、接続数千本×短い EXPIRE_TIME のような使い方では考慮に入れます
9. DB サーバ側のエラー: アラートログの Fatal NI connect error 12170(TNS-12535)
ここまで、接続が切断された証拠はすべてクライアント側と FW 役 VM で取ってきました。
残るは DB サーバ側、「1. 症状」で触れたアラートログの Fatal NI connect error 12170(TNS-12535)です。
この記録がいつ・何をきっかけに出るのかを実測で確認します。
接続が切断された瞬間には、何も記録されません。
「2地点 tcpdump」の章で見たとおり、放置明けの再送はサーバに1パケットも届いておらず、サーバ側は次の SQL の受信を待っているだけで、失敗を検出する契機がないためです。
実際、クライアントがエラーになった各時刻の周辺に、アラートログのエントリはありませんでした。
ところが、時間を置いて DB サーバのアラートログ($ORACLE_BASE/diag/rdbms/<DB名>/<インスタンス名>/trace/alert_<インスタンス名>.log)を確認すると、この日の検証で切断した4つの接続すべてが記録されていました。
その1件、「4. 実機再現」で切断した接続(ポート 17630)の記録です。
2026-08-14T20:04:20.371905+09:00
***********************************************************************
Fatal NI connect error 12170.
VERSION INFORMATION:
TNS for Linux: Version 19.0.0.0.0 - Production
Oracle Bequeath NT Protocol Adapter for Linux: Version 19.0.0.0.0 - Production
TCP/IP NT Protocol Adapter for Linux: Version 19.0.0.0.0 - Production
Version 19.28.0.0.0
Time: 14-AUG-2026 20:04:20
Tracing not turned on.
Tns error struct:
ns main err code: 12535
TNS-12535: TNS:operation timed out
ns secondary err code: 12560
nt main err code: 505
TNS-00505: Operation timed out
nt secondary err code: 110
nt OS err code: 0
Client address: (ADDRESS=(PROTOCOL=tcp)(HOST=192.168.30.12)(PORT=17630))読みどころは2か所です。
- Time: 記録された時刻。後述のとおり、接続が切断された瞬間ではなく「サーバが気づいた瞬間」です
- Client address: 相手クライアントの IP とポート番号。この例のポート 17630 は、「4. 実機再現」で切断した接続そのものです。どの接続の記録かをポート番号で特定できるため、クライアント側の障害記録との突き合わせに使えます
本文に登場した2つの接続の記録の対応です(この日の検証ではほかに2接続を切断しており、それらも同様に約2時間12分後・約2時間15分後に記録されました)。
| アラートログの時刻 | Client address のポート | どの接続か | 最後の通信からの時間差 | 検出の契機 |
|---|---|---|---|---|
| 20:04:20 | 17630 | 「4. 実機再現」の接続(最後の通信 17:52:41) | 約2時間12分 | OS の TCP keepalive |
| 18:14:30 | 27642 | 「対処」の章で EXPIRE_TIME=10 を設定した接続(最後の通信 18:03:29) | 約11分 | DCD のプローブ |
記録の契機は、サーバ側からその接続へ何かが送信されたときです。
とはいえ、サーバのプロセス自身は次の SQL を待って受信待ちしているだけで、自分から送るデータはありません。
実際に送信するのは、OS または DCD のタイマーです。
無通信が設定時間続いた接続に対して、生存確認の小さなパケット(プローブ)が自動的に送られます。
相手が正常ならすぐ応答が返り、何も起きません。
今回は FW がこのプローブも破棄するため応答が返らず、決められた回数を送り切った時点で「この接続は失われた」とタイムアウト(nt secondary err code: 110 = Linux の ETIMEDOUT)が確定し、その瞬間にアラートログへ記録されます。
実測できたプローブの契機は2つあります。
- DCD のプローブ(EXPIRE_TIME 設定時): 表の2件目。接続の約10分後に始まったプローブが約1分間無応答で確定しました。「対処」の章の補足で見た、後始末の RST 送出とまったく同じ瞬間の記録です(tcpdump の RST が 18:14:30.931607・アラートログが 18:14:30.932234)
- OS の TCP keepalive(EXPIRE_TIME なし): 表の1件目。DCD を設定していなくても、OS が接続ごとに持っている生存確認の仕組みが同じ役割を果たします
この keepalive の設定は、DB サーバの sysctl パラメータです(本検証環境(Oracle Linux 8)はデフォルトのまま)。
$ sysctl net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_keepalive_probes
net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9無通信が 7200 秒(2時間)続いたらプローブを送り始め、75 秒間隔で計 9 本すべて無応答なら「接続は失われた」と判定する、という設定です。
この動きは、DB サーバ側の tcpdump にそのまま写っていました。
2026-08-14 19:52:41.491644 IP 10.0.10.11.1521 > 192.168.30.12.17630: Flags [.], ack 4278, win 501, options [nop,nop,TS val 1049776635 ecr 4239783958], length 0
2026-08-14 19:53:57.779647 IP 10.0.10.11.1521 > 192.168.30.12.17630: Flags [.], ack 4278, win 501, options [nop,nop,TS val 1049852923 ecr 4239783958], length 0
(同様のプローブが約 76〜78 秒間隔で続き、計 9 本すべて応答なし)
2026-08-14 20:04:20.371621 IP 10.0.10.11.1521 > 192.168.30.12.17630: Flags [R.], seq 5027, ack 4278, win 501, options [nop,nop,TS val 1050475515 ecr 4239783958], length 01本目のプローブは、最後の通信(17:52:41)のちょうど 7200 秒後の 19:52:41 でした。
9 本すべてが無応答に終わり、20:04:20 の RST 送出と同時にアラートログへ記録されています。
なお、このプローブはクライアント側の tcpdump には1本も写っていません。
「2地点 tcpdump」の章と向きが逆なだけで、FW がステート外パケットを破棄している構図は同じです。
EXPIRE_TIME を設定していなくてもサーバ側のソケットに keepalive が働いていることは、稼働中の接続でも確認できます。
# ss -tno state established '( sport = :1521 )'
Recv-Q Send-Q Local Address:Port Peer Address:Port
0 0 [::ffff:10.0.10.11]:1521 [::ffff:10.0.10.11]:32470 timer:(keepalive,52min,0)
0 0 [::ffff:10.0.10.11]:1521 [::ffff:10.0.10.11]:61006 timer:(keepalive,13min,0)timer:(keepalive,52min,0) が次のプローブまでの残り時間で、デフォルトの2時間からのカウントダウン中です。
つまり DCD(EXPIRE_TIME)は、OS がもともと持っている「2時間後に気づく」仕組みを、分単位に短縮しているという関係になります。
実務でこの記録を探すときの注意は、時刻のずれです。
記録が出るのは、クライアントがエラーになった時刻ではなく、その接続の最後の通信から約2時間後(DCD なし・本検証環境のデフォルト値の場合)です。
障害時刻でアラートログを検索して「異常なし」と判断すると、この記録を見落とします。
「1. 症状」の朝の例に当てはめると、8:30 の業務エラーに対応する記録は、8:30 の周辺ではなく前日の夜〜深夜(各接続が最後に使われた時刻の約2時間後)に出ていたことになります。
「DB のアラートログにも目立った異常がない」とされていたのも、この時刻のずれと、Fatal NI connect error 12170 が業務側のエラー(ORA-03135 / ORA-03113)とエラー番号の見た目で結び付けにくいことで説明できます。
調査の入口が逆の場合、つまりアラートログの TNS-12535 からこの事象に気づいた場合も、この読み方はそのまま使えます。
Client address の IP・ポートでどのクライアントのどの接続かを特定し、その約2時間前に最後の通信があったはずだと逆算して、クライアント側の障害記録やアプリケーションログと突き合わせます。
10. 新規接続が FW で破棄される場合は ORA-12170
ここまでは「確立済みの接続」が切断される話でした。
対になるケースとして、FW が新規接続(接続時の SYN パケット)を応答なしで破棄する場合も押さえておきます。
この場合のエラーは ORA-03135 ではなく、接続タイムアウトの ORA-12170 になります。
別の検証(DB サーバのローカル FW で 1521 番ポートへのパケットを DROP)での実測です。
$ sqlplus system/<パスワード>@127.0.0.1:1521/v11c19u
ERROR:
ORA-12170: TNS: 接続タイムアウトが発生しました。このときも応答のない破棄のため即時にはエラーにならず、SYN の再送を繰り返した後、長い待ちの末にタイムアウトしました。
同じ FW の破棄でも、どの段階のパケットが破棄されたかでエラー番号が変わることになります。
TNS 接続の4段階に重ねると、ORA-12170 は②の失敗、本記事の ORA-03135 / ORA-03113 は4段階をすべて通過して接続が確立した「後」の失敗です。
① 接続先の情報を解決する(クライアント内)
│ 解決できない → ORA-12154
▼
② 接続先ホストに到達する(ネットワーク)
│ 拒否応答が返る → ORA-12543(即時)
│ 応答がなくタイムアウト → ORA-12170 ★新規接続の破棄はここ
▼
③ リスナーに接続する(ポート 1521)
│ 接続を拒否される → ORA-12541
▼
④ リスナーがサービスへ転送する
│ サービス名が未登録 → ORA-12514
▼
接続成功
│ 確立済みの接続が失われる → ORA-03135 / ORA-03113 ★本記事なお、FW の応答の仕方でエラー番号が変わる関係は新規接続側にもあり、実測では、拒否応答(REJECT)が返る場合はポート単位の拒否で ORA-12541(即時)、ホスト単位の拒否で ORA-12543(即時)、応答なしの破棄(DROP)で ORA-12170(タイムアウト)と分かれました。
「即時に返るか、待たされてから返るか」が切り分けの入口になる点は、確立済み接続の ORA-03135 / ORA-03113 と同じ構図です。
図の①と④のエラーは、それぞれ ORA-12154・ORA-12514 の記事で実機再現しています。
11. よくある Q&A
Q: ORA-03135 と ORA-03113 は別の原因を調べるべきですか?
A: いいえ、同じ手順で調べられます。
どちらも「確立済みの接続が失われた」ことを示すエラーで、実測では、切断を相手からの通知(RST)で知ると ORA-03135(即時)、応答がなく再送を打ち切って知ると ORA-03113(ハングの後)と、検出のされ方でエラー番号が分かれました。
エラー番号よりも「エラーまで待たされたかどうか」の方が、原因側の動作(RST を返す機器があるか、応答なしで破棄されているか)を推定する材料になります。
Q: NW チームから「FW のカウンタはゼロなので FW は問題ない」と回答されました。FW は原因から除外してよいですか?
A: 除外できません。
アイドルタイムアウトによるステートエントリの削除は、多くの製品で異常ではなく正常処理として扱われ、エラー・ドロップ系のカウンタに計上されないためです。
本文で実測した、接続の切断が始まる放置時間の境界と、2地点 tcpdump の結果(クライアント側は再送あり・DB サーバ側は記録なし)を添えて、「TCP アイドルタイムアウト値の確認」と確認箇所を具体的に依頼するのが有効です。
Q: アラートログに Fatal NI connect error 12170(TNS-12535: TNS:operation timed out)が記録されています。この問題と関係ありますか?
A: 確立済み接続の切断を DB サーバが後から検出したときの代表的な記録で、FW のアイドルタイムアウトが原因の場合もこの形で出ます(「DB サーバ側のエラー」の章で実測)。
ただし、この記録自体は「その接続への送信がタイムアウトした」ことしか示しておらず、原因が FW とまでは書かれていません。
末尾の Client address(相手クライアントの IP・ポート)と、記録時刻がその接続の最後の通信の約2時間後(OS の keepalive がデフォルトの場合)になる点を手掛かりにクライアント側の障害と突き合わせ、本文の切り分け手順(放置時間の境界の実測と2地点 tcpdump)で確定します。
なお、DCD(SQLNET.EXPIRE_TIME)を設定している環境では、記録は約2時間後ではなく「最後の通信から EXPIRE_TIME の分数+約1分」後に出ます(本文の実測では EXPIRE_TIME=10 の接続が約11分後に記録)。
記録時刻を決めるのは切断された時刻ではなくプローブが飛ぶ定刻なので、切断がいつ起きても、この式のとおりになります。
Q: 本番環境で tcpdump を張りっぱなしにはできません。どうやって調べればよいですか?
A: 張りっぱなしにする必要はありません。
本記事の切り分けは、発生を待ち構えるのではなく、新規接続を張って放置時間を変えながら能動的に再現する方式です(「切り分け①」の章)。
tcpdump の取得は自分のテスト接続が動いている間だけでよく、対象ホスト・ポートのフィルタで量も絞れます。
発生済みの障害との突き合わせには、アラートログの TNS-12535 の記録時刻から約2時間(keepalive の開始まで)+約10分(プローブの送信)を逆算すると、その接続の最後の通信時刻と、切断が起きた時間帯を絞り込めます(DCD〔SQLNET.EXPIRE_TIME〕を設定している環境では、約2時間の部分を EXPIRE_TIME の分数に読み替えます)。
Q: 接続プールを使っていればこの問題は防げますか?
A: 接続プール自体は防止策になりません。
むしろ「張ったまま長時間使われない接続」を計画的に作る仕組みなので、この問題の主な発生場所です。
プール側でできる対策としては、接続の借用時検証(validation query)や、アイドル接続の生存上限を FW タイムアウト未満に設定する方法があります。
ただし、アプリ側の変更なしで全接続に効く点で、DB 側の DCD が恒久対処の第一候補です。
Q: SQLNET.EXPIRE_TIME を設定したのに接続が切断されます。なぜですか?
A: 2つの可能性を確認してください。
①値が FW のアイドルタイムアウトより長い(本文の実測では、FW 5 分に対して EXPIRE_TIME=10 は働きませんでした)。
②設定後に接続を張り直していない(EXPIRE_TIME は設定後の新規接続から有効で、既存の接続には効きません)。
Q: DB サーバ側の sqlnet.ora に手を入れられない場合、クライアント側だけで対処できますか?
A: tnsnames.ora の接続記述子(DESCRIPTION 内)に (ENABLE=BROKEN) を追加し、OS の TCP keepalive 間隔(Linux なら sysctl パラメータ net.ipv4.tcp_keepalive_time・デフォルト 7200 秒)を FW タイムアウト未満に短縮する方法があります。
(ENABLE=BROKEN) は、その接続のクライアント側ソケットで OS の TCP keepalive を有効にする設定です。有効にするだけでプローブの間隔は OS 設定に従うため、keepalive の短縮とセットで初めて対処になります。
本検証で 19c の DCD プローブの実体が TCP keepalive であることを確認しており、機序は DCD と同等です(クライアント側 keepalive で接続の切断を防げることまでは、本検証では実測していません)。
ただし、AP ホスト全台への設定が必要なこと、keepalive の設定変更は OS 上の他の TCP 接続にも影響することを考慮してください。
12. まとめ
ORA-03135 / ORA-03113 は「確立済みの接続が失われた」ことを示すエラーで、長い無通信の直後だけ発生するなら、まず経路上の FW(中間機器)のアイドルタイムアウトを疑います。
FW のカウンタがゼロでも除外はできません。
接続の切断が始まる放置時間の境界の実測と、クライアント側・DB サーバ側の2地点 tcpdump の対比だけで、FW 側の情報を見ずに原因を確定できます。
DB サーバのアラートログに時間差で出る Fatal NI connect error 12170(TNS-12535)も、Client address のポート番号を突き合わせれば補強の証拠になります。
切り分けと対処のフロー
放置後の最初の SQL で ORA-03135 / ORA-03113
│
▼ エラーまでの待ち時間は?
├─ 待ち時間ゼロで即エラー(ORA-03135)
│ → 経路上の何かが RST を返している
└─ ハングの後にエラー(ORA-03113)
→ 応答なしで破棄されている
│
▼ 発生条件の確認
「長い無通信の直後だけ・リトライで成功・新規接続は正常」
に当てはまれば、アイドルタイムアウトの疑いが濃い
│
▼ 放置時間の境界の特定
新規接続 → N 分放置 → SELECT を放置時間を変えて繰り返す
「N 分は接続維持・N+1 分で切断」の境界=タイムアウト設定値
│
▼ 2地点 tcpdump
クライアント側: 同一 seq の再送が続く
DB サーバ側: 1パケットも記録なし
→ 破棄はクライアントと DB サーバの「間」の区間で確定
(補強)DB サーバのアラートログ: 時間差で
Fatal NI connect error 12170(TNS-12535)
│
▼ 対処
DB 側: SQLNET.EXPIRE_TIME を FW タイムアウトより短い値で設定(第一候補)
AP 側: (ENABLE=BROKEN) + OS keepalive 短縮(DB 側に触れない場合)
FW 側: アイドルタイムアウト延長(根本・要調整)
暫定 : 業務開始前ウォームアップ・プールの借用時検証検証で使った各設定値(FW 300 秒・tcp_retries2=5・EXPIRE_TIME=1 分)は時間短縮のための値で、実務では FW 30〜60 分・EXPIRE_TIME 10 分前後という桁になりますが、「EXPIRE_TIME < FW のアイドルタイムアウト」という成立条件と切り分けの手順はそのまま使えます。








