2. 环境搭建与第一个数据访问程序

在上一节中我们已经明确了 SQLAlchemy 在数据访问层中的定位:它既是 ORM 框架,也是独立的数据库工具包。本节要完成的是从零到一的环境搭建,并写出第一个能真正连上数据库、执行 SQL、拿到结果的最小程序。整个过程围绕五个关键动作展开:安装 SQLAlchemy、选择并安装数据库驱动、创建 Engine、执行原生 SQL、理解连接池与方言。这五步不是孤立的操作,而是一条从依赖到运行、从配置到结果的完整链路。

安装 SQLAlchemy 与选择数据库驱动

SQLAlchemy 本身通过 pip 安装即可,它不绑定任何具体数据库。真正与数据库通信的是 DBAPI 驱动,SQLAlchemy 通过方言(Dialect)适配不同驱动。因此安装时要分两步:先装框架,再装驱动。

# 安装 SQLAlchemy 2.x
pip install "SQLAlchemy>=2.0"

# 以 PostgreSQL 为例,安装 psycopg 驱动
pip install "psycopg[binary]"

# 若使用 MySQL,可安装 PyMySQL
pip install PyMySQL

# 若使用 SQLite,Python 标准库自带 sqlite3,无需额外安装

驱动选择需要关注三点:数据库类型、同步还是异步、驱动成熟度。同步场景下,PostgreSQL 推荐 psycopg(psycopg3),MySQL 推荐 PyMySQL 或 mysqlclient,SQLite 直接用内置 sqlite3。异步场景则使用 asyncpg、aiomysql 等,并配合 create_async_engine。驱动选错,最典型的报错就是 ModuleNotFoundError: No module named 'psycopg2' 或 Can't load plugin: sqlalchemy.dialects:postgresql.psycopg2,本质是方言找不到对应驱动。

创建 Engine:连接信息的统一入口

Engine 是 SQLAlchemy 访问数据库的核心对象,它并不等于一个连接,而是连接池与方言的组合管理器。创建 Engine 的标准方式是 create_engine(),参数是一个 URL。URL 的通用格式为:

dialect+driver://username:password@host:port/database

# PostgreSQL 示例
postgresql+psycopg://app_user:secret@127.0.0.1:5432/shop

# MySQL 示例
mysql+pymysql://app_user:secret@127.0.0.1:3306/shop?charset=utf8mb4

# SQLite 文件示例
sqlite:///./shop.db

# SQLite 内存示例
sqlite:///:memory:

URL 中的 dialect+driver 决定了使用哪个方言和驱动。如果只写 postgresql://,SQLAlchemy 会按默认驱动尝试加载,容易因环境差异失败,因此生产代码建议显式写全。创建 Engine 时还可以传入关键参数:echo=True 用于打印生成的 SQL,便于调试;pool_size、max_overflow、pool_pre_ping 用于控制连接池行为;connect_args 用于传递驱动特有的连接参数。

from sqlalchemy import create_engine

engine = create_engine(
    "postgresql+psycopg://app_user:secret@127.0.0.1:5432/shop",
    echo=True,
    pool_size=5,
    max_overflow=10,
    pool_pre_ping=True,
)

执行原生 SQL 并读取结果

Engine 创建后,通过 engine.connect() 获取连接,再用 text() 包装 SQL 语句执行。SQLAlchemy 2.x 要求显式使用 text(),这是为了避免字符串拼接带来的注入风险,也便于绑定参数。

from sqlalchemy import text

with engine.connect() as conn:
    # 建表
    conn.execute(text("""
        CREATE TABLE IF NOT EXISTS product (
            id SERIAL PRIMARY KEY,
            name VARCHAR(64) NOT NULL,
            price NUMERIC(10, 2) NOT NULL
        )
    """))

    # 插入,使用绑定参数
    conn.execute(
        text("INSERT INTO product (name, price) VALUES (:name, :price)"),
        {"name": "机械键盘", "price": 399.00},
    )
    conn.commit()

    # 查询
    rows = conn.execute(text("SELECT id, name, price FROM product ORDER BY id"))
    for row in rows:
        print(row.id, row.name, row.price)

