SQLAlchemy 的内存消耗

为何要研究SQLAlchemy 的内存消耗问题?因为SQLAlchemy在应用中,绝大多数问题体现在应用人员对SQLAlchemy 的内存消耗问题不认知、不重视、不处理,最终造成整个系统的大问题,使SQLAlchemy 的性能大打折扣,最终影响了SQLAlchemy的在您手中的可用性。

通过以下解决问题的手法,可以有效控制 SQLAlchemy 的内存消耗,提高应用程序的性能和稳定性。

1. 连接池相关内存消耗

原理

SQLAlchemy 使用连接池来管理数据库连接,连接池会在内存中维护一定数量的数据库连接,以避免频繁创建和销毁连接带来的开销。连接池的大小、超时时间等配置会影响内存消耗。

示例

from sqlalchemy import create_engine

# 创建一个连接池大小为 10 的数据库引擎
engine = create_engine('mysql+pymysql://user:password@host/dbname', pool_size=10)

在这个例子中,连接池会在内存中保留 10 个数据库连接,每个连接会占用一定的内存空间。连接池越大,占用的内存就越多。

应对策略

不要试图对数据库进行长连接,例如:终端程序启动就连接数据库,终端程序退出才关闭连接,这是最不可取的,这会导致大量的数据库长连接。如果您不是使用SQLAlchemy,而是手动管理数据库连接,并进行了长连接,那么系统的噩梦很可能就此开始。

数据库的连接使用应该是:需要数据库操作时连接数据库,数据库操作完毕后就管理闲置的连接。SQLAlchemy可以自动的利用数据库连接池中的空闲连接。根据实际业务需求合理配置连接池大小。如果并发访问量较小,可以适当减小连接池大小;如果并发访问量较大,可以增加连接池大小,但要注意不要过度分配内存。


2. 对象管理导致的内存消耗

原理

当使用 SQLAlchemy 从数据库中查询数据时,会将查询结果映射为 Python 对象。这些对象会在内存中占用一定的空间,尤其是当查询返回大量数据时,内存消耗会显著增加。如果您不小心返回了大量数据(尤其是在处理大数据时),您的这样一次无心之失,足以让整个系统死机。

示例

from sqlalchemy.orm import sessionmaker
from your_model_module import User  # 假设 User 是一个 SQLAlchemy 模型类

Session = sessionmaker(bind=engine)
session = Session()

# 查询所有用户,如果User表的记录条数很多(超过1万条会极度影响性能,超过10万条会迟滞系统,100万条直接死机)
users = session.query(User).all()

应对策略

  • 分批查询:对于大量数据的查询,采用分批查询的方式,每次只查询一部分数据进行处理,处理完后释放相关内存。例如:
batch_size = 100
offset = 0
while True:
    users = session.query(User).limit(batch_size).offset(offset).all()
    if not users:
        break
    # 处理当前批次的用户数据
    for user in users:
        # 处理逻辑
        pass
    offset += batch_size
  • 及时释放对象:在使用完对象后,及时释放对它们的引用,让 Python 的垃圾回收机制能够回收相关内存。例如,在处理完一批数据后,将列表置为 None

3. 查询操作中的内存消耗

原理

复杂的查询操作,尤其是涉及大量数据的连接查询、子查询等,可能会导致 SQLAlchemy 在内存中进行复杂的计算和数据处理,从而增加内存消耗。

示例

from sqlalchemy.orm import joinedload

# 进行一个复杂的连接查询
orders = session.query(Order).options(joinedload(Order.user)).all()

 在这个例子中,使用 joinedload 进行连接查询,SQLAlchemy 会将 Order 对象和关联的 User 对象一次性加载到内存中,但如果 Order 和 User 表的数据量都很大,内存消耗会明显增加(此时要么通过with_entities削减加载的数据量,要么采用流式查询)。

应对策略

  • 优化查询语句:尽量避免不必要的连接和子查询,只查询需要的字段。例如,使用 with_entities 方法指定要查询的字段:
