Python-GINO中的Baked Queries性能优化指南
什么是Baked Queries
Baked Queries是Python-GINO提供的一种查询性能优化技术,它通过预编译SQL语句和缓存查询对象构造过程来显著提升频繁执行查询的性能。根据官方测试,使用Baked Queries可以使查询执行速度提升至少40%。
为什么需要Baked Queries
在传统ORM中,每次执行查询通常需要完成以下步骤:
- 构造查询对象
- 编译查询为SQL语句
- 准备语句(对于支持预编译的数据库)
- 执行查询
对于频繁执行的相同查询,前三个步骤实际上是重复工作。Baked Queries通过缓存这些步骤的结果,避免了重复计算,从而提高了查询效率。
两种使用方式
1. 底层Bakery API
这种方式适合需要精细控制查询的场景:
import gino
import sqlalchemy as sa
# 创建Bakery实例
bakery = gino.Bakery()
# 烘焙原生SQL查询
db_time = bakery.bake("SELECT now()")
# 烘焙带参数的查询
user_query = bakery.bake("SELECT * FROM users WHERE id = :uid")
# 使用SQLAlchemy Core烘焙查询
metadata = sa.MetaData()
user_table = sa.Table(
"users", metadata,
sa.Column("id", sa.Integer, primary_key=True),
sa.Column("name", sa.String),
)
user_query = bakery.bake(
sa.select([user_table]).where(user_table.c.id == sa.bindparam("uid"))
)
# 创建带Bakery的引擎
engine = await gino.create_engine("postgresql://localhost/", bakery=bakery)
# 执行烘焙查询
now = await engine.scalar(db_time)
user = await engine.first(user_query, uid=123)
2. 高级Gino集成
这种方式更适合常规应用开发,与GINO ORM深度集成:
from gino import Gino
db = Gino()
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
# 烘焙简单查询
db_time = db.bake("SELECT now()")
# 烘焙模型查询
user_getter = db.bake(User.query.where(User.id == db.bindparam("uid")))
async def main():
async with db.with_bind("postgresql://localhost/"):
# 执行查询
print(await db_time.scalar())
user = await user_getter.first(uid=1)
print(user.name)
模型类中的Baked Queries
可以将常用查询直接定义为模型类方法:
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
@db.bake
def getter(cls):
return cls.query.where(cls.id == db.bindparam("uid"))
@classmethod
async def get(cls, uid):
return await cls.getter.one_or_none(uid=uid)
这种方式使代码组织更加清晰,查询逻辑与模型紧密关联。
自定义加载器
可以在烘焙查询时指定加载选项:
# 方式1:直接在查询中指定
user_getter = db.bake(
User.query.where(User.id == db.bindparam("uid")).execution_options(
loader=User.load(comment="Added by loader.")
)
)
# 方式2:通过bake参数指定
user_getter = db.bake(
User.query.where(User.id == db.bindparam("uid")),
loader=User.load(comment="Added by loader."),
)
# 方式3:使用装饰器
@db.bake(loader=User.load(comment="Added by loader."))
def user_getter():
return User.query.where(User.id == db.bindparam("uid"))
执行时也可以临时覆盖加载器:
user = await user_getter.load(User).first(uid=1)
可用API
BakedQuery继承了GinoExecutor的所有方法,包括:
all()
- 获取所有结果first()
- 获取第一个结果one()
- 获取唯一结果(无结果或多结果会报错)one_or_none()
- 获取零或一个结果scalar()
- 获取标量结果status()
- 获取执行状态load()
- 指定加载器timeout()
- 设置超时
延迟准备语句
默认情况下,GINO会在创建引擎时为所有烘焙查询在所有连接上创建预编译语句。如果表结构尚未创建(如依赖db.gino.create_all()
),可以禁用此行为:
# 使用底层API
e = await gino.create_engine("postgresql://...", bakery=bakery, prebake=False)
# 使用GINO集成
await db.set_bind("postgresql://...", prebake=False)
这样预编译语句会在首次执行查询时按需创建并缓存。
最佳实践
- 对于频繁执行的查询,特别是性能敏感的核心业务逻辑,优先考虑使用Baked Queries
- 将常用查询封装为模型类方法,提高代码复用性
- 在应用启动阶段完成所有查询的烘焙工作
- 根据实际场景选择是否启用prebake
- 合理使用加载器优化数据加载过程
通过合理使用Baked Queries,可以显著提升GINO应用的数据库查询性能,特别是在高并发场景下效果更为明显。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考