「同じコード・同じ環境なのに、VS Code から実行すると Python のデータベース接続が成功したり失敗したりする」――現場でよく遭遇する“再現しない不安定さ”の正体は、多くの場合 IDE(VS Code)・Python 実行環境・ネットワーク/OS・アプリコードが複合して起きます。本記事は原因の切り分け手順と、実運用で効く対処を体系化して解説します。
問題の背景と狙い
VS Code から実行した Python スクリプトが、同一の条件でもデータベース(PostgreSQL / MySQL / SQL Server など)への接続に「成功することもあれば失敗することもある」状態は、単発のリトライでは収束しづらく、原因の層(IDE 依存・環境依存・ネットワーク依存・アプリ依存)をまたいで発生します。本記事では、以下のゴールを目指します。
- IDE 依存とシステム依存を最短で切り分ける。
- Python/ドライバ/拡張機能/設定の不整合を除去する。
- ネットワーク(DNS/TCP/TLS)の揺らぎを可視化する。
- アプリ側の未クローズ・リトライ設計を是正する。
質問概要
VS Code で実行している Python スクリプトが、同じ環境・タイミングでもデータベース接続に成功したり失敗したりする。リトライ(10 回/10 秒)を入れても改善しない。原因と解決策を知りたい。
結論の先取り:最短の切り分けフロー
まずは「IDE 由来かどうか」を一撃で切り分け、その後は環境・ネットワーク・アプリの順に潰します。
| 段階 | 目的 | 実施内容(要点) | 判定 |
|---|---|---|---|
| 1 | IDE切り分け | VS Code外のターミナルで python your_script.py 実行 | 外では安定→IDE起因/外でも不安定→環境/ネットワーク/アプリ |
| 2 | 環境整合 | インタープリタ選択とドライバの再インストール | バージョン差異や壊れたキャッシュを排除 |
| 3 | 拡張競合 | 拡張無効化→段階的に有効化 | 再現する拡張を特定 |
| 4 | 実行統一 | launch.json を統合ターミナルに固定 | デバッグコンソール差異排除 |
| 5 | アプリ健全性 | 未クローズ/接続プール/指数バックオフ | スパイクに強い |
| 6 | ネットワーク | DNS/ポート/ファイアウォール/TLS の疎通検証 | 経路と遅延・拒否要因を可視化 |
| 7 | 設定刷新 | ワークスペース設定調整・キャッシュ/設定リセット | IDE 内部状態のリフレッシュ |
VS Code 以外で動かして IDE 依存を切り分ける
まずは OS のターミナル(PowerShell / bash / zsh 等)で直接実行します。
python your_script.py
# Windows (複数 Python がある場合)
py -0p # インストール済み Python の一覧を確認
ここで安定するなら、VS Code の拡張機能・設定・ターミナル種別(デバッグコンソール vs 統合ターミナル)など IDE 特有の要素が原因層です。外でも不安定なら、次の環境整合へ。
Python 環境をそろえる(インタープリタとドライバ)
- Ctrl + Shift + P →「Python: Interpreter を選択」で、想定の仮想環境(venv/conda/poetry)を選択。
- その環境をアクティブにしたうえで、DB ドライバを強制再インストール。
pip install --force-reinstall --no-cache-dir psycopg2-binary pyodbc mysql-connector-python
ポイント:
- 複数 Pythonが存在する環境(システム Python / WSL / conda / pyenv)は、VS Code の選択とシステムの
PATHがズレやすい。sys.executableで実体を必ず確認しましょう。 - ビルド済みバイナリ(例:
psycopg2-binary)とソースビルドの混在や壊れたネイティブ依存を再インストールで正します。
拡張機能の競合を疑う
VS Code は拡張機能が豊富な反面、拡張同士の影響でプロセスや環境変数、ターミナルの初期化順序が変わることがあります。いったん全部無効化し、Python/DB 関連だけを順番に有効化して再現する組み合わせを特定します。
- 候補:Python、Pylance、Jupyter、Docker、Remote Development、Test Explorer、拡張ターミナル系 など。
- 検証モード:コマンドパレットから「Developer: Reload With Extensions Disabled」。
実行コンソールを統一する(デバッグコンソール差異の排除)
デバッグコンソールと統合ターミナルでは、標準入出力/シグナル/環境変数の初期化が異なります。launch.json で実行先を統合ターミナルに固定します。
{
"version": "0.2.0",
"configurations": [
{
"name": "Run app (integrated terminal)",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/your_script.py",
"console": "integratedTerminal",
"justMyCode": true
}
]
}
未クローズ接続を検出して潰す(見落としがちな根本原因)
成功と失敗が揺れる典型は「接続やカーソルの未クローズ」「例外時の取り回し漏れ」です。プロセスが抱えているファイル/ソケットを可視化しましょう。
import os, psutil
p = psutil.Process(os.getpid())
print("open files:", len(p.open_files()))
print("open conns:", len(p.connections(kind="inet")))
想定より多ければ conn.close()、cursor.close() の漏れや、コンテキストマネージャ未使用を疑います。最小修正は「with パターン」への置換です。
# PostgreSQL (psycopg2)
import psycopg2
from contextlib import closing
dsn = "host=DBHOST dbname=DB user=USER password=PASS connect_timeout=5"
with closing(psycopg2.connect(dsn)) as conn:
with conn, conn.cursor() as cur: # トランザクションとカーソルをコンテキストで管理
cur.execute("SELECT 1")
print(cur.fetchone())
並列/並行処理では「スレッド間で接続共有」は禁止。各スレッド/プロセスで独立接続を確立するか、接続プールを用います。
リトライは指数バックオフ+ジッターへ
単純に「10 回 / 10 秒スリープ」は輻輳時に同時再試行してしまい逆効果です。指数バックオフ(上限 60 秒程度)に小さなジッターを加えます。
import random, time
from psycopg2 import OperationalError, InterfaceError
from contextlib import closing
import psycopg2
def connect_with_backoff(dsn: str, max_attempts: int = 15):
for i in range(max_attempts):
try:
return psycopg2.connect(dsn)
except (OperationalError, InterfaceError) as e:
# 可逆的な接続例外のみ再試行
base = min(60, 2 ** i)
jitter = random.uniform(0, 1.0)
wait = min(60, base + jitter)
print(f"[{i+1}/{max_attempts}] retry in {wait:.1f}s ({e})")
time.sleep(wait)
raise RuntimeError("接続に失敗しました(最大リトライ超過)")
dsn = "host=DBHOST dbname=DB user=USER password=PASS connect_timeout=5 keepalives=1 keepalives_idle=30 keepalives_interval=10 keepalives_count=3"
with closing(connect_with_backoff(dsn)) as conn:
with conn, conn.cursor() as cur:
cur.execute("SELECT 1")
print(cur.fetchone())
重要:再試行対象の例外を絞る・総試行時間の上限を決める・ログに試行回数と遅延を残す、の3点を徹底します。
ネットワーク疎通を確認(DNS / ポート / TLS)
VS Code の統合ターミナルで、名前解決・到達性・ポート・TLS の順で調べます。Windows / macOS / Linux でコマンドが異なるため代表例を示します。
| 目的 | Windows | macOS / Linux | 判定の観点 |
|---|---|---|---|
| 名前解決 | nslookup DBHOST | dig DBHOST +short / nslookup DBHOST | IP が安定しているか(VPN/スプリット DNS で揺れていないか) |
| 到達性 | ping DBHOST | ping -c 5 DBHOST | パケットロスや大きなジッターがないか |
| ポート開放 | Test-NetConnection DBHOST -Port 5432 | nc -vz DBHOST 5432 | ファイアウォール/SG による拒否や経路ブロック |
| TLS ハンドシェイク | openssl s_client -connect DBHOST:5432 -starttls postgres | openssl s_client -connect DBHOST:5432 -starttls postgres | 証明書検証・SNI ミスマッチの有無 |
| 経路 | tracert DBHOST | traceroute DBHOST | VPN/プロキシ越えの遅延と経路逸脱 |
IPv6/IPv4 切替(getaddrinfo の順序差)でも揺れることがあります。ホスト名で揺れる場合は、一時的に IP 直指定で改善するかを確認してください。
ワークスペース設定の調整(.vscode/settings.json)
{
"terminal.integrated.env.linux": { "PYTHONUNBUFFERED": "1" },
"python.terminal.activateEnvironment": true,
"python.languageServer": "Pylance",
"terminal.integrated.inheritEnv": true,
"python.analysis.typeCheckingMode": "basic"
}
補足:
PYTHONUNBUFFERED=1はログの取りこぼし防止に効きます。- 環境変数を VS Code が上書きしている疑いがある時は、
inheritEnvの挙動を見直します。
VS Code のキャッシュ/設定をリセット
壊れたキャッシュが疑われる場合の手順です。実施前にプロジェクトのバックアップを推奨します。
- 拡張機能キャッシュ削除(Linux/macOS 例)
rm -rf ~/.vscode/extensions/ms-python.python-*/pythonFiles
- ユーザー設定の初期化:Ctrl + Shift + P →「設定: リセット」
- 起動オプション:
code --disable-extensionsで検証起動
接続プールと接続文字列の最適化
PostgreSQL(psycopg2)
from psycopg2.pool import SimpleConnectionPool
dsn = (
"host=DBHOST dbname=DB user=USER password=PASS "
"connect_timeout=5 keepalives=1 keepalives_idle=30 "
"keepalives_interval=10 keepalives_count=3 application_name=vscode"
)
pool = SimpleConnectionPool(minconn=1, maxconn=5, dsn=dsn)
conn = pool.getconn()
try:
with conn, conn.cursor() as cur:
cur.execute("SELECT 1")
finally:
pool.putconn(conn)
MySQL(mysql-connector-python)
import mysql.connector
from mysql.connector import pooling
config = {
"host": "DBHOST",
"database": "DB",
"user": "USER",
"password": "PASS",
"connection_timeout": 5,
"consume_results": True
}
pool = pooling.MySQLConnectionPool(pool_name="mypool", pool_size=5, **config)
cnx = pool.get_connection()
try:
cur = cnx.cursor()
cur.execute("SELECT 1")
print(cur.fetchone())
finally:
cur.close(); cnx.close()
SQL Server(pyodbc)
import pyodbc
conn_str = (
"DRIVER={ODBC Driver 18 for SQL Server};"
"SERVER=DBHOST,1433;"
"DATABASE=DB;"
"UID=USER;PWD=PASS;"
"Encrypt=yes;TrustServerCertificate=no;"
"Connection Timeout=5;App=VSCodePython"
)
with pyodbc.connect(conn_str, autocommit=True) as conn:
with conn.cursor() as cur:
cur.execute("SELECT 1")
print(cur.fetchone())
接続文字列では「接続タイムアウト」「keepalive」「アプリケーション名」を必ず指定し、DB 側ログで相関を取りやすくします。
エラー症状別の対処チャート
| よくあるメッセージ | 主因の候補 | 対処 |
|---|---|---|
could not translate host name | DNS 不安定・検索ドメイン不一致・VPN スプリット DNS | FQDN 指定、DNS サフィックス確認、IP 直指定で再現性確認 |
connection timed out | ファイアウォール/SG、ポート閉塞、経路輻輳 | ポート検査、経路追跡、セキュリティ設定と一致確認 |
connection already closed / server closed the connection unexpectedly | アイドル切断、keepalive 未設定、未クローズ多発 | keepalive 付与、プール採用、with 構文徹底 |
FATAL: too many connections | プール枯渇、接続リーク | 最大接続数調整、リーク検査、プールの maxconn を設計 |
SSL: CERTIFICATE_VERIFY_FAILED | 証明書チェーン不備、SNI 不一致、プロキシ介在 | 正しい CA 設定、サーバ名一致、プロキシ迂回で再検 |
HYT00(pyodbc Timeout) | ネットワーク遅延、SQL サーバ応答遅延 | Connection Timeout 明示、クエリタイムアウト分離 |
OS/インフラ側で見落としがちなボトルネック
Windows のエフェメラルポート枯渇/TIME_WAIT 蓄積
# TIME_WAIT の多さを確認
netstat -ano | findstr TIME_WAIT | measure
# 動的ポート範囲の確認
netsh int ipv4 show dynamicport tcp
# (管理者権限)範囲を広げる例
netsh int ipv4 set dynamicport tcp start=49152 num=16384
Linux のファイルディスクリプタ上限
ulimit -n
# 永続化は /etc/security/limits.conf にて(必要最小限の値に)
DNS キャッシュ・フラッシュ
# Windows
ipconfig /flushdns
# systemd-resolved 環境例
sudo resolvectl flush-caches
VPN/プロキシ/MTU
VPN やプロキシを経由していると、経路上の MTU 不一致で断続的再送が起きることがあります。ping -f(Windows)/ ping -M do(Linux)でフラグメントを禁止しつつ最大サイズを探ると、MTU 問題の切り分けが可能です。
Jupyter/Interactive Window 特有の落とし穴
- カーネルが長寿命のため、前回の未クローズ接続やグローバル状態が残りやすい。
- プロキシや証明書の環境変数(
HTTP_PROXY/REQUESTS_CA_BUNDLEなど)がカーネルだけ異なる場合がある。
カーネル再起動/「すべての変数をクリア」のうえで再検証し、Notebook では明示的に with を徹底してください。
観測性:ログを最初に仕込む
import logging, os, socket, time
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
def log_env():
logging.info("pid=%s host=%s python=%s",
os.getpid(), socket.gethostname(), os.environ.get("VIRTUAL_ENV") or sys.executable)
def log_attempt(n, dsn_masked):
logging.info("connect attempt=%d dsn=%s", n, dsn_masked)
# パスワードを伏せた DSN を別途作る(ログに秘匿情報を出さない)
接続試行のタイムスタンプ・試行回数・環境(仮想環境/ホスト名)を必ず記録すると、DB 側ログと突き合わせて原因区間を短縮できます。
実運用テンプレート(launch.json / tasks.json)
{
"version": "2.0.0",
"tasks": [
{
"label": "Run app",
"type": "shell",
"command": "python",
"args": ["${workspaceFolder}/your_script.py"],
"options": {
"env": { "PYTHONUNBUFFERED": "1" }
},
"problemMatcher": []
}
]
}
タスク経由で実行を固定すると、手元と CI の挙動が揃いやすく、再現性が上がります。
追加で確認すべきポイント(補足)
- 接続プール(例:
psycopg2.pool)で接続確立回数を減らし安定化。 - 接続文字列に
connect_timeout、keepalives、application_nameを指定。 - データベース側ログ(接続拒否/タイムアウト/認証/SSL)を必ず参照し、アプリログと相関を取る。
- VPN・プロキシ利用時は DNS 遅延・MTU を検証。名前解決の一貫性を最優先で担保。
最小サンプル:DB 別の堅牢な接続コード
PostgreSQL
import psycopg2, random, time
from contextlib import closing
from psycopg2 import OperationalError, InterfaceError
DSN = (
"host=DBHOST port=5432 dbname=DB user=USER password=PASS "
"connect_timeout=5 keepalives=1 keepalives_idle=30 "
"keepalives_interval=10 keepalives_count=3 application_name=vscode"
)
def connect_pg():
for i in range(12):
try:
return psycopg2.connect(DSN)
except (OperationalError, InterfaceError) as e:
time.sleep(min(45, 2 ** i + random.random()))
with closing(connect_pg()) as conn:
with conn, conn.cursor() as cur:
cur.execute("SELECT version()")
print(cur.fetchone())
MySQL
import mysql.connector, time, random
from mysql.connector import errors
cfg = dict(host="DBHOST", user="USER", password="PASS", database="DB", connection_timeout=5)
def connect_mysql():
for i in range(12):
try:
return mysql.connector.connect(**cfg)
except (errors.InterfaceError, errors.DatabaseError) as e:
time.sleep(min(45, 2 ** i + random.random()))
cnx = connect_mysql()
try:
cur = cnx.cursor()
cur.execute("SELECT VERSION()")
print(cur.fetchone())
finally:
cur.close(); cnx.close()
SQL Server
import pyodbc, time, random
conn_str = (
"DRIVER={ODBC Driver 18 for SQL Server};SERVER=DBHOST,1433;"
"DATABASE=DB;UID=USER;PWD=PASS;Encrypt=yes;TrustServerCertificate=no;Connection Timeout=5"
)
def connect_mssql():
for i in range(12):
try:
return pyodbc.connect(conn_str)
except pyodbc.OperationalError as e:
time.sleep(min(45, 2 ** i + random.random()))
with connect_mssql() as conn:
with conn.cursor() as cur:
cur.execute("SELECT @@VERSION")
print(cur.fetchone())
よくある「VS Code 由来」の具体例と対処
- 拡張が端末環境を上書き:拡張ターミナル/デバッグコンソールの
PATHやSSL_CERT_FILEが異なる。
→envや$Env:(Windows)で差分を採取し、settings.jsonで明示設定。 - Remote – WSL/Containers:ホスト側とネットワークスタックが異なり、名前解決・証明書ストアも別。
→ 接続先と同一のネットワークから疎通確認。コンテナでは/etc/ssl/certsの CA を整備。 - Jupyter 実行:前回のソケットが残り、カーネル再起動まで解放されない。
→ 各セルでwithを徹底、検証前にカーネルを再起動。
トラブルが収束しない時に集めるべき情報
- 利用データベース種別・バージョン(例:PostgreSQL 14、MySQL 8.0、SQL Server 2019)
sys.executableの出力(Python 実行ファイルパス)- 失敗時の完全なスタックトレース(先頭から末尾まで)
- 接続ドライバのバージョン(
pip show psycopg2等) - OS / 実行場所(ローカル / WSL / コンテナ / リモート)
- VPN/プロキシの有無、DNS の設定(検索ドメイン/順序)
- DB 側ログの同時刻エントリ(認証/接続/タイムアウト/SSL)
この情報が揃っていると、原因の層を一気に狭められます。
まとめ:不安定さを消す“三種の神器”
- 切り分け:IDE 外で再現するかを最初に確認し、拡張/設定/ターミナル差異を潰す。
- 健全化:接続/カーソルを
withで管理し、プール・keepalive・タイムアウトを明示。 - 観測:指数バックオフ+ジッターのリトライと詳細ログで、DB 側ログと相関を取る。
上記を順番に適用すれば、「成功と失敗がランダムに見える」状態は確実に原因層が浮かび上がり、再発防止につながります。
実践チェックリスト(配布用)
| 項目 | 実施 | メモ |
|---|---|---|
| VS Code 外で安定するか | □ | |
インタープリタ選択と sys.executable の一致 | □ | |
| ドライバ再インストール(キャッシュ無効) | □ | |
| 拡張機能の競合テスト(段階的有効化) | □ | |
console=integratedTerminal で統一 | □ | |
| 未クローズ接続の検出と修正(with パターン) | □ | |
| 指数バックオフ+ジッターの導入 | □ | |
| DNS/ポート/TLS の疎通 | □ | |
| ワークスペース設定の見直し | □ | |
| キャッシュ/設定リセットの実施 | □ |
最後に:それでも困ったら
上記を一通り試しても再現する場合は、以下の3点(+可能なら関連情報)を添えて相談してください。原因特定が飛躍的に速くなります。
- 利用データベース種別・バージョン
sys.executableの出力(Python 実行ファイルパス)- 失敗時の完全なスタックトレース
加えて、ドライバ/OS/ネットワーク(VPN/プロキシ)情報と DB 側ログがあれば、ほぼ確実に詰まる箇所を特定できます。

コメント