上下文管理器(Context Manager)是Python中用于管理资源分配与释放的重要机制,它通过with语句实现资源的自动管理。这种设计模式在数据库连接、文件操作等场景中尤为重要,能有效避免资源泄漏问题。
Python的上下文管理器基于两个特殊方法实现:
python复制class MyContextManager:
def __enter__(self):
# 资源分配代码
print("进入上下文")
return resource # 返回的资源会被as变量接收
def __exit__(self, exc_type, exc_val, exc_tb):
# 资源释放代码
print("退出上下文")
if exc_type: # 处理异常情况
print(f"发生异常: {exc_val}")
return True # 返回True表示已处理异常
__enter__方法在进入with代码块时执行,负责资源的初始化;__exit__方法在退出代码块时执行,无论是否发生异常都会调用,确保资源被正确释放。这种机制比传统的try-finally更简洁直观。
SQLAlchemy的Session管理是上下文管理器的典型应用场景。以下是其内部实现的核心逻辑:
python复制class Session:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.commit()
else:
self.rollback()
self.close()
这种设计确保了在任何情况下(包括异常发生时)会话都能被正确关闭,数据库连接能及时返回到连接池。实际使用中,我们可以这样封装:
python复制from sqlalchemy.orm import sessionmaker
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class DatabaseSession:
def __init__(self):
self.session = SessionLocal()
def __enter__(self):
return self.session
def __exit__(self, exc_type, exc_val, exc_tb):
try:
if exc_type is None:
self.session.commit()
else:
self.session.rollback()
finally:
self.session.close()
Python标准库中的contextlib模块提供了更简洁的实现方式:
python复制from contextlib import contextmanager
from sqlalchemy.orm import Session
@contextmanager
def get_db_session():
session = SessionLocal()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
这种实现方式:
yield语句之前相当于__enter__yield之后相当于__exit__Python 3.7+引入了异步上下文管理器协议(__aenter__和__aexit__),适用于异步IO场景:
python复制class AsyncDatabaseSession:
async def __aenter__(self):
self.session = AsyncSessionLocal()
return self.session
async def __aexit__(self, exc_type, exc_val, exc_tb):
try:
if exc_type is None:
await self.session.commit()
else:
await self.session.rollback()
finally:
await self.session.close()
使用示例:
python复制async with AsyncDatabaseSession() as session:
result = await session.execute(select(User))
users = result.scalars().all()
在实际项目中,经常需要同时操作多个数据库。我们可以扩展上下文管理器来支持:
python复制from typing import Dict
from sqlalchemy.orm import sessionmaker
class MultiDBSession:
def __init__(self, engine_configs: Dict[str, dict]):
self.engines = {
name: create_engine(**config)
for name, config in engine_configs.items()
}
self.sessions = {}
def __enter__(self):
self.sessions = {
name: sessionmaker(bind=engine)()
for name, engine in self.engines.items()
}
return self.sessions
def __exit__(self, exc_type, exc_val, exc_tb):
for session in self.sessions.values():
try:
if exc_type is None:
session.commit()
else:
session.rollback()
finally:
session.close()
配置示例:
python复制db_configs = {
'primary': {
'url': 'postgresql://user:pass@localhost:5432/main',
'pool_size': 5
},
'secondary': {
'url': 'mysql://user:pass@localhost:3306/replica',
'echo': True
}
}
with MultiDBSession(db_configs) as dbs:
primary_users = dbs['primary'].query(User).all()
secondary_logs = dbs['secondary'].query(AccessLog).all()
会话作用域控制:
连接池配置优化:
python复制engine = create_engine(
'postgresql://user:pass@localhost:5432/db',
pool_size=10, # 连接池保持的连接数
max_overflow=5, # 允许超出pool_size的临时连接数
pool_timeout=30, # 获取连接的超时时间(秒)
pool_recycle=3600, # 连接自动回收时间(秒)
pool_pre_ping=True # 执行前检查连接有效性
)
python复制# 避免N+1查询问题
users = session.query(User).options(
joinedload(User.posts) # 使用JOIN立即加载
# 或
subqueryload(User.posts) # 使用子查询加载
).all()
# 批量操作优化
session.bulk_save_objects([...]) # 批量插入
session.bulk_update_mappings(...) # 批量更新
问题现象:
解决方案:
python复制# 明确刷新会话状态
session.refresh(user) # 从数据库重新加载对象
# 分离对象
session.expunge(user) # 从会话移除但保留数据
# 合并外部对象
external_user = User(name='external')
session.merge(external_user) # 将外部对象合并到当前会话
不同数据库的隔离级别设置方式:
python复制# PostgreSQL设置隔离级别
engine = create_engine(
'postgresql://user:pass@localhost/db',
isolation_level='REPEATABLE_READ'
)
# 临时修改隔离级别
from sqlalchemy import text
with engine.connect() as conn:
conn.execute(text("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"))
# 执行事务操作
python复制import logging
logging.basicConfig()
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
python复制from sqlalchemy import event
from datetime import datetime
@event.listens_for(engine, "before_cursor_execute")
def before_cursor_execute(conn, cursor, statement, parameters, context, executemany):
context._query_start_time = datetime.now()
@event.listens_for(engine, "after_cursor_execute")
def after_cursor_execute(conn, cursor, statement, parameters, context, executemany):
duration = (datetime.now() - context._query_start_time).total_seconds()
if duration > 0.5: # 记录慢查询
print(f"Slow query ({duration:.3f}s): {statement[:100]}...")
使用两阶段提交协议实现跨数据库事务:
python复制from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engines = [
create_engine('postgresql://user:pass@db1/db'),
create_engine('mysql://user:pass@db2/db')
]
sessions = [sessionmaker(bind=e)() for e in engines]
try:
# 第一阶段:准备
for session in sessions:
session.begin(prepare=True)
# 业务操作
sessions[0].add(User(name='dist_user'))
sessions[1].execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
# 第二阶段:提交
for session in sessions:
session.commit()
except Exception:
for session in sessions:
session.rollback()
raise
finally:
for session in sessions:
session.close()
创建具备特定功能的会话工厂:
python复制from sqlalchemy.orm import Session
class AuditedSession(Session):
def __init__(self, auditor, **kwargs):
super().__init__(**kwargs)
self._auditor = auditor
def commit(self):
for obj in self.new:
self._auditor.log_creation(obj)
for obj in self.dirty:
self._auditor.log_update(obj)
for obj in self.deleted:
self._auditor.log_deletion(obj)
super().commit()
# 使用自定义会话
SessionLocal = sessionmaker(class_=AuditedSession, bind=engine)
session = SessionLocal(auditor=FileAuditor())
使用分支策略实现多租户:
python复制from sqlalchemy.orm import Session
class TenantSession(Session):
_current_tenant = None
@classmethod
def set_tenant(cls, tenant_id):
cls._current_tenant = tenant_id
def get_bind(self, mapper=None, clause=None):
if self._current_tenant:
return get_tenant_engine(self._current_tenant)
return super().get_bind(mapper, clause)
# 使用示例
SessionLocal = sessionmaker(class_=TenantSession)
TenantSession.set_tenant('tenant_a')
with SessionLocal() as session:
# 自动连接到tenant_a的数据库
users = session.query(User).all()
在实际项目中,我发现在处理复杂事务时,将业务操作封装在独立的上下文管理器中可以显著提高代码的健壮性。例如,以下模式在实践中非常有效:
python复制@contextmanager
def transactional(session: Session, retries: int = 3):
for attempt in range(retries):
try:
with session.begin_nested():
yield session
session.commit()
break
except (OperationalError, InterfaceError) as e:
session.rollback()
if attempt == retries - 1:
raise
time.sleep(2 ** attempt)
这种实现提供了自动重试机制,特别适合网络不稳定的云环境。当遇到临时性数据库连接问题时,它会按照指数退避策略自动重试,大幅提高了系统的容错能力。