Databases: sqlite3 & MySQL
SQL from Python with sqlite3, parameters vs SQL injection, transactions, connecting to MySQL and a peek at ORMs.
Files are fine for small amounts of data, but real applications need to store, query and update data safely, with many users at once. That's what relational databases are for. Python's standard library includes sqlite3, a complete SQL database in a single file — perfect for learning, tests, desktop apps and small sites. For multi-user production systems you'll often use a server such as MySQL (or PostgreSQL). The good news: Python's database API (PEP 249) looks almost the same for all of them.
This lesson assumes basic SQL (CREATE TABLE, SELECT, INSERT, JOIN). If that's new, Elephantoo's MySQL course covers it.
sqlite3: a database with zero setup#
The pattern is the same for every database:
- Connect to get a connection.
- Get a cursor and execute SQL.
- Commit changes (inserts, updates, deletes).
- Close the connection.
Use sqlite3.connect(":memory:") for a throw-away in-memory database — ideal for tests.
Parameters: never format SQL yourself#
Values always go in as parameters with ? placeholders — never by pasting them into the SQL string:
That's SQL injection — one of the most common and damaging security bugs in web applications. Parameters prevent it completely. Note the trailing comma in (evil,): parameters must be a tuple (or list), even for one value.
Inserting many rows and querying#
fetchone()returns the next row orNone;fetchall()returns a list;fetchmany(n)returns up to n rows. Iterating the cursor is the memory-friendly option.- Setting
conn.row_factory = sqlite3.Rowlets you access columns by name.
Transactions: all or nothing#
A transaction groups several statements so that either all of them take effect or none do. Using the connection as a context manager commits on success and rolls back if an exception occurs:
The failed transfer had already credited ada before the debit failed — the rollback undid it, so no money was created out of thin air. Note that with conn: manages the transaction but doesn't close the connection; call close() (or use contextlib.closing).
JOINs and a small repository layer#
In real code, keep SQL in a few well-named functions instead of scattering it everywhere:
Connecting to MySQL#
For MySQL 8.x, install the official driver into your virtual environment:
Then create a database and a dedicated user (in the mysql client, as an admin):
The Python code follows the same connect → cursor → execute → commit pattern. The main differences: the placeholder is %s (for every type), and connection details come from environment variables rather than being hard-coded:
Notice that DECIMAL columns come back as Python Decimal objects — exact, which is what you want for money. Other popular drivers are PyMySQL (pure Python, same %s style) and, for async code, aiomysql. For PostgreSQL, the equivalent is psycopg.
Connection pooling and ORMs
Opening a connection per request is slow. Web apps use a connection pool (mysql.connector.pooling.MySQLConnectionPool, or the pool built into an ORM). Many teams also use an ORM (Object-Relational Mapper) such as SQLAlchemy or the Django ORM, which maps tables to Python classes and generates SQL for you:
ORMs speed up development, but learn plain SQL first — you'll need it to understand and debug what the ORM does.
Common mistakes#
- Building SQL with f-strings or
+— SQL injection. Always use parameters. - Forgetting to
commit()— your inserts vanish. - Forgetting the comma in one-element parameter tuples:
(name,), not(name). - Mixing up placeholders:
?for sqlite3,%sfor MySQL drivers. - Hard-coding passwords in code — use environment variables or a secrets manager.
- Using floats for money columns — use
DECIMALin MySQL andDecimalin Python (or integer paise). - Leaving connections open — close them, or use a pool.
What's next#
Once data is in a database or CSV, you'll want to analyse it. Next up: data analysis with NumPy and pandas.
Check your understanding
Quick quiz
1.Why must you never build SQL with f-strings like
f"SELECT * FROM users WHERE name = '{name}'"?2.In sqlite3, what happens to your INSERTs if you never call
commit()?3.Which placeholder style does
mysql-connector-pythonuse?
Finished reading?
Mark this lesson complete to track your progress.