orders = session.query(Order).with_entities(Order.id, Order.amount).all()
  • 使用流式查询:对于大数据量的查询,可以使用流式查询,避免将所有数据一次性加载到内存中。例如:
for order in session.query(Order).yield_per(100):
    # 处理每个订单
    pass

 4. 关联关系处理的内存消耗

原理

在处理对象之间的关联关系时,SQLAlchemy 可能会自动加载关联对象,这会增加内存消耗。例如,当使用 relationship 定义关联关系时,如果没有合理配置加载策略,可能会导致大量关联对象被加载到内存中。

示例

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    orders = relationship("Order")

user = session.query(User).first()
# 访问用户的订单,可能会导致所有订单对象被加载到内存中
for order in user.orders:
    pass

应对策略

  • 合理配置加载策略:使用 joinedloadsubqueryload 等加载策略来控制关联对象的加载时机和方式。例如,使用 joinedload 一次性加载关联对象:
user = session.query(User).options(joinedload(User.orders)).first()
  • 延迟加载对于不需要立即使用的关联对象,可以配置为延迟加载,只有在实际访问时才加载到内存中。例如,在 relationship 中使用 lazy='dynamic'
class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    orders = relationship("Order", lazy='dynamic')

5.分批操作和流式操作

在 SQLAlchemy 里,分批查询和流式查询都是用于处理大量数据查询的技术手段,它们在实现方式、内存使用、性能表现、适用场景等方面存在一定差异。

(1)实现方式

分批查询

分批查询是将大数据集分割成多个较小的数据批次进行查询。一般通过设置 limit 和 offset 参数来实现,每次查询返回固定数量的记录,处理完这批记录后,再调整 offset 值进行下一批次的查询,直至查询完所有数据。

示例代码

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from your_model_module import User  # 假设 User 是模型类

engine = create_engine('sqlite:///:memory:')
Session = sessionmaker(bind=engine)
session = Session()

batch_size = 100
offset = 0
while True:
    users = session.query(User).limit(batch_size).offset(offset).all()
    if not users:
        break
    # 处理当前批次的用户数据
    for user in users:
        print(user)
    offset += batch_size

session.close()

流式查询

流式查询借助 yield_per 方法,以流式方式逐行处理查询结果。数据库游标会逐行从数据库中读取数据,每读取一定数量(yield_per 指定的数量)的记录后就将其返回,而不是一次性把所有数据加载到内存中。

示例代码

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from your_model_module import User  # 假设 User 是模型类

engine = create_engine('sqlite:///:memory:')
Session = sessionmaker(bind=engine)
session = Session()

for user in session.query(User).yield_per(100):
    print(user)

session.close()

 (2)内存使用

分批查询

分批查询每次仅将一批数据加载到内存中,相较于一次性加载全量数据,能显著减少内存占用。不过,每批数据仍需全部加载到内存后再进行处理,若批次大小设置不合理(过大),仍可能导致内存占用过高。

流式查询

流式查询以逐行方式处理数据,每次只将少量数据加载到内存中,内存占用非常低,即使处理超大规模数据集,也能有效避免内存溢出问题。

(3)性能表现

分批查询

分批查询需要多次与数据库交互,每次查询都有一定的开销,如建立连接、执行查询语句等。而且,随着 offset 值的增大,查询效率可能会逐渐降低,因为数据库需要跳过大量记录来定位到指定偏移量的位置。

流式查询

流式查询与数据库保持持续连接,逐行读取数据,减少了多次查询的开销,在处理大数据集时性能优势明显。但流式查询依赖数据库的游标机制,若数据库游标性能不佳,可能会影响整体查询效率。

(4)适用场景

分批查询

  • 适用于需要对每一批数据进行批量处理的场景,例如批量更新、批量插入等操作。
  • 当需要对数据进行分页展示时,分批查询可以方便地实现分页逻辑。

