こんにちは、AIシステムズです。
この記事は、代表コバが現場で対応してきたPython開発・運用の知見をもとに、AIを活用して構成・執筆し、弊社にて最終チェックを行ったものです。
PythonからMySQLに接続しようとして「Can't connect to MySQL server」「Access denied for user」「2003 - HY000」などのエラーが返ってくる——業務効率化スクリプトやデータ処理を組む現場で必ず一度は遭遇するトラブルです。接続エラーは「ネットワーク到達性」「認証情報」「ドライバ・文字コード」の3層に分けて切り分けると短時間で解消できます。
- PythonからのMySQL接続エラーの分類
- ホスト・ポート到達性の確認
- ユーザー権限とホスト指定の落とし穴
- ドライバ(PyMySQL・mysqlclient・mysql-connector)の選定
- 文字コードとタイムアウトの設定
目次
- 接続エラーの分類
- ネットワーク到達性の確認
- 認証情報とMySQLユーザーのホスト指定
- Pythonドライバの選定と使い分け
- 文字コードと接続タイムアウト
- こういう用途に向いている/向いていない
- 再発防止チェックリスト
接続エラーの分類
弊社が中小企業のデータ連携・業務効率化スクリプトで対応してきた中で、エラーは次の3層に分けられます。
- ネットワーク層:
Can't connect to MySQL server on 'host' (timed out)、Connection refused - 認証層:
Access denied for user 'xxx'@'yyy' - ドライバ・データ層:
UnicodeDecodeError、Lost connection during query
エラーメッセージのキーワードで層を見分け、その層から切り分けます。
ネットワーク到達性の確認
1. ポートが空いているか
nc -zv mysql.example.com 3306
# 接続成功なら succeeded! と表示
タイムアウトする場合は、MySQLサーバーがLISTENしていないか、ファイアウォール/セキュリティグループで遮断されています。VPCやセキュリティグループで「DBサーバーへのインバウンドは特定IPのみ」になっているケースが多いので、接続元IPを許可します。
2. bind-addressの確認
MySQL側の /etc/my.cnf または /etc/mysql/mysql.conf.d/mysqld.cnf で bind-address = 127.0.0.1 になっていると、ローカルからしか接続できません。外部から接続させるには 0.0.0.0 に変更してMySQLを再起動します。
3. SSH越し接続の選択肢
直接3306を開けたくない場合は、SSHトンネル経由で接続します。
ssh -L 3307:localhost:3306 user@db-server
Python側からは localhost:3307 へ接続します。
認証情報とMySQLユーザーのホスト指定
MySQLのユーザーは「ユーザー名 + 接続元ホスト」の組み合わせで識別されます。'webuser'@'localhost' と 'webuser'@'%' はまったく別のユーザー扱いです。
SELECT user, host FROM mysql.user WHERE user = 'webuser';
接続元から見たMySQL側のhostがワイルドカード(%)か特定IPで許可されている必要があります。許可されていなければ作成します。
CREATE USER 'webuser'@'192.168.1.%' IDENTIFIED BY 'password';
GRANT SELECT, INSERT, UPDATE ON appdb.* TO 'webuser'@'192.168.1.%';
FLUSH PRIVILEGES;
Pythonドライバの選定と使い分け
- PyMySQL:純Pythonで実装、インストールが簡単。中小規模スクリプトの第一選択
- mysqlclient:C実装で高速。本番Webアプリで採用するならこちら。OS側にlibmysqlclientが必要
- mysql-connector-python:Oracle公式。デフォルトの認証プラグイン互換性が高い
PyMySQLでの接続例:
import pymysql
conn = pymysql.connect(
host='mysql.example.com',
port=3306,
user='webuser',
password='password',
database='appdb',
charset='utf8mb4',
connect_timeout=10,
cursorclass=pymysql.cursors.DictCursor,
)
文字コードと接続タイムアウト
日本語データを扱う場合、charset='utf8mb4' を必ず指定します。utf8(=utf8mb3)は絵文字を含む文字を保存できず、UnicodeDecodeErrorの原因になります。
長時間スクリプトでは、MySQL側のタイムアウト(wait_timeout)でコネクションが切られて「Lost connection」になります。対策は次のいずれかです。
- 処理ごとに接続を開閉する
- 定期的に
conn.ping(reconnect=True)を呼ぶ - SQLAlchemyなどのコネクションプールを使う
こういう用途に向いている/向いていない
このPython+MySQL構成は、中小企業の業務データの抽出・加工・連携、定期バッチ、Webアプリのバックエンドなど幅広く適しています。データ量がテラバイト規模に達するケースや、リアルタイム性が極めて重要なケースでは、別のデータベース(PostgreSQL、ClickHouseなど)の検討も必要です。
再発防止チェックリスト
- 接続先ホスト・ポートに到達できるか
ncで確認しているか - MySQLユーザーの
host指定が接続元と一致しているか - charsetを
utf8mb4に指定しているか - 長時間処理ではconnection.pingまたはプール再接続を実装しているか
- パスワードを環境変数で渡し、ソースコードに直書きしていないか
まとめ
PythonからのMySQL接続エラーは、ネットワーク到達性 → 認証情報 → ドライバ・文字コードの順に切り分ければ短時間で原因を特定できます。nc でポート確認、mysql.user でユーザーのhost確認、utf8mb4 指定の3点をルーチン化するのが定石です。
本記事は、代表コバが中小企業のPython・データ連携の現場で対応してきた知見をもとに、AIを活用して構成・執筆し、弊社にて最終確認を行っています。業務スクリプトの設計、データ連携基盤の構築、既存スクリプトの保守・改善について、具体的な状況をふまえた相談を承っています。費用感だけ知りたい方も、お気軽にご相談ください。