Как работать с SQL в Python: модуль sqlite3 от подключения до запросов

Как работать с SQL в Python: модуль sqlite3 от подключения до запросов Полезное

Чтобы работать с SQL в Python, достаточно встроенного модуля sqlite3: он входит в стандартную библиотеку, ничего устанавливать не нужно. Схема всегда одна: открыть соединение, выполнить SQL-запрос, передав данные через параметры ?, зафиксировать изменения commit() и закрыть соединение. Все примеры ниже проверены 24.09.2026 на Python 3.14 с SQLite 3.46.1. Версия SQLite зависит от сборки Python, а не от версии языка: в Linux модуль обычно берет системную библиотеку, установщики с python.org несут свою. Узнать ее можно так: print(sqlite3.sqlite_version).

SQLite здесь — учебная среда: база живет в одном файле, сервер не нужен. Приемы (параметры, транзакции, курсоры) переносятся на PostgreSQL и MySQL, а отличия диалектов собраны в конце статьи.

Четыре термина, которые путают

  • Соединение (Connection) — открытая связь с файлом базы. Через него фиксируют (commit) и отменяют (rollback) изменения.
  • Курсор (Cursor) — объект, который выполняет запрос и отдает строки результата по одной или порциями.
  • Транзакция — группа изменений, которая применяется целиком или не применяется вовсе. Пока нет commit(), изменения видит только ваше соединение.
  • Параметр (?) — место в SQL, куда драйвер сам подставляет значение. Это не форматирование строки, а отдельная передача данных.

Минимальный рабочий пример

Скрипт создает файл shop.db, таблицу пользователей, добавляет три строки и читает их обратно.

import sqlite3

conn = sqlite3.connect("shop.db")
cur = conn.cursor()

cur.execute("DROP TABLE IF EXISTS users")  # чтобы скрипт можно было запускать повторно
cur.execute("""
    CREATE TABLE users (
        id    INTEGER PRIMARY KEY,
        name  TEXT NOT NULL,
        email TEXT NOT NULL UNIQUE
    )
""")

cur.execute("INSERT INTO users (name, email) VALUES (?, ?)", ("Анна", "anna@example.com"))
cur.executemany(
    "INSERT INTO users (name, email) VALUES (?, ?)",
    [("Борис", "boris@example.com"), ("Вера", "vera@example.com")],
)
conn.commit()

cur.execute("SELECT id, name, email FROM users ORDER BY id")
for row in cur.fetchall():
    print(row)

conn.close()

В консоли появятся три кортежа:

(1, 'Анна', 'anna@example.com')
(2, 'Борис', 'boris@example.com')
(3, 'Вера', 'vera@example.com')

Разбор по шагам:

  1. sqlite3.connect("shop.db") открывает файл относительно текущего рабочего каталога; если файла нет, он будет создан. Для временной базы в памяти передают ":memory:" — она исчезнет при закрытии соединения, это удобно в тестах.
  2. execute() выполняет один SQL-оператор. Многострочный запрос удобно писать в тройных кавычках.
  3. INTEGER PRIMARY KEY в SQLite — псевдоним внутреннего rowid, поэтому id заполняется сам: 1, 2, 3.
  4. executemany() выполняет один запрос для каждого кортежа из списка.
  5. commit() фиксирует вставку. Без него данные пропадут (пример ниже).

Параметры ?: почему нельзя f-строку

Самая частая ошибка — собрать запрос форматированием строки. Предположим, email пришел из формы:

import sqlite3

conn = sqlite3.connect("shop.db")
email = "x' OR '1'='1"   # «email», пришедший из формы

bad = f"SELECT name FROM users WHERE email = '{email}'"
print(conn.execute(bad).fetchall())

good = "SELECT name FROM users WHERE email = ?"
print(conn.execute(good, (email,)).fetchall())
conn.close()
[('Анна',), ('Борис',), ('Вера',)]
[]

Первый запрос вернул всех пользователей: кавычка из ввода закрыла строковый литерал, и условие превратилось в OR '1'='1'. Это SQL-инъекция. Во втором случае значение ушло в базу как данные, и пользователя с таким email ожидаемо нет.

Параметры подставляют только значения. Имя таблицы или колонки через ? передать нельзя — если оно зависит от ввода, выбирайте его из заранее заданного списка разрешенных имен. Параметризация защищает от инъекции, но не проверяет сами данные: длину, формат email и права пользователя проверяйте отдельно.

Две ошибки с параметрами

Параметры передаются последовательностью, даже если значение одно. Строка — тоже последовательность (символов), поэтому:

import sqlite3

conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE t (name TEXT)")
conn.execute("INSERT INTO t VALUES (?)", "Анна")
sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 1, and there are 4 supplied.

Четыре — это буквы слова «Анна». Исправление: кортеж из одного элемента с запятой — ("Анна",).