流式查询

  • 适合处理超大规模数据集,尤其是在内存资源有限的情况下,流式查询能有效避免内存问题。
  • 适用于实时数据处理场景,如实时数据分析、日志处理等,可在获取数据的同时立即进行处理,无需等待全量数据加载完成。

分批查询和流式查询各有优劣,在实际应用中,需要根据数据规模、内存资源、处理需求等因素综合考虑,选择合适的查询方式。


6.一次性加载关联 vs 动态加载关联

在 SQLAlchemy 中,joinedload 和 dynamic 是两种不同的关联对象加载策略,它们在资源消耗方面各有特点,具体哪种更省资源取决于具体的使用场景,下面从内存、数据库查询、CPU 等资源消耗维度详细分析。

(1)joinedload 加载策略

原理

joinedload 是一种立即加载策略,它会使用 SQL 的 JOIN 操作在一次数据库查询中同时获取主对象和关联对象的数据。查询结果会被一次性加载到内存中,并且关联对象会被直接关联到主对象上。

示例代码

from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship, joinedload
from sqlalchemy.ext.declarative import declarative_base

# 创建数据库引擎,使用 SQLite 内存数据库
engine = create_engine('sqlite:///:memory:')
# 创建基类
Base = declarative_base()

# 定义 User 类
class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    # 定义与 Order 的关联关系
    orders = relationship("Order")

# 定义 Order 类
class Order(Base):
    __tablename__ = 'orders'
    id = Column(Integer, primary_key=True)
    order_number = Column(String(20))
    user_id = Column(Integer, ForeignKey('users.id'))

# 创建表
Base.metadata.create_all(engine)

# 创建会话
Session = sessionmaker(bind=engine)
session = Session()

# 使用 joinedload 一次性加载用户及其关联的订单
users = session.query(User).options(joinedload(User.orders)).all()
for user in users:
    for order in user.orders:
        print(f"User: {user.name}, Order: {order.order_number}")

session.close()

资源消耗情况

  • 内存消耗:由于 joinedload 会一次性将主对象和关联对象的数据都加载到内存中,如果关联对象数量较多或者数据量较大,会占用较多的内存。例如,一个用户关联了大量的订单记录,使用 joinedload 会将所有订单数据都加载到内存中。
  • 数据库查询消耗:只进行一次数据库查询,减少了与数据库的交互次数,降低了数据库的负载。但是,如果关联表的数据量很大,查询语句可能会变得复杂,导致查询时间增加。
  • CPU 消耗:由于只进行一次查询和数据处理,CPU 在查询和数据转换方面的计算量相对较小。

(2)dynamic 加载策略 

原理

dynamic 是一种延迟加载策略,它返回一个可查询对象(Query 对象),而不是直接加载关联对象。当需要访问关联对象时,会根据具体的查询条件进行按需查询,每次只查询需要的数据。

示例代码

from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base

# 创建数据库引擎,使用 SQLite 内存数据库
engine = create_engine('sqlite:///:memory:')
# 创建基类
Base = declarative_base()

# 定义 User 类
class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    # 定义与 Order 的关联关系,使用 dynamic 加载策略
    orders = relationship("Order", lazy='dynamic')

# 定义 Order 类
class Order(Base):
    __tablename__ = 'orders'
    id = Column(Integer, primary_key=True)
    order_number = Column(String(20))
    user_id = Column(Integer, ForeignKey('users.id'))

# 创建表
Base.metadata.create_all(engine)

# 创建会话
Session = sessionmaker(bind=engine)
session = Session()

# 查询用户
users = session.query(User).all()
for user in users:
    # 按需查询用户的订单
    user_orders = user.orders.filter(Order.order_number.like('123%')).all()
    for order in user_orders:
        print(f"User: {user.name}, Order: {order.order_number}")

session.close()

