Python 数据访问与 SQLAlchemy
数据访问层隔离 SQL/ORM;SQLAlchemy 为核心生态之一,需理解会话、事务与 N+1。
#synthesis
#growing
#dev
#python
#python
[!info] related notes
- 所属 MOC:数据访问 · 学习路线
- 库与 API 速查:SQLAlchemy
- 相关:错误处理 · 配置
Python 数据访问与 SQLAlchemy
这个概念为什么出现
业务若直接拼 SQL 字符串,会散落注入风险与事务边界混乱。数据访问层(Repository/DAO)+ ORM/查询构建器提供结构,但 ORM 不是免费午餐(N+1、隐式 IO)。
[!abstract] 一句话理解 把持久化细节关在数据层;SQLAlchemy 提供 Core/ORM,会话与事务界定工作单元,查询行为必须可观察。
最小可运行示例
场景:用 SQLAlchemy 2.x 写入并查询用户
在隔离 Python 环境中安装 python -m pip install 'SQLAlchemy>=2,<3'。示例用内存 SQLite,只为验证 ORM 映射、提交和查询,不连接生产数据库。
# sqlalchemy_demo.py
from sqlalchemy import String, create_engine, select
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[str] = mapped_column(String, primary_key=True)
email: Mapped[str] = mapped_column(String, nullable=False)
def main() -> None:
engine = create_engine("sqlite+pysqlite:///:memory:")
try:
Base.metadata.create_all(engine) # 演示建表,不代替生产迁移
with Session(engine) as session:
with session.begin():
session.add(User(id="u1", email="a@example.com"))
# begin 正常结束才提交;仅 close Session 不会自动提交。
with Session(engine) as session:
email = session.scalar(select(User.email).where(User.id == "u1"))
print(email) # 取标量,不把依赖会话的 ORM 对象传到外层。
finally:
engine.dispose()
if __name__ == "__main__":
main()
执行 python sqlalchemy_demo.py,应输出 a@example.com。内存库仅在这个 Engine 的连接生命周期内存在;多连接、多线程测试要明确 SQLite 内存库与连接池的配置,不能假设所有连接看到同一份数据。
原来的字典仓储例子只演示内存读写:add 会覆盖同 ID 值,也没有独立接口或事务。它不能用来证明 SQLAlchemy 的唯一约束、持久性、并发或回滚行为。
结合场景再看三个关注点
- 示例直接使用 ORM;Repository/DAO 是按业务需要选择的边界,不是 SQLAlchemy 强制要求。
- Engine 通常按数据库配置复用,Session 按工作单元创建。
- 谨防延迟加载触发隐式查询;按使用路径选择预加载,并观测 SQL 数量。
核心概念与准确模型
- Engine 管理方言、DBAPI 与连接池策略;不是业务事务本身。
- Session 维护工作单元与身份映射,flush 把变更发往数据库但不等于 commit;查询也可能触发 autoflush。
- Core 提供 SQL 表达式与连接层,ORM 在其上增加映射、身份跟踪与对象工作单元。
- create_all 适合示例或初始化,不负责已有表的增量版本迁移;迁移可用 Alembic。
边界情况与反直觉行为
- N+1:先查列表,再逐条懒加载关联;一次查询是否足够取决于访问路径,不由使用 ORM 自动保证。
- 并发与生命周期:Session 不供多线程共享,AsyncSession 不供并发任务共享;按线程/任务工作单元创建,事务范围不必等于整个 HTTP 请求。
- 身份映射只属于会话:不是跨进程对象缓存。提交后对象默认可能过期,关闭会话后访问尚未加载的属性可能失败。
- 失败处理:begin 上下文遇异常会回滚;自行 flush 失败后若继续复用同一 Session,需要显式 rollback 或结束该工作单元。
- 参数化:表达式会绑定值;自行拼接 text SQL、动态表名或排序字段仍需明确约束,ORM 不自动消除所有注入风险。
常见误区
[!warning] 常见误区:ORM 对象当跨层 DTO 到处传 错误理解:省转换。
正确模型:边界用明确 schema,避免会话剥离问题。
工程实践
- 事务边界清晰
- 慢查询日志
- 迁移版本化
本节总结
数据访问是正确性与性能交汇点。先边界,后 ORM 技巧。
自测题
- 仓储模式收益?
- N+1 是什么?
参考答案
- 业务与存储解耦、可测试。
- 循环中每次额外查询,放大延迟。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| SQLAlchemy | 文档 | ORM/Core |