SQLAlchemy 奇怪的查数据库行为

文章目录

  • SQLAlchemy 奇怪的查数据库行为
  • 起步
  • 环境准备
  • 奇怪的查数据库行为
  • expire\_on\_commit
  • 如何解决

020.10.16

起步

进行数十亿数据压测时,测试环境发生了一个很奇怪的现象:在对 SQLAlchemy 查出的结果做遍历操作时,随着集合越大,遍历的时间跟着变长,是肉眼可见的变长。

根据现象,一位腾讯的程序员向我提出了疑问:会不会遍历过程中 SQLAlchemy 又去查询了数据库?

环境准备

-- DDL
CREATE TABLE `student` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(10) NOT NULL,`age` int(4) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8-- DML
INSERT INTO `test`.`student`(`name`, `age`) VALUES ('小庭', 16), ('小英', 17), ('小慧', 10);

奇怪的查数据库行为

在使用 SQLAlchemy 时,为了更方便的使用,我自己对 session 做了一层封装:

from contextlib import contextmanagersession = sessionmaker(create_engine("mysql+pymysql://root:123456@192.168.111.136/test"))@contextmanager
def MSession():""" session 管理器 """sess = session()yield sesssess.commit()

这样做的好处是,我不用每一次都显式地调用 session() 和 commit()。代码组织起来也好看许多。完整代码如下:

from contextlib import contextmanagerfrom sqlalchemy import Column, String, Integer, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext import declarativeBase = declarative.declarative_base()class Student(Base):__tablename__ = "student"id = Column(Integer, primary_key=True, autoincrement=True)name = Column(String, nullable=False)age = Column(Integer, nullable=False)session = sessionmaker(create_engine("mysql+pymysql://root:123456@192.168.111.136/test"))@contextmanager
def MSession():""" session 管理器 """sess = session()yield sesssess.commit()def main():with MSession() as sess:# 查询所有学生信息students = sess.query(Student).all()# 遍历查询结果for stu in students:print(f"查询结果: {stu.id} {stu.name} {stu.age}")if __name__ == "__main__":main()

如果就这样执行上述代码,可以看到输出合理、一切正常。但要是打开 SQLAlchemy 的 echo 行为,就会发现事情并没有那么简单。

设置 echo=True:

session = sessionmaker(create_engine("mysql+pymysql://root:123456@192.168.111.136/test", echo=True)
)

此时再运行代码会看到如下输出(输出的一部分信息):

1. 2020-10-16 17:12:04,147 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2. 22020-10-16 17:12:04,147 INFO sqlalchemy.engine.base.Engine SELECT student.id AS student_id, student.name AS student_name, student.age AS student_age
FROM student
3. 2020-10-16 17:12:04,148 INFO sqlalchemy.engine.base.Engine {}
4. 2020-10-16 17:12:04,158 INFO sqlalchemy.engine.base.Engine COMMIT
5. 2020-10-16 17:12:04,171 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
6. 2020-10-16 17:12:04,173 INFO sqlalchemy.engine.base.Engine SELECT student.id AS student_id, student.name AS student_name, student.age AS student_age
FROM student
WHERE student.id = %(param_1)s
7. 2020-10-16 17:12:04,173 INFO sqlalchemy.engine.base.Engine {'param_1': 1}
查询结果: 1 小庭 16
8. 2020-10-16 17:12:04,183 INFO sqlalchemy.engine.base.Engine SELECT student.id AS student_id, student.name AS student_name, student.age AS student_age
FROM student
WHERE student.id = %(param_1)s
9. 2020-10-16 17:12:04,183 INFO sqlalchemy.engine.base.Engine {'param_1': 2}
查询结果: 2 小英 17
10. 2020-10-16 17:12:04,195 INFO sqlalchemy.engine.base.Engine SELECT student.id AS student_id, student.name AS student_name, student.age AS student_age
FROM student
WHERE student.id = %(param_1)s
11. 2020-10-16 17:12:04,195 INFO sqlalchemy.engine.base.Engine {'param_1': 3}
查询结果: 3 小慧 10

程序的执行顺序对应到日志从上到下的输出顺序。可以看到,最开始 sqlalchemy 确实执行了查询语句:

SELECT student.id AS student_id, student.name AS student_name, student.age AS student_age FROM student

对应到代码的:

with MSession() as sess:# 查询所有学生信息students = sess.query(Student).all()

可是后面的行为就很奇怪了,每 print 一次查询结果之前,就会有一次数据库查询行为:

6. 2020-10-16 17:12:04,173 INFO sqlalchemy.engine.base.Engine SELECT student.id AS student_id, student.name AS student_name, student.age AS student_age
FROM student
WHERE student.id = %(param_1)s
7. 2020-10-16 17:12:04,173 INFO sqlalchemy.engine.base.Engine {'param_1': 1}
查询结果: 1 小庭 16
...

假设 students 是一个包含 1w 个元素的集合,那么这里就会有 1w 次查询,对应 1w 次 io 操作。且不说这样查询多此一举,重要的是大量 io 操作会拖慢程序的运行速度。

expire_on_commit

起初我是在网上找原因,无果。继而到 SQLAlchemy 源码里来回逛几圈,终于在 Session 类的注释中找到了造成上述现象的原因。

# SQLAlchemy 源码,非关键内容省略
class Session(_SessionClassMethods):"""Manages persistence operations for ORM-mapped objects.The Session's usage paradigm is described at :doc:`/orm/session`."""...def __init__(self,bind=None,autoflush=True,expire_on_commit=True,...):""":param expire_on_commit:  Defaults to ``True``. When ``True``, allinstances will be fully expired after each :meth:`~.commit`,so that all attribute/object access subsequent to a completedtransaction will load from the most recent database state."""

