Sqlalchemy 使用 query.count() 导致查询速度慢问题的优化

Sqlalchemy 使用 query.count() 统计数量

sqlalchemy 官方文档参考

问题背景

后台管理系统后台在对接管理端时,管理端某个页面展示中,需要调后台接口获取数据展示并做分页处理,请求时发现请求接口响应平均耗时4~5s。后台数据量并不大,20W 的数据量,联表查询有索引的情况下,理论上不至于耗时这么久。

问题查因

代码示例

trade_db = SessionMixin()
trade_db.factory = trade_factory

start = 0  # 起始参数
count = 100  # 获取数据条数

with trade_db.make_session() as session:
    query = session.query(Instruction).join(
        Order, Order.instructionId.__eq__(Instruction.instructionId)
    ).join(
        SubAccount, SubAccount.subAccountId.__eq__(Instruction.subAccountId)
    ).join(
        Account, Account.accountId.__eq__(SubAccount.accountId)
    )
    # 取数据返回
    result = query.order_by(desc(Instruction.instructionId)).offset(start).limit(count).all()
    # 返回数据总量,给前端做分页展示
    start_time = time.time()
    total = query.count()
    end_time = time.time()
    cost = end_time - start_time
    logger.debug(f'ins total is {total}, cost {cost}s')

这里代码片段分别做了两次查询,一次是获取前端展示数据,一次是获取数据总量。
query.count()调用时,查看日志输出的原始 sql 语句如下:
在这里插入图片描述
这里使用到了子查询,anon_1 子表为临时表。
查阅官方文档
在这里插入图片描述
官方文档有特别说明,使用 func.count() 的方式即不使用子表查询。

解决方案

根据官方文档,使用 func.count() 的方式查询数据总数,注意使用 scalar() 获取 int 返回值。

trade_db = SessionMixin()
trade_db.factory = trade_factory

start = 0  # 起始参数
count = 100  # 获取数据条数

with trade_db.make_session() as session:
    query = session.query(Instruction).join(
        Order, Order.instructionId.__eq__(Instruction.instructionId)
    ).join(
        SubAccount, SubAccount.subAccountId.__eq__(Instruction.subAccountId)
    ).join(
        Account, Account.accountId.__eq__(SubAccount.accountId)
    )
    # 取数据返回
    result = query.order_by(desc(Instruction.instructionId)).offset(start).limit(count).all()
    # 返回数据总量,给前端做分页展示
    # start_time = time.time()
    # total = query.count()
    # end_time = time.time()
    # cost = end_time - start_time
    # logger.debug(f'ins total is {total}, cost {cost}s')

with trade_db.make_session() as session:
    start_time = time.time()
    total = session.query(func.count(Instruction.instructionId)).join(
        Order, Order.instructionId.__eq__(Instruction.instructionId)
    ).join(
        SubAccount, SubAccount.subAccountId.__eq__(Instruction.subAccountId)
    ).join(
        Account, Account.accountId.__eq__(SubAccount.accountId)
    ).scalar()
    end_time = time.time()
    cost = end_time - start_time
    logger.debug(f'ins total is {total}, cost {cost}s')

问题总结

sqlalchemy 打开调试模式时,可以在日志中查看 orm 执行的原始 sql 语句, query.count() 方法被用于确定返回的结果集中有多少行,sqlalchemy 先是取出符合条件的所有行集合,作为一个子表(临时表),然后再通过 SELECT count(*) 来统计有多少行。这样会遍历整个结果集,当数据量大时,耗时剧增。若单纯统计数量则应该使用 func.count(Instruction.instructionId) 通过统计主键来查询总数,相当于直接 SELECT count(*) ,这样可以避免使用子查询。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值