SQLAlchemy 奇怪的查数据库行为
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 会重新去数据库加载实例对应的数据记录。
如何解决
知道原因以后,解决方案就有了。目前我想到了两种:
- 把用到实例的逻辑移到 commit 操作之前,如:
with MSession() as sess:# 查询所有学生信息students = sess.query(Student).all()# 遍历查询结果for stu in students:print(f"查询结果: {stu.id} {stu.name} {stu.age}")
但这样的缺点是,当你需要处理实例的逻辑很耗时时,意味着会产生一个长事务。无论何种情况下,长事务都应该尽量避免。
- 将
expire_on_commit
置为 False:
@contextmanager
def MSession():""" session 管理器 """sess = session(expire_on_commit=False) # 关闭 expire_on_commit 行为yield sesssess.commit()
SQLAlchemy 奇怪的查数据库行为相关推荐
- day 45 SQLAlchemy,和增删查改
SQLAlchemy,和增删查改 SQLAlchemy: SQLAlchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简言之便是:将对象 ...
- 关于Qt的CRUD增删改查数据库那些事,带GUI图像界面
关于Qt的CRUD增删改查数据库那些事,带GUI图像界面 首先感谢CSDN平台提供这样强大的分享平台. Qt Creator 的几个常用快捷键必须要会,开发事半功倍, Ctrl 简称 C C + i ...
- entity framework不查数据库修改或排除指定字段集合通用方法
其中DataDBEntities为数据库实体对象,代码如下: 下载地址:http://files.cnblogs.com/stone_w/EFDBHelper.zip using System; us ...
- PHP增删改查数据库(前端+后台)
PHP增删改查数据库(前端+后台) 要求: 首页导航栏中内置功能 查看数据库 点击Edit修改数据库内容 点击Delete后删除数据库内此记录,返回首页输出删除成功. 向数据库里增加数据 向搜索框输入 ...
- python查数据库写入excel_【Python】将数据库中的数据查询出来自动写入excel文档...
近期每天都要监控一个数据. 第一个版本是这样的: 每天新增一个文档来汇总这个数据.这样搞了几天之后,过了一个周末,过来突然发现数据变多了很多,这个时候要调整策略,直接一个文档汇总出要的数据就可以了. ...
- 根据数据库表gengxin实体类_Python学习第四十八天记录打call:SQLALchemy操作MySQL关系型数据库...
1.SQLALchemy使用 安装 pip install sqlalchemy: SQLAlchemy是Python编程语言下的一款开源软件,是PythonSQL工具包和对象关系映射器,它为应用程序 ...
- 【Python Flask】SQLAlchemy增删改查总结;不重复查询某一列
SQLAlchemy数据库操作 1.增加一条记录 # model层 class Users_menu(db.Model):__tablename__ = 'users_menu'id = db.Col ...
- flask-sqlalchemy mysql_Flask SQLAlchemy连接到MySQL数据库
设置代码: 我正在构建一个带有AngularJS前端的基本Flask应用程序,目前我需要连接到我用Godaddy phpmyadmin托管的MySQL数据库. 这是我的一部分__init__.pyfr ...
- Python笔记-使用sqlalchemy根据类创建数据库表
这里有一点要提的 这个mysql+pymysql,这个mysql会通过字符串导入对应的模块这里要预装下mysql模块: 逻辑操作: 使用create_engine创建数据库连接, 使用sessionm ...
最新文章
- [原创]关于javax.servlet.ServletException: File [/loginController/getVerifCode.jsp] not found异常 解决方案
- 皮一皮:这是为什么呢???
- Selector SelectionKey
- 前端动态菜单权限、按钮权限实现思路
- Python描述性统计示例
- C++17新特性学习笔记
- python3 asyncio 爬虫_python3 asyncio异步新浪微博爬虫WeiboSpider
- Column name pattern can not be NULL or empty.
- 【推荐实践】内容分发场景的多目标架构实践
- linux文件共享加锁,Linux共享数据管理——文件锁定
- wap(dopra linux )命令,自行更换HG8321R千兆光猫记录
- 线粒体DNA(mtDNA)捕获探针panel试剂盒myBaits Expert Mito,适用各种样本类型(包含降解和环境DNA,化石或博物馆样本均可),应用于遗传学研究
- 关于File.separator[转]
- HTML语言中img标签的alt属性和title属性的作用与区别
- Redis - 几款可视化工具
- 转载:浅谈Session与Cookie的区别与联系
- Latex调整行间距
- 对话姚期智:中国人工智能界是怎么被我教出来的?
- 走出舒适圈有多难? 在职跳槽+非CS, 我收获了Amazon/Walmart/Indeed offer!
- 索尼大变身:消费电子业务转向医疗设备
热门文章
- CJMCU-Beetle Arduino Leonardo引脚编号对应关系
- 云和恩墨恭祝您牛年大吉!
- 惠普企业第一财季营收76亿美元 净利同比降88.1%
- postgresql 执行 drop table 后的一些分析
- 如何使用 Wireshark 分析 TCP 吞吐瓶颈
- confluence7.4.6邮件配置
- java日期相差周_利用Java中Calendar计算两个日期之间的天数和周数
- 股票量化交易软件_如何降低交易者的风险
- 面试怕被问“后端优化”问题?看看这套java性能调优手册吧!
- 基于SSM的图书进销存管理系统