大概意思就是,当 expire_on_commit=True 时,commit 之后所有实例都会过期,之后再访问这些过期实例的属性时,SQLAlchemy 会重新去数据库加载实例对应的数据记录。

如何解决

知道原因以后,解决方案就有了。目前我想到了两种:

  1. 把用到实例的逻辑移到 commit 操作之前,如:
with MSession() as sess:# 查询所有学生信息students = sess.query(Student).all()# 遍历查询结果for stu in students:print(f"查询结果: {stu.id} {stu.name} {stu.age}")

但这样的缺点是,当你需要处理实例的逻辑很耗时时,意味着会产生一个长事务。无论何种情况下,长事务都应该尽量避免。

  1. expire_on_commit 置为 False:
@contextmanager
def MSession():""" session 管理器 """sess = session(expire_on_commit=False)  # 关闭 expire_on_commit 行为yield sesssess.commit()

SQLAlchemy 奇怪的查数据库行为相关推荐

  1. day 45 SQLAlchemy,和增删查改

    SQLAlchemy,和增删查改 SQLAlchemy: SQLAlchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简言之便是:将对象 ...

  2. 关于Qt的CRUD增删改查数据库那些事,带GUI图像界面

    关于Qt的CRUD增删改查数据库那些事,带GUI图像界面 首先感谢CSDN平台提供这样强大的分享平台. Qt Creator 的几个常用快捷键必须要会,开发事半功倍, Ctrl 简称 C C + i ...

  3. entity framework不查数据库修改或排除指定字段集合通用方法

    其中DataDBEntities为数据库实体对象,代码如下: 下载地址:http://files.cnblogs.com/stone_w/EFDBHelper.zip using System; us ...

  4. PHP增删改查数据库(前端+后台)

    PHP增删改查数据库(前端+后台) 要求: 首页导航栏中内置功能 查看数据库 点击Edit修改数据库内容 点击Delete后删除数据库内此记录,返回首页输出删除成功. 向数据库里增加数据 向搜索框输入 ...

  5. python查数据库写入excel_【Python】将数据库中的数据查询出来自动写入excel文档...

    近期每天都要监控一个数据. 第一个版本是这样的: 每天新增一个文档来汇总这个数据.这样搞了几天之后,过了一个周末,过来突然发现数据变多了很多,这个时候要调整策略,直接一个文档汇总出要的数据就可以了. ...

  6. 根据数据库表gengxin实体类_Python学习第四十八天记录打call:SQLALchemy操作MySQL关系型数据库...

    1.SQLALchemy使用 安装 pip install sqlalchemy: SQLAlchemy是Python编程语言下的一款开源软件,是PythonSQL工具包和对象关系映射器,它为应用程序 ...

  7. 【Python Flask】SQLAlchemy增删改查总结;不重复查询某一列

    SQLAlchemy数据库操作 1.增加一条记录 # model层 class Users_menu(db.Model):__tablename__ = 'users_menu'id = db.Col ...

  8. flask-sqlalchemy mysql_Flask SQLAlchemy连接到MySQL数据库

    设置代码: 我正在构建一个带有AngularJS前端的基本Flask应用程序,目前我需要连接到我用Godaddy phpmyadmin托管的MySQL数据库. 这是我的一部分__init__.pyfr ...

  9. Python笔记-使用sqlalchemy根据类创建数据库表

    这里有一点要提的 这个mysql+pymysql,这个mysql会通过字符串导入对应的模块这里要预装下mysql模块: 逻辑操作: 使用create_engine创建数据库连接, 使用sessionm ...

最新文章

  1. [原创]关于javax.servlet.ServletException: File [/loginController/getVerifCode.jsp] not found异常 解决方案
  2. 皮一皮:这是为什么呢???
  3. Selector SelectionKey
  4. 前端动态菜单权限、按钮权限实现思路
  5. Python描述性统计示例
  6. C++17新特性学习笔记
  7. python3 asyncio 爬虫_python3 asyncio异步新浪微博爬虫WeiboSpider
  8. Column name pattern can not be NULL or empty.
  9. 【推荐实践】内容分发场景的多目标架构实践
  10. linux文件共享加锁,Linux共享数据管理——文件锁定
  11. wap(dopra linux )命令,自行更换HG8321R千兆光猫记录
  12. 线粒体DNA(mtDNA)捕获探针panel试剂盒myBaits Expert Mito,适用各种样本类型(包含降解和环境DNA,化石或博物馆样本均可),应用于遗传学研究
  13. 关于File.separator[转]
  14. HTML语言中img标签的alt属性和title属性的作用与区别
  15. Redis - 几款可视化工具
  16. 转载:浅谈Session与Cookie的区别与联系
  17. Latex调整行间距
  18. 对话姚期智:中国人工智能界是怎么被我教出来的?
  19. 走出舒适圈有多难? 在职跳槽+非CS, 我收获了Amazon/Walmart/Indeed offer!
  20. 索尼大变身:消费电子业务转向医疗设备

热门文章

  1. CJMCU-Beetle Arduino Leonardo引脚编号对应关系
  2. 云和恩墨恭祝您牛年大吉!
  3. 惠普企业第一财季营收76亿美元 净利同比降88.1%
  4. postgresql 执行 drop table 后的一些分析
  5. 如何使用 Wireshark 分析 TCP 吞吐瓶颈
  6. confluence7.4.6邮件配置
  7. java日期相差周_利用Java中Calendar计算两个日期之间的天数和周数
  8. 股票量化交易软件_如何降低交易者的风险
  9. 面试怕被问“后端优化”问题?看看这套java性能调优手册吧!
  10. 基于SSM的图书进销存管理系统