资源消耗情况

  • 内存消耗:由于是按需查询,只有在实际访问关联对象时才会加载数据到内存中,不会一次性加载大量数据,因此内存消耗相对较低。例如,只需要查看部分订单记录时,不会将所有订单数据都加载到内存中。
  • 数据库查询消耗:可能会进行多次数据库查询,增加了与数据库的交互次数,提高了数据库的负载。但是,每次查询的数据量较小,查询语句相对简单,查询时间可能会较短。
  • CPU 消耗:由于需要多次进行查询和数据处理,CPU 在查询和数据转换方面的计算量相对较大。

(3)对比总结

  • 当关联对象数据量较小且需要一次性访问所有关联对象时joinedload 更省资源。因为它只进行一次数据库查询,减少了数据库的交互次数,虽然会占用一定的内存,但整体的资源消耗相对较低。
  • 当关联对象数据量较大且只需要访问部分关联对象时dynamic 更省资源。它按需查询,避免了一次性加载大量数据到内存中,降低了内存消耗,虽然会增加数据库的交互次数,但每次查询的数据量较小。

选择 joinedload 还是 dynamic 加载策略需要根据具体的业务场景和数据特点来决定,以达到最优的资源利用效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/967636.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IDEA编写SpringBoot项目时使用Lombok报错“找不到符号”的原因和解决

目录 概述|背景 报错解析 解决方法 IDEA配置解决 Pom配置插件解决 概述|背景 报错发生背景:在SpringBoot项目中引入Lombok依赖并使用后出现"找不到符号"的问题。 本文讨论在上述背景下发生的报错原因和解决办法,如果仅为了解决BUG不论原…

使用golang wails写了一个桌面端小工具:WoWEB, 管理本地多前端项目

WoWEB 本地快速启动 http 服务。 辅助管理本地前端项目。 使用界面配置代理转发。 支持平台 windows 10macOS 功能描述 管理本地前端项目启动本地 HTTP 服务,可本地或者局域网访问快速打开项目文件夹配置 HTTP 代理转发规则,方便开发调试 以下情况…

Unity Dots理论学习-5.与ECS相关的概念

DOTS的面向数据编程方式比你在MonoBehaviour项目中常见的面向对象编程方式更适合硬件开发。可以尝试理解一些与数据导向设计(DOD)相关的关键概念,以及这些概念如何影响你的代码,对你在MonoBehaviour项目中的C#编程通常是较少涉及的…

【hive】记一次hiveserver内存溢出排查,线程池未正确关闭导致

一、使用 MemoryAnalyzer软件打开hprof文件 很大有30G,win内存24GB,不用担心可以打开,ma软件能够生成索引文件,逐块分析内存,如下图。 大约需要4小时。 overview中开不到具体信息。 二、使用Leak Suspects功能继续…

(篇三)基于PyDracula搭建一个深度学习的软件之解析yolo算法融合

文章目录 1YoloPredictor类——检测器1.1继承BasePredictor解析1.2继承QObject解析 2MainWindow类——主窗口 在前面两篇中,篇一介绍了启动界面的制作,篇二介绍了如何修改PyDracula的界面,那么这一篇我们学习一下yolo要融合进入软件中&#x…

26~31.ppt

目录 26.北京主要的景点 题目 解析 27.创新产品展示及说明会 题目​ 解析 28.《小企业会计准则》 题目​ 解析 29.学习型社会的学习理念 题目​ 解析 30.小王-产品展示信息 题目​ 解析 31.小王-办公理念-信息工作者的每一天 题目​ 解析 26.北京主要的景点…

Vue.js 状态管理库Pinia

Pinia Pinia :Vue.js 状态管理库Pinia持久化插件-persist Pinia :Vue.js 状态管理库 Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。 要使用Pinia ,先要安装npm install pinia在main.js中导入Pinia 并使用 示例…

day10-字符串

目录 字符串1、API 和 API 帮助文档2、String概述3、String构造方法代码实现 和 内存分析3.1 创建String对象的两种方式3.2 Java的内存模型 4、字符串的比较4.1 号的作用4.2 equals方法的作用 练习5、用户登录6、遍历字符串和统计字符个数7、字符串拼接和翻转8、较难练习-金额转…