Та же ошибка у executemany(), если передать один кортеж вместо списка кортежей: executemany(sql, ("Анна", "anna@example.com")) дает «uses 2, and there are 4 supplied» — каждая строка кортежа разбирается как отдельная запись. Нужно [("Анна", "anna@example.com")].

Кроме ? поддерживаются именованные параметры :name со словарем: conn.execute("SELECT name FROM users WHERE id > :min_id", {"min_id": 1}). Смешивать нельзя: в Python 3.14 именованный параметр с кортежем вызывает ProgrammingError, в 3.12-3.13 это было предупреждение DeprecationWarning.

Чтение: fetchone, fetchmany, fetchall и sqlite3.Row

import sqlite3

conn = sqlite3.connect("shop.db")
conn.row_factory = sqlite3.Row  # строки как словари: row["name"]
cur = conn.cursor()

cur.execute("SELECT id, name FROM users ORDER BY id")
first = cur.fetchone()
print(first["id"], first["name"])       # одна строка
print([tuple(r) for r in cur.fetchmany(1)])  # следующая порция
print([tuple(r) for r in cur.fetchall()])    # все, что осталось
print(cur.fetchone())                   # строки кончились

cur.execute("SELECT name FROM users WHERE email = ?", ("nobody@example.com",))
print(cur.fetchone())
conn.close()
1 Анна
[(2, 'Борис')]
[(3, 'Вера')]
None
None
Метод Что возвращает Когда брать
fetchone() одну строку или None поиск по ключу, проверка «есть ли запись»
fetchmany(n) список до n строк обработка большой выборки порциями
fetchall() список всех оставшихся строк небольшой результат целиком
for row in cur строки по одной большой результат без загрузки в память

Курсор движется только вперед: после fetchone() метод fetchall() вернет уже оставшиеся строки. None из fetchone() — нормальный ответ «ничего не найдено», его нужно проверять до обращения row["name"]. sqlite3.Row позволяет читать поля по имени, что надежнее номеров колонок.

commit, откат и контекстный менеджер

Что будет, если забыть commit():

import sqlite3

conn = sqlite3.connect("shop.db")
conn.execute("INSERT INTO users (name, email) VALUES (?, ?)", ("Глеб", "gleb@example.com"))
print(conn.in_transaction)   # транзакция открыта, но не зафиксирована
conn.close()                 # close() НЕ делает commit

conn = sqlite3.connect("shop.db")
print(conn.execute("SELECT count(*) FROM users").fetchone())
conn.close()
True
(3,)

Глеба в базе нет: незафиксированная транзакция отменилась при закрытии. Это поведение по умолчанию (режим совместимости с прежними версиями, autocommit не задан): перед INSERT, UPDATE, DELETE модуль сам открывает транзакцию, а закрыть ее должны вы.

Чтобы не держать это в голове, используют соединение как контекстный менеджер. Важная граница: with conn: управляет транзакцией (commit при успехе, rollback при исключении), но не закрывает соединение. Для закрытия нужен contextlib.closing или явный close(). В Python 3.13+ незакрытое соединение дает ResourceWarning (виден с флагом -W default).

import sqlite3
from contextlib import closing

with closing(sqlite3.connect("shop.db")) as conn:
    conn.execute("PRAGMA foreign_keys = ON")
    conn.execute("DROP TABLE IF EXISTS orders")
    conn.execute("""
        CREATE TABLE orders (
            id      INTEGER PRIMARY KEY,
            user_id INTEGER NOT NULL REFERENCES users(id),
            amount  INTEGER NOT NULL CHECK (amount > 0)  -- сумма в копейках
        )
    """)

    with conn:  # commit при успехе, rollback при исключении
        conn.execute("INSERT INTO orders (user_id, amount) VALUES (?, ?)", (1, 150000))
        conn.execute("INSERT INTO orders (user_id, amount) VALUES (?, ?)", (2, 99000))

    try:
        with conn:
            conn.execute("INSERT INTO orders (user_id, amount) VALUES (?, ?)", (3, 50000))
            conn.execute("INSERT INTO orders (user_id, amount) VALUES (?, ?)", (99, 10000))
    except sqlite3.IntegrityError as e:
        print("Откат:", e)

    print(conn.execute("SELECT count(*) FROM orders").fetchone())

    rows = conn.execute("""
        SELECT u.name, o.amount / 100.0 AS rub
        FROM orders AS o
        JOIN users AS u ON u.id = o.user_id
        ORDER BY o.id
    """).fetchall()
    print(rows)

    with conn:
        cur = conn.execute("UPDATE users SET name = ? WHERE email = ?", ("Вера П.", "vera@example.com"))
        print("обновлено:", cur.rowcount)
        cur = conn.execute("DELETE FROM users WHERE email = ?", ("nobody@example.com",))
        print("удалено:", cur.rowcount)

    try:
        with conn:
            conn.execute("DELETE FROM users WHERE id = ?", (1,))
    except sqlite3.IntegrityError as e:
        print("Не удалить:", e)
