Every Python database library built on PEP 249, the Python Database API, works the same way. A connection is a session with the database; a cursor runs statements and reads results; the connection commits or rolls back the transaction and is closed when you're done:
import sqlite3
from contextlib import closing
with closing(sqlite3.connect("swim.db")) as conn:
cur = conn.cursor()
cur.execute("SELECT session, swimmers FROM attendance")
print(cur.fetchall())This lesson explains those roles, the three ways to fetch results, why transaction handling and closing are separate jobs, and how to respond to different kinds of failure. The examples record attendance at a swimming club. It complements SQL CRUD with Python, which focuses on the statements themselves.
Connection and cursor
The connection owns the session: the network link or open file, the login, and the current transaction. It provides cursor(), commit(), rollback() and close().
A cursor executes statements with execute() and executemany(), and holds the results of the last query for fetching. You can have several cursors on one connection, and they share its transaction.
conn = sqlite3.connect("swim.db")
cur = conn.cursor()
cur.execute("SELECT COUNT(*) FROM attendance")
print(cur.fetchone()[0])
cur.close()
conn.close()sqlite3 adds shortcuts, such as conn.execute(), which creates a cursor for you; that's what most examples in this course use. Other drivers, such as psycopg for PostgreSQL and MySQL Connector, expect you to create cursors explicitly, and some, such as psycopg, let you use them in with blocks. Think of the cursor as a pointer into one result set, and the connection as the session it belongs to.
Fetching
After a SELECT, read the rows with one of three methods:
| Method | Returns | Use when |
|---|---|---|
fetchone() | one row or None | you expect a single result |
fetchmany(n) | up to n rows; [] when done | processing large results in batches |
fetchall() | every remaining row as a list | the result is known to be small |
fetchall() on a table with ten million rows tries to hold them all in memory at once. Process large results in batches instead:
cur.execute("SELECT session, swimmers FROM attendance ORDER BY session")
while True:
batch = cur.fetchmany(500)
if not batch:
break
for session, swimmers in batch:
process(session, swimmers)Iterating directly over the cursor (for row in cur:) also fetches progressively in most drivers. Better still, let the database do the work: SELECT SUM(swimmers) returns one number instead of every row for Python to add up.
Transactions and closure
Two different responsibilities are easy to confuse:
- Transaction handling decides whether changes are kept (
commit()) or discarded (rollback()). - Resource closure ends the session and releases the file or network connection (
close()).
In sqlite3, with conn: handles the transaction only: it commits on success and rolls back on an exception, but leaves the connection open. To close it as well, wrap it with contextlib.closing():
from contextlib import closing
with closing(sqlite3.connect("swim.db")) as conn: # closes at the end
with conn: # commits or rolls back
conn.execute("INSERT INTO attendance VALUES (?, ?)", ("Friday", 12))Closing without committing discards pending changes in sqlite3. Leaving connections open wastes resources, may keep a SQLite file locked so other programs can't write to it, and on servers can exhaust the database's connection limit. Long-running applications use connection pools that hand out and reclaim connections, usually provided by SQLAlchemy or the driver.
Failures
Different failures need different responses:
| Failure | Typical exception | Response |
|---|---|---|
| Can't connect | OperationalError | report configuration problem; maybe retry later |
| Bad SQL | OperationalError, ProgrammingError | a bug: fix the code |
| Constraint violated | IntegrityError | duplicate or invalid data: tell the user |
| Invalid application data | your own ValueError | validate before writing |
try:
with conn:
conn.execute("INSERT INTO swimmers (email) VALUES (?)", (email,))
except sqlite3.IntegrityError:
print(f"{email} is already registered")Retry carefully. Re-running a read after a dropped connection is safe. Re-running a write whose response was lost may apply it twice, for example registering a swimmer or charging a fee twice, unless the operation is idempotent (repeating it has no extra effect), for instance by using a unique key. Log the failing operation's context, such as the session name, but never the full connection string with its password.
Independent practice and a worked check
Task: read attendance in batches and guarantee both transaction handling and connection closure.
import sqlite3
from contextlib import closing
def weekly_total(path):
with closing(sqlite3.connect(path)) as conn:
with conn:
conn.execute("CREATE TABLE IF NOT EXISTS attendance (session TEXT, swimmers INTEGER)")
conn.executemany("INSERT INTO attendance VALUES (?, ?)", [("Mon", 9), ("Wed", 14), ("Fri", 11)])
cur = conn.execute("SELECT swimmers FROM attendance")
total = 0
while rows := cur.fetchmany(2):
total += sum(swimmers for (swimmers,) in rows)
return total
print(weekly_total(":memory:")) # 34The inner with commits the inserts; closing() closes the connection even if an error occurs. For the worked check: does with sqlite3.connect(...) by itself close the connection? No. It only manages the transaction.
Reference and next practice
PEP 249 defines connections, cursors and the standard exceptions, and the sqlite3 documentation explains its context manager. Continue with HTTP requests and REST APIs. Practise with the Python exercises and Python quiz.
Database Connections and Queries Summary
- Connection and cursor: A connection represents a database session while a cursor performs queries and reads results.
- Fetching: fetchone reads one row while fetchmany and iteration can process results in smaller portions.
- Transactions and closure: Resource closure and transaction handling are related but distinct responsibilities.
- Failures: Connection failures, query errors and invalid application data require different handling.
Database Connections and Queries Examples
Each example shows the code, the output it produces and what each part does.
Database Connections and Queries in a small program
import sqlite3
from contextlib import closing
with closing(sqlite3.connect(":memory:")) as db:
db.execute("CREATE TABLE attendance (session TEXT, swimmers INTEGER)")
db.execute("INSERT INTO attendance VALUES (?, ?)", ("Tuesday", 9))
db.commit()
cursor = db.execute("SELECT swimmers FROM attendance")
print(cursor.fetchone()[0])9Line-by-line explanation
- Line 3
closing()guarantees the connection is closed when the block ends. - Lines 4 to 6Create a table, insert a row and commit the transaction.
- Lines 7 to 8The cursor returned by
execute()holds the result;fetchone()returns the first row.
Batches, a rollback and a constraint error
import sqlite3
from contextlib import closing
with closing(sqlite3.connect(":memory:")) as conn:
conn.execute("CREATE TABLE attendance (session TEXT PRIMARY KEY, swimmers INTEGER)")
with conn:
conn.executemany("INSERT INTO attendance VALUES (?, ?)",
[(f"S{n:02}", n * 3) for n in range(1, 8)])
cur = conn.execute("SELECT session, swimmers FROM attendance ORDER BY session")
batch_number = 0
while batch := cur.fetchmany(3):
batch_number += 1
print(f"batch {batch_number}: {[s for s, _ in batch]}")
try:
with conn:
conn.execute("UPDATE attendance SET swimmers = 0 WHERE session = 'S01'")
conn.execute("INSERT INTO attendance VALUES ('S02', 99)") # duplicate key
except sqlite3.IntegrityError as error:
print("rolled back:", error)
print(conn.execute("SELECT swimmers FROM attendance WHERE session = 'S01'").fetchone()[0])batch 1: ['S01', 'S02', 'S03']
batch 2: ['S04', 'S05', 'S06']
batch 3: ['S07']
rolled back: UNIQUE constraint failed: attendance.session
3Line-by-line explanation
- Line 12
fetchmany(3)returns at most three rows; an empty list ends the loop. - Lines 17 to 19The duplicate insert fails, so
with conn:rolls back the update made just before it. - Line 23S01 still has 3 swimmers, proving the whole transaction was undone.
Common Database Connections and Queries Mistakes
Assuming with sqlite3.connect() closes the connection
with sqlite3.connect("swim.db") as conn: # commits or rolls back, but stays open
conn.execute("INSERT INTO attendance VALUES (?, ?)", ("Sat", 8))from contextlib import closing
with closing(sqlite3.connect("swim.db")) as conn:
with conn:
conn.execute("INSERT INTO attendance VALUES (?, ?)", ("Sat", 8))Fetching a huge table with fetchall()
rows = conn.execute("SELECT * FROM visits").fetchall() # millions of rows in memory
total = sum(r[2] for r in rows)total = conn.execute("SELECT SUM(duration) FROM visits").fetchone()[0]Let the database aggregate, or use fetchmany() batches when you must process every row in Python.
Database Connections and Queries in Real Projects
Connections in production code
Web applications rarely open connections by hand. A framework or SQLAlchemy creates a pool at start-up, each request borrows a connection, and a context manager returns it and commits or rolls back. Background jobs that export large tables stream rows with server-side cursors or fetchmany() so memory use stays flat.
Monitoring tracks connection counts and slow queries, and error handling distinguishes outages (alert the team, retry reads with back-off) from bad data (reject with a clear message). These distinctions start with the habits in this lesson.
Database Connections and Queries FAQ
What is the difference between a connection and a cursor?
The connection is the session with the database and controls transactions and closing. A cursor executes statements and holds the results of a query. One connection can have several cursors.
What is the difference between fetchone, fetchmany and fetchall?
fetchone() returns the next row or None; fetchmany(n) returns up to n rows as a list; fetchall() returns all remaining rows. Use fetchmany() or iterate over the cursor for large results.
Do I need to close database connections in Python?
Yes. Close them when finished, using try/finally, contextlib.closing() or a framework's context manager. Open connections hold locks and server resources and can block other programs.
What is a connection pool?
A set of open connections that an application reuses instead of connecting for every request. Pools reduce latency and limit the number of simultaneous connections. SQLAlchemy and most drivers provide them.
What does sqlite3.OperationalError: database is locked mean?
Another connection holds a write lock on the SQLite file, often because a transaction wasn't committed or a connection wasn't closed. Commit or close promptly, avoid long transactions, and set a timeout= in sqlite3.connect() so writers wait briefly instead of failing immediately.
Database Connections and Queries Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Does with sqlite3.connect(...) by itself close the connection?
Which review concern is valid for database connections and queries?
Database Connections and Queries Quiz
2 questions to check what you learned.
Does with sqlite3.connect(...) by itself close the connection?
- Yes
- No
- Python ignores the distinction
Which statement correctly describes transactions and closure?
- Resource closure and transaction handling are related but distinct responsibilities.
- Only the example output matters, not the rule
- Every operation has the same behavior