不要让你的应用程序崩溃:通过批量从数据库中加载数据以提高性能

原文:towardsdatascience.com/dont-crash-your-app-load-records-from-the-databse-in-batches-for-better-performance-ab09f3598d96

本文旨在优化您的 Python 应用程序与数据库之间的通信,以确保您的应用程序运行顺畅,数据库服务器不会过载。本文讨论了一个常见的低效习惯:一次性从查询中加载所有数据

面对返回大量记录的查询时,通常不切实际,甚至不可能加载所有返回的记录。而不是在内存中加载所有结果并逐行处理,本文将介绍如何加载多个小块。而不是加载 100 万条记录并处理,我们将加载 400 批,每批 2500 条记录!这样,您的应用程序不需要在内存中加载所有结果,这具有明显的优势:

  • 增强内存使用

  • 更好的感知响应时间

  • 减少数据库压力

我们还将深入了解技术细节,展示这个技术是如何在幕后工作的。让我们开始编码!


为什么使用 fetchmany

SQLAlchemy(或其他库中的类似方法)的fetchmany方法允许您分批检索相同的结果。在许多情况下,这种方法可能比一次性检索所有行(fetchall)或逐行检索(fetchone)更好。在某些情况下,检索多个小批量可能比检索所有记录更合适,原因如下:

1. 内存效率 在许多情况下,通过数据集进行批处理要内存效率更高,因为它可以防止您需要将所有数据加载到内存中。当您的数据集太大而无法适应内存时,这一点至关重要。

2. 感知响应时间 批处理可以带来更好的感知响应时间,因为我们可以在等待一小段时间后开始处理第一批数据,而不是等待整个结果集被检索。

这可以在需要响应性以保持流畅用户体验的用户界面应用程序中带来更好的感知响应时间。例如,考虑先加载前 25 篇文章,然后在页面滚动到底部时加载下一批数据。

SQL 中的 UNION 和 JOIN 有什么区别?

3. 减少数据库负载 以批量的方式检索数据有助于在时间上分散数据库的负载。这种分阶段的方法减少了数据库的负载。此外,它还有助于保持数据库服务器更好的整体性能。检索大量数据会对服务器造成重大负载,可能会影响使用相同数据库的其他应用程序的性能。

4. 更高效地使用数据库连接在处理大量结果集时保持数据库连接打开可能效率低下,并可能限制应用程序的可扩展性。通过分批获取和处理数据,您可以减少每个连接需要保持打开的时间,从而更有效地使用可用的数据库连接。这种方法允许数据库更有效地管理并发连接。

SQL – 将数据删除到另一个表


类比

让我们用一个类比来思考:想象一辆装满砖块的卡车到达建筑工地。我们有几个砖匠需要砖块来建造墙壁。与其试图从卡车将一整托盘的砖块运送到建筑工地,不如几次往返用装满砖块的手推车。这样做有几个原因是有用的:

  • 自己搬运整个托盘将花费非常长的时间,因为。此外,砖匠们没有足够的空间来储存所有的砖块。

  • 你在交付第一车砖块后,砖匠们就可以开始工作了。与此同时,你可以往返于第二批次。

  • 卡车上的工作要少得多;你只需在装满手推车的时候短暂地阻塞入口。这样其他工人就可以为其他砖匠团队取砖。

Python args, kwargs, 和传递参数到函数的所有其他方式


实际示例

为了演示fetchmany的工作原理,解决一个非常简单的问题。让我们想象我们是一家网店,我们想要给所有客户发送个性化的电子邮件。

为了这个目的,我们有一个包含200 万客户(我们的商店非常成功)的表格。这个表格叫做clients,看起来是这样的:

| id | name  | email                    |
| -- | ----- | -------------------------|
| 1  | oscar | [[email protected]](/cdn-cgi/l/email-protection)   |
| 2  | bert  | [[email protected]](/cdn-cgi/l/email-protection)    |
| 3  | ernie | [[email protected]](/cdn-cgi/l/email-protection)  |

Python: init 不是构造函数:Python 对象创建的深入探讨


直接方法

最直接的方法就是查询所有记录并将它们全部发送到发送个性化客户电子邮件的函数:

stmt_select = "SELECT * FROM clients" 
with session_maker() as con:
    all_records = con.execute(stmt_select).fetchall()
 # Process the found records
    found_clients = [r.Client for r in all_records]   
    send_email_to_clients(list_of_clients=found_clients)