从二叉树遍历深入理解BFS和DFS

1. 介绍 1.1 基础 BFS(Breadth-First Search,广度优先搜索)和 DFS(Depth-First Search,深度优先搜索)是两种常见的图和树的遍历算法。 BFS:从根节点(或起始节点)开始&am…

【大数据安全分析】大数据安全分析技术框架与关键技术

在数字化时代,网络安全面临着前所未有的挑战。传统的网络安全防护模式呈现出烟囱式的特点,各个安全防护措施和数据相互孤立,形成了防护孤岛和数据孤岛,难以有效应对日益复杂多变的安全威胁。而大数据分析技术的出现,为…

亚博microros小车-原生ubuntu支持系列 27、手掌控制小车运动

背景知识 本节跟上一个测试类似:亚博microros小车-原生ubuntu支持系列:26手势控制小车基础运动-CSDN博客 都是基于MediaPipe hands做手掌、手指识别的。 为了方便理解,在贴一下手指关键点分布。手掌位置就是靠第9点来识别的。 2、程序说明…

MySQL第五次作业

根据图片内容完成作业 1.建表 (1)建立两个表:goods(商品表)、orders(订单表) mysql> create table goods( -> gid char(8) primary key, -> name varchar(10), -> price decimal(8,2), -> num int); mysql> create t…

Linux:软硬链接和动静态库

hello,各位小伙伴,本篇文章跟大家一起学习《Linux:软硬链接和动静态库》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 ! 如果本篇文章对你有帮助,还请各位点点赞&#xff0…

CSS 组合选择符详解与实战示例

在 Web 开发过程中,CSS 用于定义页面元素的样式,而选择器则帮助我们精确定位需要添加样式的元素。今天我们主要来讲解 CSS 中的组合选择符,它们能够根据 DOM 结构中元素之间的关系来选中目标元素,从而写出结构清晰、易于维护的 CS…

【Linux系统】—— 简易进度条的实现

【Linux系统】—— 简易进度条的实现 1 回车和换行2 缓冲区3 进度条的准备代码4 第一版进度条5 第二版进度条 1 回车和换行 先问大家一个问题:回车换行是什么,或者说回车和换行是同一个概念吗?   可能大家对回车换行有一定的误解&#xff0…

Winform开发框架(蝇量级) MiniFramework V2.1

C/S框架网与2022年发布的一款蝇量级开发框架,适用于开发Windows桌面软件、数据管理应用系统、软件工具等轻量级软件,如:PLC上位机软件、数据采集与分析软件、或企业管理软件,进销存等。适合个人开发者快速搭建软件项目。 适用开发…

win10 llamafactory模型微调相关②

微调 使用微调神器LLaMA-Factory轻松改变大语言模型的自我认知_llamafactory 自我认知-CSDN博客 【大模型微调】使用Llama Factory实现中文llama3微调_哔哩哔哩_bilibili 样本数据集 (数据集管理脚本处需更改,见报错解决参考1) 自我认知微…

AI大模型随机初始化权重并打印网络结构方法(以Deepseekv3为例,单机可跑)

背景 当前大模型的权重加载和调用,主要是通过在HuggingFace官网下载并使用transformer的库来加以实现;其中大模型的权重文件较大(部分>100GB),若只是快速研究网络结构和数据流变化,则无需下载权重。本文…

前端项目打包完成后dist本地起node服务测试运行项目

1、新建文件夹 node-test 将打包dist 文件同步自定义本地服务文件夹node-test 中,安装依赖包。 npm install express serve-static cors 2、新创建服务文件js server.js 构建链接及端口 const express require(express); const path require(path); const co…

《语义捕捉全解析:从“我爱自然语言处理”到嵌入向量的全过程》

首先讲在前面,介绍一些背景 RAG(Retrieval-Augmented Generation,检索增强生成) 是一种结合了信息检索与语言生成模型的技术,通过从外部知识库中检索相关信息,并将其作为提示输入给大型语言模型&#xff…