MariaDB講座 / Pythonから接続してパラメーター化クエリーを使う
PythonからMariaDBへ接続するときは、SQL文と入力値をドライバーへ別々に渡します。パラメーター化クエリー、必要最小限のアカウント、明示したcommitとrollbackで、安全に値を保存する流れを作ります。
環境と制限アカウントを準備する
Python 3とPyMySQL 1.1.2、ローカルTCPを使います。この教材の中核処理の検証環境はPython 3.12とMariaDB 12.3.3です。PyMySQLはDBドライバーで、この例の値プレースホルダーは%sです。他のドライバーの?をそのまま写しません。
管理者で未使用の表とアカウントを確認し、仮パスワードを自分の値へ変えて実行します。
CREATE DATABASE IF NOT EXISTS appops_lab CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE appops_lab; CREATE TABLE app_notes ( note_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, note_name VARCHAR(80) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE USER 'appops_app'@'127.0.0.1' IDENTIFIED BY 'Replace_With_Your_Own_Lab_Password'; GRANT SELECT, INSERT ON appops_lab.app_notes TO 'appops_app'@'127.0.0.1';
SELECTとINSERTだけです。Host127.0.0.1はローカルTCPを前提とし、コンテナーや遠隔では見える接続元が異なり得ます。権限と接続経路を調べ、どこからでも管理者で接続する回避策にしません。
仮想環境と完全なプログラムを保存する
新規のプロジェクトフォルダーでWindows PowerShellを使います。
python -m venv .venv .venv\Scripts\python.exe -m pip install PyMySQL==1.1.2
仮想環境のPythonを直接呼ぶため、起動スクリプトの実行方針を変えずに使えます。Linux・macOSは.venv/bin/pythonへ読み替えます。次をUTF-8のnotes_app.pyとして保存します。
import argparse
import getpass
import os
import pymysql
def save_and_find(conn, name, sort_key="id"):
if not name or len(name) > 80:
raise ValueError("Name must contain 1 to 80 characters")
columns = {"id": "note_id", "name": "note_name, note_id"}
if sort_key not in columns:
raise ValueError("Unknown sort key")
with conn.cursor() as cur:
cur.execute("INSERT INTO app_notes (note_name) VALUES (%s)", (name,))
cur.execute(
"SELECT note_id, note_name FROM app_notes "
"WHERE note_name = %s ORDER BY " + columns[sort_key],
(name,),
)
return cur.fetchall()
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--name", default="O'Reilly")
parser.add_argument("--sort", choices=("id", "name"), default="id")
args = parser.parse_args()
password = getpass.getpass("Database password: ")
conn = pymysql.connect(
host=os.getenv("DB_HOST", "127.0.0.1"),
port=int(os.getenv("DB_PORT", "3306")),
user=os.getenv("DB_USER", "appops_app"),
password=password,
database="appops_lab",
charset="utf8mb4", autocommit=False,
connect_timeout=5, read_timeout=10, write_timeout=10,
)
try:
rows = save_and_find(conn, args.name, args.sort)
conn.commit()
for note_id, note_name in rows:
print(note_id, note_name)
except Exception:
conn.rollback()
raise
finally:
conn.close()
if __name__ == "__main__":
main()
接続・読取・書込タイムアウト、utf8mb4とautocommit=Falseを明示します。パスワードは実行時入力でソースへ埋め込みません。このPC内の実習と違い、外部ネットワークではサーバー証明書を検証するTLSも必要です。パスワードだけで通信が保護されるわけではありません。
普通の名前と引用符入りの名前を実行する
二回の別実行で、各回パスワードを入力します。
.venv\Scripts\python.exe notes_app.py --name "Notebook" .venv\Scripts\python.exe notes_app.py --name "O'Reilly" --sort name
最初はNotebook、次はO'Reillyという名前を含む行が表示されます。新規表ならIDが1、2となりますが、過去の実行後は実際のIDを読みます。繰り返すと新しい行を追加し、同名検索は以前の行も返し得ます。引用符をSQLの構文へ連結していないことを確認します。
値の束縛と識別子の許可一覧
cur.execute(sql, (name,))でSQLと値を別々に渡し、一要素タプルのカンマも必要です。%sを引用符で囲んだり、Pythonの%やf文字列で入力を挿入したりしません。ドライバーに値の処理を任せます。
列名やASC/DESCは通常の値プレースホルダーでは任意指定できません。例のcolumnsは固定した許可列だけを対応させ、入力を直接ORDER BYへ足しません。長さ1~80も保存前に検証しますが、束縛は入力方針や権限の代わりではありません。
成功と例外の境界を確認する
保存と検索が成功してからcommit、失敗ならrollbackし、finallyで切断します。同じ接続の未確定範囲を使うため、接続中に勝手な自動確定を混ぜません。入力エラー、接続、権限、SQL実行を分けて診断し、秘密をログへ出さないようにします。
許可されていないUPDATEが制限アカウントで拒否されることも確認できます。終える場合は実習専用アカウントと表であること、必要データがないことを確かめてから削除します。
DROP USER 'appops_app'@'127.0.0.1'; DROP TABLE appops_lab.app_notes;
業務アカウントや表へ名前を読み替えません。表削除は通常のROLLBACKでは復元できません。




