| name | python-pymysql |
| description | This skill should be used when the user asks to "connect to MySQL with PyMySQL", "use PyMySQL in Python", "query a MySQL database with Python", "set up PyMySQL", or needs guidance on PyMySQL best practices, transactions, parameterized queries, or cursor types. |
PyMySQL Best Practices
PyMySQL is a pure-Python MySQL client library implementing the DB-API 2.0 specification (PEP 249). It supports MySQL >= 5.7 and MariaDB >= 10.3 on Python >= 3.7.
Installation
pip install PyMySQL
pip install "PyMySQL[rsa]"
Establishing a Connection
Use pymysql.connect() as the entry point. Always specify charset='utf8mb4' and use context managers to guarantee cleanup.
import pymysql
import pymysql.cursors
connection = pymysql.connect(
host="localhost",
user="app_user",
password="secret",
database="mydb",
charset="utf8mb4",
cursorclass=pymysql.cursors.DictCursor,
connect_timeout=10,
read_timeout=30,
write_timeout=30,
autocommit=False,
)
Key Connection Parameters
| Parameter | Default | Notes |
|---|
charset | '' | Always set utf8mb4 for full Unicode support |
cursorclass | Cursor | Use DictCursor for dict rows; SSCursor for large result sets |
autocommit | False | Keep False; commit/rollback explicitly |
connect_timeout | 10 | Seconds before connection attempt fails |
read_timeout | None | Set to prevent hung reads |
write_timeout | None | Set to prevent hung writes |
ssl_ca | None | Path to CA cert for TLS connections |
Context Manager Usage
Connections and cursors implement the context manager protocol. Use with blocks to ensure proper resource cleanup.
with pymysql.connect(**db_config) as connection:
with connection.cursor() as cursor:
cursor.execute("SELECT id, email FROM users WHERE active = %s", (1,))
rows = cursor.fetchall()
Note: Using with connection: handles transaction commit/rollback but does not close the connection. Call connection.close() explicitly or manage it via a pool.
Parameterized Queries (SQL Injection Prevention)
Always pass values as the second argument to execute(). Never use string formatting or concatenation to build SQL.
cursor.execute(
"INSERT INTO orders (user_id, amount) VALUES (%s, %s)",
(user_id, amount),
)
cursor.execute(
"SELECT * FROM products WHERE category = %(cat)s AND price < %(max_price)s",
{"cat": "electronics", "max_price": 500},
)
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")
Use cursor.mogrify(query, args) to preview the interpolated query string during debugging.
Cursor Types
| Cursor Class | Returns | Buffered | Use Case |
|---|
Cursor | tuple | Yes | Default; small-to-medium result sets |
DictCursor | dict | Yes | When column-name access is needed |
SSCursor | tuple | No | Large result sets; memory-constrained |
SSDictCursor | dict | No | Large result sets with dict access |
Pass cursorclass at connection time (global default) or per-cursor:
with connection.cursor(pymysql.cursors.DictCursor) as cursor:
cursor.execute("SELECT * FROM users")
rows = cursor.fetchall()
For large tables, prefer SSCursor and iterate without calling fetchall():
with connection.cursor(pymysql.cursors.SSCursor) as cursor:
cursor.execute("SELECT * FROM large_table")
for row in cursor:
process(row)
Transaction Management
PyMySQL defaults to autocommit=False. Explicitly commit successful work and roll back on errors.
try:
with connection.cursor() as cursor:
cursor.execute(
"UPDATE accounts SET balance = balance - %s WHERE id = %s",
(amount, from_id),
)
cursor.execute(
"UPDATE accounts SET balance = balance + %s WHERE id = %s",
(amount, to_id),
)
connection.commit()
except Exception:
connection.rollback()
raise
Call connection.begin() to start a transaction block explicitly when needed.
Bulk Inserts with executemany()
Use executemany() for inserting multiple rows. PyMySQL batches the statements up to Cursor.max_stmt_length (1 MB) for efficiency.
records = [
("alice@example.com", "hash1"),
("bob@example.com", "hash2"),
]
with connection.cursor() as cursor:
cursor.execute("TRUNCATE TABLE staging_users")
cursor.executemany(
"INSERT INTO users (email, password_hash) VALUES (%s, %s)",
records,
)
connection.commit()
Fetching Rows
| Method | Returns | Notes |
|---|
fetchone() | single row or None | Efficient for single-row lookups |
fetchmany(size) | list of rows | Page through results |
fetchall() | list of all rows | Avoid on large result sets |
cursor.execute("SELECT id, name FROM users WHERE id = %s", (user_id,))
row = cursor.fetchone()
if row is None:
raise ValueError(f"User {user_id} not found")
Error Handling
Catch PyMySQL exceptions from pymysql.err:
import pymysql.err
try:
with connection.cursor() as cursor:
cursor.execute(sql, args)
connection.commit()
except pymysql.err.IntegrityError as exc:
connection.rollback()
raise
except pymysql.err.OperationalError as exc:
raise
except pymysql.err.ProgrammingError as exc:
raise
Key exception classes:
pymysql.err.IntegrityError — constraint violations (duplicate key, FK)
pymysql.err.OperationalError — connection / server errors
pymysql.err.ProgrammingError — SQL syntax errors, wrong param count
pymysql.err.DataError — invalid data for column type
pymysql.err.DatabaseError — base class for all DB errors
Connection Health Check
Use connection.ping(reconnect=True) to check liveness before executing queries in long-lived connections (e.g., background workers):
connection.ping(reconnect=True)
with connection.cursor() as cursor:
cursor.execute(query)
Reading Config from my.cnf
Avoid hardcoding credentials. Use read_default_file to read from a MySQL option file:
connection = pymysql.connect(
read_default_file="~/.my.cnf",
read_default_group="client",
database="mydb",
charset="utf8mb4",
cursorclass=pymysql.cursors.DictCursor,
)
~/.my.cnf example:
[client]
host = db.internal
user = app_user
password = secret
Quick Reference
import pymysql
import pymysql.cursors
conn = pymysql.connect(
host="localhost", user="u", password="p", database="db",
charset="utf8mb4", cursorclass=pymysql.cursors.DictCursor,
)
with conn.cursor() as cur:
cur.execute("SELECT * FROM t WHERE id = %s", (1,))
row = cur.fetchone()
with conn.cursor() as cur:
cur.execute("INSERT INTO t (col) VALUES (%s)", ("val",))
conn.commit()
with conn.cursor() as cur:
cur.executemany("INSERT INTO t (a, b) VALUES (%s, %s)", rows)
conn.commit()
conn.close()
Additional Resources
For detailed patterns and advanced usage, consult:
references/connection-patterns.md — Connection pooling, SSL/TLS configuration, timeout tuning, my.cnf patterns
references/cursor-guide.md — Cursor type selection, streaming large result sets, stored procedures, mogrify debugging