Откат: FOREIGN KEY constraint failed
(2,)
[('Анна', 1500.0), ('Борис', 990.0)]
обновлено: 1
удалено: 0
Не удалить: FOREIGN KEY constraint failed

Что здесь видно:

  • Во втором блоке with conn: первый заказ (для Веры) вставился, второй сослался на несуществующего пользователя 99. Откатились оба — заказов осталось 2, а не 3. Так работает транзакция: все или ничего.
  • PRAGMA foreign_keys = ON нужен в каждом новом соединении: в SQLite проверка внешних ключей по умолчанию выключена, и без этой строки заказ для пользователя 99 записался бы молча.
  • Деньги хранятся целым числом в копейках: в SQLite нет точного десятичного типа, а REAL — это float с ошибками округления.
  • cur.rowcount показывает, сколько строк затронули UPDATE и DELETE. Ноль — сигнал, что условие WHERE ничего не нашло, и это стоит обработать в коде, а не считать успехом.

Если не получилось

Симптом Причина Что делать
no such table: users опечатка в имени файла или скрипт запущен из другого каталога: connect() создал новую пустую базу абсолютный путь к базе, например через pathlib.Path(__file__).parent / "shop.db"
данные «пропали» после перезапуска не вызван commit() commit() или блок with conn:
database is locked другое соединение держит незавершенную транзакцию записи завершать транзакции быстро, закрывать соединения, увеличить timeout в connect()
Incorrect number of bindings supplied параметры переданы строкой, а не кортежем, или не хватает значений ("Анна",), число ? = число значений
UNIQUE constraint failed повторная вставка того же email ловить sqlite3.IntegrityError или INSERT ... ON CONFLICT

Что меняется при переходе на другую СУБД

Интерфейс драйверов в Python описан стандартом DB-API 2.0 (PEP 249), поэтому connect, cursor, execute, commit выглядят похоже. Отличаются стиль параметров и детали SQL.

Что sqlite3 PostgreSQL (psycopg) MySQL (PyMySQL)
Установка встроен в Python pip install psycopg pip install pymysql
Параметр ? или :name %s или %(name)s %s или %(name)s
Автоинкремент INTEGER PRIMARY KEY GENERATED ... AS IDENTITY AUTO_INCREMENT
Внешние ключи включать PRAGMA проверяются всегда проверяются в InnoDB
Строгость типов мягкая (affinity), строгая в STRICT-таблицах строгая зависит от sql_mode

%s в psycopg — тоже параметр драйвера, а не оператор % Python: значения по-прежнему передаются вторым аргументом execute(). Если вместо ручного SQL нужны модели и миграции, следующий шаг — ORM; о ней в статье введение в SQLAlchemy. Устройство самой СУБД, ее типы и ограничения разобраны в материале знакомство с SQLite.

Выводы

  • Для SQL в Python не нужны сторонние пакеты: sqlite3 встроен, схема работы — connect -> execute -> commit -> close.
  • Значения передаются только параметрами ? или :name; f-строка в SQL открывает дорогу инъекции.
  • Без commit() изменения теряются; with conn: фиксирует или откатывает транзакцию, но соединение не закрывает.
  • fetchone() может вернуть None, rowcount может быть 0 — оба случая проверяются в коде.
  • Внешние ключи в SQLite включаются PRAGMA foreign_keys = ON в каждом соединении.

Где применяется / связь с практикой

Освойте тему на практике

sqlite3 используют для локальных утилит, кэшей, прототипов, тестов и настольных приложений; те же приемы DB-API работают с серверными СУБД в веб-сервисах и скриптах обработки данных. Системно разобрать Python от основ языка до работы с базами и проектов можно на курсе «Python-разработчик. Базовый уровень». Бесплатно познакомиться с преподавателями и темами можно на открытых уроках Otus.

FAQ

Можно ли выполнить несколько SQL-команд одной строкой?
execute() принимает один оператор. Для SQL-скрипта из нескольких команд есть executescript(), но он не принимает параметры, поэтому подходит только для заранее известного SQL, например создания схемы.

Как получить id только что вставленной строки?
Через cur.lastrowid после execute() с INSERT. Если sqlite3.sqlite_version не ниже 3.35, можно также написать INSERT ... RETURNING id и прочитать результат через fetchone().

Можно ли использовать одно соединение из нескольких потоков?
По умолчанию sqlite3 запрещает использовать соединение не из того потока, где оно создано. Надежнее открывать отдельное соединение в каждом потоке; одновременная запись в SQLite все равно идет по очереди.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)