这里有两个容易踩坑的点。第一,SQLAlchemy 2.x 默认不会自动提交,执行 INSERT、UPDATE、DELETE 后必须调用 conn.commit(),否则连接关闭时事务回滚,数据不会落库。第二,查询结果 rows 是 Result 对象,可以迭代,也可以使用 rows.mappings() 得到字典式行,或 rows.scalars() 取单列。若使用 engine.begin() 上下文管理器,则会在退出时自动提交或回滚,更适合写操作。

连接池与方言:Engine 背后的两条主线

连接池解决的是“频繁建立和销毁数据库连接代价高”的问题。Engine 默认使用 QueuePool,维护一定数量的空闲连接,请求到来时复用,超出 pool_size 后允许创建 max_overflow 个临时连接,超过上限则等待。连接不是永久有效的,数据库可能因超时、重启或网络中断而断开,pool_pre_ping=True 会在借出连接前先发一个轻量探测,失效连接被丢弃并重建,避免程序拿到坏连接后报错。

方言则是 SQLAlchemy 与具体数据库之间的翻译层。它负责把统一的 SQLAlchemy 表达式编译成目标数据库能识别的 SQL,并处理类型映射、自增主键、分页语法、标识符引用等差异。例如同样是自增主键,PostgreSQL 用 SERIAL,MySQL 用 AUTO_INCREMENT,SQLite 用 INTEGER PRIMARY KEY AUTOINCREMENT,这些差异都由方言吸收。理解这一点,就能明白为什么换数据库时主要改的是 URL 和驱动,而不是业务代码。

Engine 内部结构与数据访问链路 应用代码 create_engine() engine.connect() Engine 连接池 QueuePool 空闲 空闲 借出 溢出 pool_size=5 · max_overflow=10 · pool_pre_ping 方言 Dialect SQL 编译 · 类型映射 · 分页语法 自增主键 · 标识符引用 postgresql+psycopg / mysql+pymysql / sqlite DBAPI 驱动 psycopg / PyMySQL sqlite3 数据库 PostgreSQL / MySQL 执行链路: 应用代码 → Engine 借出连接 → 方言编译 SQL → 驱动发送到数据库 → 结果返回 Result 对象 → 连接归还池中 关键点:写操作需 commit;pool_pre_ping 防坏连接;换库主要改 URL 与驱动,业务代码基本不动

一个可运行的最小程序

把上面的片段组合起来,就是一个完整可运行的程序。下面以 SQLite 为例,因为它无需额外安装驱动,适合第一次验证环境。

from sqlalchemy import create_engine, text

engine = create_engine("sqlite:///./shop.db", echo=True)

with engine.begin() as conn:
    conn.execute(text("""
        CREATE TABLE IF NOT EXISTS product (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name VARCHAR(64) NOT NULL,
            price NUMERIC(10, 2) NOT NULL
        )
    """))
    conn.execute(
        text("INSERT INTO product (name, price) VALUES (:name, :price)"),
        {"name": "机械键盘", "price": 399.00},
    )

with engine.connect() as conn:
    result = conn.execute(text("SELECT id, name, price FROM product"))
    for row in result:
        print(f"id={row.id}, name={row.name}, price={row.price}")

运行后,如果 echo=True 生效,控制台会打印出 SQLAlchemy 生成的 SQL 和参数,这能帮你确认方言是否正确工作。查询输出类似 id=1, name=机械键盘, price=399.00,说明从建表、插入到查询的链路已经打通。若报错,优先检查三处:驱动是否安装、URL 格式是否正确、写操作是否提交。

调试建议:先用 SQLite 内存库 sqlite:///:memory: 验证代码逻辑,再切换到真实数据库。内存库随连接关闭即销毁,适合快速试错,但要注意连接池可能复用同一连接,导致“表已存在”或“数据仍在”的现象。
安全提醒:不要把数据库密码硬编码在代码里。生产环境应通过环境变量或配置中心注入,例如 os.environ["DATABASE_URL"],再传给 create_engine()。同时,原生 SQL 一律使用 text() 加绑定参数,不要用 f-string 拼接用户输入。

到这里,环境搭建与第一个数据访问程序已经完成。你应当能够独立完成:安装 SQLAlchemy 与驱动、根据数据库类型写出正确的 URL、创建带连接池参数的 Engine、用 text() 执行原生 SQL 并读取结果,以及解释连接池和方言各自解决什么问题。这些是后续 ORM 映射、会话管理和事务控制的基础,建议在本地至少用 SQLite 和另一种数据库各跑通一次,形成可复用的环境检查习惯。