VS CodeでPythonのDB接続が成功したり失敗したりする原因と対処法【再現しない不安定さを解消】

「同じコード・同じ環境なのに、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 由来かどうか」を一撃で切り分け、その後は環境・ネットワーク・アプリの順に潰します。

段階目的実施内容(要点)判定
1IDE切り分け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 でコマンドが異なるため代表例を示します。

目的WindowsmacOS / Linux判定の観点
名前解決nslookup DBHOSTdig DBHOST +short / nslookup DBHOSTIP が安定しているか(VPN/スプリット DNS で揺れていないか)
到達性ping DBHOSTping -c 5 DBHOSTパケットロスや大きなジッターがないか
ポート開放Test-NetConnection DBHOST -Port 5432nc -vz DBHOST 5432ファイアウォール/SG による拒否や経路ブロック
TLS ハンドシェイクopenssl s_client -connect DBHOST:5432 -starttls postgresopenssl s_client -connect DBHOST:5432 -starttls postgres証明書検証・SNI ミスマッチの有無
経路tracert DBHOSTtraceroute DBHOSTVPN/プロキシ越えの遅延と経路逸脱

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 nameDNS 不安定・検索ドメイン不一致・VPN スプリット DNSFQDN 指定、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)

この情報が揃っていると、原因の層を一気に狭められます。

まとめ:不安定さを消す“三種の神器”

  1. 切り分け:IDE 外で再現するかを最初に確認し、拡張/設定/ターミナル差異を潰す。
  2. 健全化:接続/カーソルを with で管理し、プール・keepalive・タイムアウトを明示。
  3. 観測:指数バックオフ+ジッターのリトライと詳細ログで、DB 側ログと相関を取る。

上記を順番に適用すれば、「成功と失敗がランダムに見える」状態は確実に原因層が浮かび上がり、再発防止につながります。

実践チェックリスト(配布用)

項目実施メモ
VS Code 外で安定するか□
インタープリタ選択と sys.executable の一致□
ドライバ再インストール(キャッシュ無効)□
拡張機能の競合テスト(段階的有効化)□
console=integratedTerminal で統一□
未クローズ接続の検出と修正(with パターン)□
指数バックオフ+ジッターの導入□
DNS/ポート/TLS の疎通□
ワークスペース設定の見直し□
キャッシュ/設定リセットの実施□

最後に:それでも困ったら

上記を一通り試しても再現する場合は、以下の3点(+可能なら関連情報)を添えて相談してください。原因特定が飛躍的に速くなります。

  • 利用データベース種別・バージョン
  • sys.executable の出力(Python 実行ファイルパス)
  • 失敗時の完全なスタックトレース

加えて、ドライバ/OS/ネットワーク(VPN/プロキシ)情報と DB 側ログがあれば、ほぼ確実に詰まる箇所を特定できます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次