这种方法的优点是相当简单。缺点也很明显:我们必须将 200 万客户加载到内存中,并传递给send_email_to_clients函数。


使用 fetchmany

让我们善待我们的应用程序和数据库,并检索数据块。我们可以使用一个名为fetchmany的 sqlalchemy 方法来完成这项工作。这看起来是这样的:

stmt_select = "SELECT * FROM clients"
with session_maker() as con:
    result = con.execute(stmt_select)
    while found_rows := result.fetchmany(size=1000):
        # Process the found records
        found_clients = [r.Client for r in found_rows]
        send_email_to_clients(list_of_clients=found_clients)

这是一个相当简单的更改;我们只需将result.fecthall()替换为result.fatchmany()。此方法返回行序列,直到没有更多行,此时它返回一个空序列。

接下来,我们使用罕见的 walrus 操作符 将行存储在一个名为 found_rows 的变量中,直到没有更多行。这是一个由 size 参数指定的 1000 条记录的批次。我们可以像上一部分一样使用这个批次,并将批次发送到 send_email_to_clients 函数。

使用 Docker 和 Compose 完整指南


内部工作原理:fetchmany 的工作方式

让我们再次查看查询,看看它由两部分组成:执行查询获取结果

result = con.execute(stmt_select)                          # <-- execute
    while found_rows := result.fetchmany(size=1000):       # <-- fetch
        print(f"Found a batch with {len(found_rows)} rows")

让我们分解一下:

1. 执行查询

con.execute 方法将 SQL(在 stmt_select 中定义)发送到数据库,执行它并准备一个结果集。此时,所有与查询匹配的数据都已识别,但尚未必然被传输。

2. 获取结果

获取意味着实际上将数据从数据库传输到我们的 Python 应用程序。这是 result 方法发挥作用的地方:

  • result.fetchall() 将数据库中的所有记录移动到我们的应用中

  • result.fetchmany() 以批量方式获取结果

明显的优势是,我们不需要使用后一种方法发送所有记录。当结果集太大而无法放入我们的 Python 应用程序内存中时,这是必不可少的。传输较小的对象更快,并且处理所需的内存更少。

在两行代码中应用 Python 多进程


实现考虑

当使用 fetchmany 时,选择合适的批量大小非常重要。批量大小过小可能导致由于多次小批量获取的开销而效率低下,而批量大小过大可能会抵消使用 fetchmany 的初衷带来的好处。最佳批量大小取决于具体的应用和正在获取的行的大小。

使用 SQLAlchemy 最简单的方式 UPSERT


用例

许多用例都与在不烧毁机器的情况下保持高性能有关。我们通过在时间上分散工作来有效地管理资源利用率。这个原则有多个用例:


用例 1:批量数据迁移/处理

在在数据库之间迁移大量数据时,可以使用 fetchmany 以批量方式获取和插入数据。这降低了压倒源数据库或目标数据库的风险,并允许跟踪进度,如果迁移被中断,还可以恢复迁移。

同样,你可以使用 fetchmany数据库到文件 流式传输数据,而不会遇到内存限制。在机器学习工作流程中,fetchmany 可以用来 批量加载数据。这种方法可以帮助优化训练过程,尤其是在处理大型数据集时。

用例 2:网页分页

对于显示大量数据库记录的 Web 应用程序(如搜索结果或日志条目),fetchmany 可以用来实现服务器端分页。与其让网页等待加载和发送整个数据集的 API,不如只获取当前页面所需的记录子集,这可以提高应用程序的响应时间。

用例 3:数据源和集成

以可管理的块发送数据对于有速率限制的系统或网络带宽是关注点的系统来说很有用。

Python 中处理相对路径的简单技巧


结论

在这篇文章中,我希望展示你可以轻松升级你的数据库连接的行为,以便批量检索结果。这种方法不仅对你的应用程序来说更容易管理,使用更少的内存,用户也会觉得它更快,因为他们只需要等待第一个、小批量数据,然后才能开始处理。

我希望这篇文章和我希望的一样清晰,但如果不是这样,请告诉我我可以做什么来进一步澄清。同时,查看我关于各种编程相关主题的 其他文章,例如这些:

编程愉快!

— Mike

P.S:喜欢我在做的事情?关注我!

Mike Huls – Medium

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值