SQLAlchemy 作为 Python 生态中最强大的 ORM 工具之一,其设计哲学是"SQL 表达式语言 + ORM"的双重模式。这种独特架构让开发者既能享受面向对象编程的便利,又能在需要时直接操作原生 SQL。我在实际项目中最欣赏的是它的"不妥协"设计——不像某些 ORM 为了简化而牺牲灵活性。
引擎(Engine)是 SQLAlchemy 的心脏,它管理着两个关键资源:
会话(Session)是业务操作的主要入口,它的生命周期管理有几点需要注意:
连接池配置需要根据业务特点调整:
python复制engine = create_engine(
"postgresql+psycopg2://user:pass@localhost/db",
pool_size=10, # 常驻连接数
max_overflow=20, # 临时允许扩容的连接数
pool_recycle=3600, # 连接自动回收时间(秒)
pool_pre_ping=True # 执行前检查连接有效性
)
重要提示:MySQL 默认会关闭闲置 8 小时以上的连接,必须设置 pool_recycle 小于 wait_timeout 参数
除了基础的 Integer/String 类型,SQLAlchemy 提供了丰富的特殊类型:
python复制from sqlalchemy import ARRAY, JSON, UUID
from sqlalchemy.dialects.postgresql import HSTORE
class Product(Base):
__tablename__ = "products"
# PostgreSQL 专用数组类型
tags = Column(ARRAY(String))
# 跨平台的 JSON 字段
specs = Column(JSON)
# UUID 主键(替代自增ID)
uid = Column(UUID(as_uuid=True), primary_key=True)
多对多关系需要中间关联表,这是实际项目中最易出错的场景之一:
python复制# 关联表(纯表结构,不需要模型类)
post_tags = Table(
"post_tags",
Base.metadata,
Column("post_id", ForeignKey("posts.id"), primary_key=True),
Column("tag_id", ForeignKey("tags.id"), primary_key=True)
)
class Post(Base):
__tablename__ = "posts"
tags = relationship("Tag", secondary=post_tags, back_populates="posts")
class Tag(Base):
__tablename__ = "tags"
posts = relationship("Post", secondary=post_tags, back_populates="tags")
关联加载策略对性能影响巨大:
python复制# 查询优化示例
from sqlalchemy.orm import selectinload
posts = session.query(Post).options(
selectinload(Post.tags),
selectinload(Post.comments).joinedload(Comment.author)
).all()
SQLAlchemy 提供了强大的表达式构建能力:
python复制from sqlalchemy import or_, and_, not_
# 多条件组合查询
query = session.query(User).filter(
or_(
User.role == "admin",
and_(
User.status == "active",
User.signup_date > datetime(2023, 1, 1)
)
)
)
# NULL 值特殊处理
query = query.filter(User.deleted_at.is_(None))
分析型查询可以使用窗口函数:
python复制from sqlalchemy import func, over
# 计算每个部门的薪资排名
query = session.query(
Employee.name,
Employee.salary,
func.rank().over(
order_by=Employee.salary.desc(),
partition_by=Employee.department_id
).label("rank")
)
不同数据库支持的隔离级别各异,需要合理配置:
python复制# PostgreSQL 设置隔离级别
engine = create_engine(
"postgresql+psycopg2://user:pass@localhost/db",
isolation_level="REPEATABLE READ"
)
使用版本号防止更新冲突:
python复制class Account(Base):
__tablename__ = "accounts"
id = Column(Integer, primary_key=True)
balance = Column(Numeric)
version_id = Column(Integer, nullable=False)
__mapper_args__ = {
"version_id_col": version_id
}
# 更新时会自动检查版本
account = session.query(Account).get(1)
account.balance += 100
session.commit() # 如果版本不匹配会抛出 StaleDataError
大量插入时使用 bulk 操作可提升 10 倍以上性能:
python复制# 普通插入(慢)
for item in data:
session.add(MyModel(**item))
# 批量插入(快)
session.bulk_insert_mappings(MyModel, data)
模型定义时声明索引:
python复制class LogRecord(Base):
__tablename__ = "logs"
id = Column(Integer, primary_key=True)
created_at = Column(DateTime, index=True) # 单列索引
user_id = Column(Integer)
action = Column(String(50))
__table_args__ = (
Index("idx_user_action", "user_id", "action"), # 复合索引
)
使用事件监听检测未关闭的连接:
python复制from sqlalchemy import event
@event.listens_for(Engine, "checkout")
def on_checkout(dbapi_conn, connection_record, connection_proxy):
print(f"Checkout connection: {id(dbapi_conn)}")
@event.listens_for(Engine, "checkin")
def on_checkin(dbapi_conn, connection_record):
print(f"Checkin connection: {id(dbapi_conn)}")
记录执行时间过长的查询:
python复制from sqlalchemy import event
import time
@event.listens_for(Engine, "before_cursor_execute")
def before_cursor_execute(conn, cursor, statement, parameters, context, executemany):
context._query_start_time = time.time()
@event.listens_for(Engine, "after_cursor_execute")
def after_cursor_execute(conn, cursor, statement, parameters, context, executemany):
duration = time.time() - context._query_start_time
if duration > 0.5: # 记录超过500ms的查询
print(f"Slow query ({duration:.2f}s): {statement}")
使用绑定路由实现简单分片:
python复制from sqlalchemy.ext.horizontal_shard import ShardedSession
shard_lookup = {
"shard1": create_engine("postgresql://shard1/db"),
"shard2": create_engine("postgresql://shard2/db")
}
def shard_chooser(mapper, instance, clause=None):
if instance.user_id % 2 == 0:
return "shard1"
return "shard2"
Session = ShardedSession(
sessionmaker(),
shard_lookup,
shard_chooser
)
使用模式(Schema)隔离租户数据:
python复制from sqlalchemy import event
from sqlalchemy.orm import Session
@event.listens_for(Session, "do_orm_execute")
def set_search_path(execute_state):
if execute_state.is_select:
tenant_id = get_current_tenant() # 从请求上下文中获取
execute_state.statement = execute_state.statement.execution_options(
schema_translate_map={None: f"tenant_{tenant_id}"}
)
在长期使用 SQLAlchemy 的过程中,我发现最容易被忽视的是会话生命周期管理。特别是在 Web 应用中,一定要确保每个请求结束时正确关闭会话,否则会导致连接泄露。另一个实用技巧是合理使用 expire_on_commit=False 配置,这可以避免很多意外的延迟加载问题,特别是在返回 JSON 响应时。