How to Connect to MySQL from Python
Python has three main MySQL drivers -- mysqlclient, PyMySQL, and MySQL Connector/Python -- plus SQLAlchemy as an abstraction on top, and aiomysql/asyncmy for asyncio. They all get the job done; they differ in installation friction, speed, and async support. This guide shows working code for each and the trade-offs that decide between them.
Which driver should you use?
| Driver | Implementation | Async | Notes |
|---|---|---|---|
| mysqlclient | C extension (libmysqlclient) | No | Fastest sync driver; needs build tools or wheels |
| PyMySQL | Pure Python | No | Installs anywhere, no compilation, slower on big result sets |
| MySQL Connector/Python | Pure Python + optional C extension | No | Oracle's official driver, GPLv2 with FOSS exception |
| aiomysql | Pure Python (reuses PyMySQL) | Yes | The established asyncio option |
| asyncmy | Cython (reuses PyMySQL/aiomysql) | Yes | Faster asyncio fork of aiomysql |
Rules of thumb: mysqlclient when you control the environment and want raw speed (it wraps the C client library, so it is the fastest at fetching large result sets). PyMySQL when you want zero installation drama -- pure Python, works on any platform, and is a drop-in replacement for mysqlclient's API. MySQL Connector/Python when you want the vendor-supported driver or features like the X DevAPI. For asyncio, aiomysql or asyncmy. All three sync drivers implement DB-API 2.0, so the query code looks nearly identical; switching later is cheap.
These drivers also speak to MariaDB, which uses the same wire protocol. (MariaDB additionally ships its own mariadb connector.)
Connection details you need
Host, port (3306 by default), database name, user, password. The examples read from environment variables -- never hardcode credentials.
PyMySQL
pip install PyMySQLimport os
import pymysql
conn = pymysql.connect(
host=os.environ["MYSQL_HOST"],
port=3306,
user=os.environ["MYSQL_USER"],
password=os.environ["MYSQL_PASSWORD"],
database=os.environ["MYSQL_DATABASE"],
charset="utf8mb4",
cursorclass=pymysql.cursors.DictCursor,
)
try:
with conn.cursor() as cur:
cur.execute(
"SELECT id, email FROM users WHERE created_at > %s",
("2026-01-01",),
)
for row in cur.fetchall():
print(row["id"], row["email"])
conn.commit()
finally:
conn.close()Three details worth knowing:
charset="utf8mb4": always set this. MySQL's legacyutf8charset is a 3-byte subset that cannot store emoji or many CJK characters;utf8mb4is actual UTF-8.- autocommit is off by default (per DB-API). Writes are invisible to other connections until you call
conn.commit(). Forgetting this is the single most common "my INSERT disappeared" report. Passautocommit=Trueif you want MySQL's native behavior. - Parameters use
%sregardless of type -- it is not Python string formatting, the driver escapes values for you. Never build SQL with f-strings.
mysqlclient
The C-extension descendant of the old MySQLdb project, and the driver Django's MySQL backend recommends.
pip install mysqlclientOn Linux you may need headers first (default-libmysqlclient-dev on Debian/Ubuntu); macOS users typically brew install mysql-client pkg-config. Recent versions ship binary wheels for common platforms, which removes most of the historical pain.
import MySQLdb
conn = MySQLdb.connect(
host="localhost",
user="app",
password="...",
database="mydb",
charset="utf8mb4",
)
cur = conn.cursor()
cur.execute("SELECT id, email FROM users WHERE id = %s", (42,))
print(cur.fetchone())
conn.commit()
conn.close()Same DB-API shape as PyMySQL -- intentionally so. PyMySQL was written as a pure-Python drop-in for this exact API. If you start on PyMySQL and later need mysqlclient's speed, the migration is mostly the import line.
MySQL Connector/Python
Oracle's official driver. Pure Python by default with an optional C extension; licensed GPLv2 with the FOSS exception (fine for most uses, but check if you ship proprietary software -- the other two drivers are MIT/dual-licensed and avoid the question).
pip install mysql-connector-pythonimport mysql.connector
conn = mysql.connector.connect(
host="localhost",
user="app",
password="...",
database="mydb",
)
cur = conn.cursor(dictionary=True)
cur.execute("SELECT id, email FROM users WHERE id = %s", (42,))
print(cur.fetchone())
cur.close()
conn.close()It also ships a built-in pool:
from mysql.connector import pooling
pool = pooling.MySQLConnectionPool(pool_name="app", pool_size=5, host="localhost", user="app", password="...", database="mydb")
conn = pool.get_connection() # returns to the pool on conn.close()Async: aiomysql and asyncmy
Both reuse PyMySQL's protocol implementation. aiomysql is the established choice; asyncmy rewrites the hot paths in Cython for speed and is the one SQLAlchemy's docs point to for async MySQL.
pip install aiomysqlimport asyncio
import aiomysql
async def main():
pool = await aiomysql.create_pool(
host="localhost", user="app", password="...", db="mydb", autocommit=True
)
async with pool.acquire() as conn:
async with conn.cursor() as cur:
await cur.execute("SELECT count(*) FROM users")
print(await cur.fetchone())
pool.close()
await pool.wait_closed()
asyncio.run(main())SQLAlchemy
Not a driver -- it needs one of the above underneath, selected by the URL scheme:
pip install sqlalchemy mysqlclientimport os
from sqlalchemy import create_engine, text
# mysql+mysqldb:// for mysqlclient, mysql+pymysql:// for PyMySQL,
# mysql+mysqlconnector:// for Connector/Python, mysql+asyncmy:// for async
engine = create_engine(
"mysql+mysqldb://app:password@localhost:3306/mydb?charset=utf8mb4",
pool_size=5,
max_overflow=10,
pool_recycle=3600,
pool_pre_ping=True,
)
with engine.connect() as conn:
result = conn.execute(text("SELECT id, email FROM users WHERE id = :id"), {"id": 42})
print(result.fetchone())pool_recycle and pool_pre_ping are not optional decoration for MySQL -- see the next section.
Connection pooling and the wait_timeout trap
MySQL servers drop idle connections after wait_timeout seconds (28800 by default, but cloud providers and DBAs often set it much lower). A pooled connection that sat idle past the timeout produces the classic:
MySQL server has gone away (error 2006)
on its next query. Defenses, in order of preference:
pool_pre_ping=True(SQLAlchemy): emits a lightweight ping before handing out a pooled connection and transparently replaces dead ones.pool_recycle=3600: proactively retires connections older than N seconds. Set it below your server'swait_timeout.- With raw drivers,
conn.ping(reconnect=True)(PyMySQL/mysqlclient) before use accomplishes the same manually.
For serverless or many-instance deployments where in-process pools multiply into thousands of connections, put ProxySQL or your cloud provider's pooler (e.g. RDS Proxy) in front of the server.
SSL/TLS
Hosted MySQL (RDS, Cloud SQL, PlanetScale, Aiven) generally requires TLS. With PyMySQL:
conn = pymysql.connect(..., ssl={"ca": "/path/to/ca.pem"})With SQLAlchemy, append ?ssl_ca=/path/to/ca.pem to the URL (parameter names vary slightly per underlying driver). Passing a CA bundle enables server certificate verification; connecting with TLS but no CA check encrypts the traffic without proving you reached the right server.
Common errors and what they mean
(2003) Can't connect to MySQL server: wrong host/port, server down, or firewall. On Linux servers also checkbind-addressin MySQL config --127.0.0.1means no remote connections.(1045) Access denied for user: bad credentials, or the user exists for a different host pattern ('app'@'localhost'is a different account than'app'@'%').(1049) Unknown database: the database name in your connection call does not exist;SHOW DATABASESto verify.(2006) MySQL server has gone away: idle timeout (see pooling section) or a query exceedingmax_allowed_packet.(1156) caching_sha2_passwordauth errors on MySQL 8+: old driver versions lack the default auth plugin. Upgrade the driver; switching the user tomysql_native_passwordis the legacy workaround, not the fix.
Verifying your connection and exploring the data
After connecting, you usually want to see the schema and check your data landed. The mysql CLI with SHOW TABLES works; a GUI is faster for browsing -- we compared the options in Best MySQL GUI Clients. For loading data, see Import CSV to MySQL and Import JSON to MySQL. When you start writing real queries, the MySQL window functions and JSON queries guides cover the highest-leverage SQL features.
Version notes (as of June 2026): mysqlclient 2.2.x, PyMySQL 1.2.x, MySQL Connector/Python 9.x, aiomysql 0.3.x, asyncmy 0.2.x.
Mako connects to MySQL with AI-powered autocomplete for writing queries. Try it free at mako.ai.
Skip the terminal. Use Mako.
Connect your database, write queries with AI assistance, and import/export data in clicks. Free to start.