Django 中大表的内存效率(恒定)和速度优化迭代

2023-12-31

我有一张非常大的桌子。 它当前位于 MySQL 数据库中。 我用的是Django。

我需要迭代each表的元素来预先计算一些特定的数据(也许如果我更好的话我可以这样做,但这不是重点)。

我希望在不断使用内存的情况下尽可能快地保持迭代。

因为它已经明确地在限制*大型* Django 查询集中的内存使用 https://stackoverflow.com/questions/4856882/limiting-memory-use-in-a-large-django-queryset and 为什么迭代大型 Django QuerySet 会消耗大量内存? https://stackoverflow.com/questions/4222176/why-is-iterating-through-a-large-django-queryset-consuming-massive-amounts-of-me,对 django 中所有对象的简单迭代将杀死机器,因为它将从数据库中检索所有对象。

寻求解决方案

首先,为了减少内存消耗,您应该确保 DEBUG 为 False (或猴子修补光标:关闭 SQL 日志记录,同时保留设置。DEBUG? https://stackoverflow.com/questions/7768027/turn-off-sql-logging-while-keeping-settings-debug)以确保 django 不会将内容存储在connections用于调试。

但即便如此,

for model in Model.objects.all()

是不行的。

即使形式略有改进也不行:

for model in Model.objects.all().iterator()

Using iterator() https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.iterator不会在内部存储缓存的结果,从而节省一些内存(尽管不一定在 PostgreSQL 上!);但显然仍会从数据库中检索整个对象。

一个幼稚的解决方案

The 第一个问题的解决方案 https://stackoverflow.com/a/5188179/422670是根据计数器对结果进行切片chunk_size。有多种写法,但基本上都归结为一个OFFSET + LIMITSQL 中的查询。

就像是:

qs = Model.objects.all()
counter = 0
count = qs.count()
while counter < count:     
    for model in qs[counter:counter+chunk_size].iterator()
        yield model
    counter += chunk_size

虽然这是内存高效的(恒定的内存使用量与chunk_size),就速度而言确实很差:随着 OFFSET 的增长,MySQL 和 PostgreSQL(可能还有大多数数据库)都会开始阻塞并变慢。

更好的解决方案

更好的解决方案可以在这个帖子 http://www.mellowmorning.com/2010/03/03/django-query-set-iterator-for-really-large-querysets/作者:蒂埃里·谢伦巴赫。 它对 PK 进行过滤,这比抵消要快得多(多快可能取决于数据库)

pk = 0
last_pk = qs.order_by('-pk')[0].pk
queryset = qs.order_by('pk')
while pk < last_pk:
    for row in qs.filter(pk__gt=pk)[:chunksize]:
        pk = row.pk
        yield row
    gc.collect()

这开始变得令人满意。现在内存 = O(C),速度 ~= O(N)

“更好”解决方案的问题

仅当 PK 在 QuerySet 中可用时,更好的解决方案才有效。 不幸的是,情况并非总是如此,特别是当 QuerySet 包含不同的 (group_by) 和/或值 (ValueQuerySet) 的组合时。

对于这种情况,不能使用“更好的解决方案”。

我们可以做得更好吗?

现在我想知道我们是否可以走得更快并避免有关没有 PK 的 QuerySet 的问题。 也许使用我在其他答案中找到的东西,但仅限于纯 SQL:使用cursors.

由于我对原始 SQL 很不熟悉,尤其是在 Django 中,所以真正的问题来了:

我们如何为大型表构建更好的 Django QuerySet Iterator

我从读到的内容中得出的结论是,我们应该使用服务器端游标(显然(参见参考资料)使用标准 Django 游标不会达到相同的结果,因为默认情况下 python-MySQL 和 psycopg 连接器都会缓存结果)。

这真的是一个更快(和/或更有效)的解决方案吗?

这可以在 django 中使用原始 SQL 来完成吗?或者我们应该根据数据库连接器编写特定的Python代码?

服务器端游标位于PostgreSQL http://initd.org/psycopg/docs/usage.html#server-side-cursors and in MySQL http://mysql-python.sourceforge.net/MySQLdb.html#using-and-extending

这就是我目前所能得到的......

一个姜戈chunked_iterator()

现在,当然最好让这个方法发挥作用queryset.iterator(), 而不是iterate(queryset),并成为 django 核心的一部分或至少是一个可插入应用程序。

Update感谢评论中的“T”找到了Django 门票 https://code.djangoproject.com/ticket/16614携带一些附加信息。连接器行为的差异使得最好的解决方案可能是创建一个特定的chunked方法而不是透明扩展iterator(对我来说听起来是个好方法)。 实现存根exists https://github.com/akaariai/django-old/commit/8990e20df50ce110fe6ddbbdfed7a98987bb5835,但是已经一年没有任何作品了,而且看起来作者还没有准备好继续做下去。

附加参考资料:

  1. 为什么 MYSQL 较高的 LIMIT 偏移量会减慢查询速度? https://stackoverflow.com/questions/4481388/why-does-mysql-higher-limit-offset-slow-the-query-down
  2. 如何加快 LIMIT 子句中具有较大偏移量的 MySQL 查询速度? https://stackoverflow.com/questions/1243952/how-can-i-speed-up-a-mysql-query-with-a-large-offset-in-the-limit-clause
  3. http://explainextend.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
  4. postgresql:偏移+限制变得非常慢 https://stackoverflow.com/questions/7974155/postgresql-offset-limit-gets-to-be-very-slow
  5. 提高 PostgreSQL 中的 OFFSET 性能 https://stackoverflow.com/questions/6618366/improving-offset-performance-in-postgresql
  6. http://www.depesz.com/2011/05/20/pagination-with-fixed-order/ http://www.depesz.com/2011/05/20/pagination-with-fixed-order/
  7. 如何在Python中获取逐行MySQL结果集 https://stackoverflow.com/questions/337479/how-to-get-a-row-by-row-mysql-resultset-in-pythonMySQL 中的服务器端游标

Edits:

Django 1.6 正在添加持久数据库连接

Django 数据库持久连接 https://docs.djangoproject.com/en/dev/ref/databases/#persistent-connections

在某些情况下,这应该有助于使用游标。但这仍然超出了我目前的技能(以及学习时间)如何实施这样的解决方案。

此外,“更好的解决方案”肯定不适用于所有情况,不能用作通用方法,只能根据情况进行调整的存根......


简答

如果您使用 PostgreSQL 或 Oracle,则可以使用 Django 的内置迭代器 https://docs.djangoproject.com/en/dev/ref/models/querysets/#iterator:

queryset.iterator(chunk_size=1000)

这导致 Django 使用服务器端游标 https://docs.djangoproject.com/en/4.1/ref/models/querysets/#with-server-side-cursors并且在迭代查询集时不缓存模型。从 Django 4.1 开始,这甚至可以与prefetch_related.

对于其他数据库,您可以使用以下内容:

def queryset_iterator(queryset, page_size=1000):
    page = queryset.order_by("pk")[:page_size]
    while page:
        for obj in page:
            yield obj
            pk = obj.pk
        page = queryset.filter(pk__gt=pk).order_by("pk")[:page_size]

如果您想要返回页面而不是单个对象以与其他优化相结合,例如bulk_update, 用这个:

def queryset_to_pages(queryset, page_size=1000):
    page = queryset.order_by("pk")[:page_size]
    while page:
        yield page
        pk = max(obj.pk for obj in page)
        page = queryset.filter(pk__gt=pk).order_by("pk")[:page_size]

PostgreSQL 性能分析

我在 Django 3.2 和 Postgres 13 上对大约 200,000 行的 PostgreSQL 表分析了多种不同的方法。对于每个查询,我将 ids 的总和相加,既确保 Django 实际检索对象,也使我能够验证查询之间迭代的正确性。所有计时都是在对相关表进行多次迭代后进行的,以最大限度地减少后续测试的缓存优势。

基本迭代

基本方法只是迭代表。这种方法的主要问题是所使用的内存量不是恒定的;它随着表的大小而增长,并且我已经看到在较大的表上内存不足。

x = sum(i.id for i in MyModel.objects.all())

挂壁时间:3.53 秒,22MB 内存(BAD)

Django迭代器

Django 迭代器(至少从 Django 3.2 开始)修复了内存问题,并带来了较小的性能提升。据推测,这是因为 Django 管理缓存的时间减少了。

assert sum(i.id for i in MyModel.objects.all().iterator(chunk_size=1000)) == x

挂载时间:3.11 秒,

自定义迭代器

自然的比较点是尝试通过逐渐增加对主键的查询来自己进行分页。虽然这是对简单迭代的改进,因为它具有恒定的内存,但它实际上在速度上输给了 Django 的内置迭代器,因为它进行了更多的数据库查询。

def queryset_iterator(queryset, page_size=1000):
    page = queryset.order_by("pk")[:page_size]
    while page:
        for obj in page:
            yield obj
            pk = obj.pk
        page = queryset.filter(pk__gt=pk).order_by("pk")[:page_size]

assert sum(i.id for i in queryset_iterator(MyModel.objects.all())) == x

挂载时间:3.65 秒,

自定义分页功能

使用自定义迭代的主要原因是您可以在页面中获取结果。此函数对于在仅使用常量内存时插入批量更新非常有用。在我的测试中,它比 queryset_iterator 慢一点,并且我没有一个连贯的理论来解释为什么,但速度减慢并不严重。

def queryset_to_pages(queryset, page_size=1000):
    page = queryset.order_by("pk")[:page_size]
    while page:
        yield page
        pk = max(obj.pk for obj in page)
        page = queryset.filter(pk__gt=pk).order_by("pk")[:page_size]

assert sum(i.id for page in queryset_to_pages(MyModel.objects.all()) for i in page) == x

挂载时间:4.49 秒,

替代自定义分页功能

鉴于 Django 的查询集迭代器比我们自己进行分页更快,因此可以交替实现查询集分页器来使用它。它比我们自己进行分页要快一点,但实现起来比较混乱。可读性很重要,这就是为什么我个人更喜欢前一个分页功能,但如果您的查询集在结果中没有主键(无论出于何种原因),这个功能可能会更好。

def queryset_to_pages2(queryset, page_size=1000):
    page = []
    page_count = 0
    for obj in queryset.iterator():
        page.append(obj)
        page_count += 1
        if page_count == page_size:
            yield page
            page = []
            page_count = 0
    yield page

assert sum(i.id for page in queryset_to_pages2(MyModel.objects.all()) for i in page) == x

挂载时间:4.33 秒,


不良方法

以下是您永远不应该使用的方法(问题中建议了其中许多方法)以及原因。

不要对无序查询集使用切片

无论你做什么,都不要对无序查询集进行切片。这不能正确地迭代表。这样做的原因是切片操作根据您的查询集执行 SQL limit + offset 查询,并且 django 查询集没有顺序保证,除非您使用order_by。此外,PostgreSQL 没有默认的 order by,并且Postgres 文档特别警告不要使用 limit + offset 而不使用 order by https://www.postgresql.org/docs/current/queries-limit.html。因此,每次获取切片时,您都会获得表的不确定性切片,这意味着你的切片可能不重叠 https://dba.stackexchange.com/a/138210并且不会覆盖它们之间表格的所有行。根据我的经验,只有当您在进行迭代时有其他东西正在修改表中的数据时,才会发生这种情况,这只会让这个问题更加严重,因为这意味着如果您单独测试代码,则该错误可能不会出现。

def very_bad_iterator(queryset, page_size=1000):
    counter = 0
    count = queryset.count()
    while counter < count:     
        for model in queryset[counter:counter+page_size].iterator():
            yield model
        counter += page_size

assert sum(i.id for i in very_bad_iterator(MyModel.objects.all())) == x

断言错误;即计算的结果不正确!

一般情况下不要使用切片进行全表迭代

即使我们对查询集进行排序,从性能角度来看,列表切片也是很糟糕的。这是因为 SQL offset 是线性时间操作,这意味着表的 limit + offset 分页迭代将是二次时间,这是您绝对不希望的。

def bad_iterator(queryset, page_size=1000):
    counter = 0
    count = queryset.count()
    while counter < count:     
        for model in queryset.order_by("id")[counter:counter+page_size].iterator():
            yield model
        counter += page_size

assert sum(i.id for i in bad_iterator(MyModel.objects.all())) == x

挂载时间:15 秒(BAD),

不要使用 Django 的分页器进行全表迭代

Django 带有一个内置的分页器 https://docs.djangoproject.com/en/dev/topics/pagination/。人们可能会认为这适合对数据库进行分页迭代,但事实并非如此。 Paginator 的目的是将单页结果返回到 UI 或 API 端点。它比任何迭代表的好方法都要慢得多。

from django.core.paginator import Paginator

def bad_paged_iterator(queryset, page_size=1000):
    p = Paginator(queryset.order_by("pk"), page_size)
    for i in p.page_range:
        yield p.get_page(i)
        
assert sum(i.id for page in bad_paged_iterator(MyModel.objects.all()) for i in page) == x

挂载时间:13.1 秒(BAD),

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Django 中大表的内存效率(恒定)和速度优化迭代 的相关文章

  • 对原始模型进行预过滤后的相关对象的 Django 查询集

    给定一个模型的查询集 我想获取通过外键相关的另一个模型的查询集 采用 Django 项目文档的博客架构 class Blog models Model name models CharField max length 100 tagline
  • Mysql 中 UNION 子句的替代方案

    我有两张桌子 表 a 表 b table a ID 1 2 3 4 5 7 table b ID 2 3 4 5 6 我必须得到这样的输出而无需UNION命令 ID 1 2 3 4 5 6 7 注意 我有一个联合解决方案 select fr
  • 是否有适用于所有数据库的标准sql

    如下所示 不同数据库的语法有所不同 是否存在适用于所有数据库的标准方法 有没有什么工具可以将任意sql转换为任意sql SQL Server 2005 CREATE TABLE Table01 Field01 int primary key
  • 跨多个表进行搜索,并在结果行中显示表名称

    如何构建 SQL 语句以跨多个平面不相关的表运行 并使用选择结果和结果来自的表的名称显示结果 这种情况是这样的 我有几个表 每个表都有相同的列名 这是我从外部各方收到的数据 并将其存储在不同的表中 相同的表看起来像 Table 1 pid
  • 使用 Http Post 发送图像

    我想使用 Http Post 将图像从 android 客户端发送到 Django 服务器 该图像是从图库中选择的 目前 我正在使用列表值名称 Pairs 将必要的数据发送到服务器并接收来自 Django 的 JSON 响应 是否可以对图像
  • 使用 ADODB 连接从关闭的工作簿中检索数据。某些数据被跳过?

    我目前正在编写一些代码 可以通过 ADODB 连接访问单独的工作簿 由于速度的原因 我选择了这种方法而不是其他方法 下面是我的代码 Sub GetWorksheetData strSourceFile As String strSQL As
  • 如何 md5 所有列(无论类型如何)

    我想创建一个 sql 查询 或 plpgsql 它将 md5 所有给定的行 无论类型如何 但是 在下面 如果 1 为空 则哈希为空 UPDATE thetable SET hash md5 accountid accounttype cre
  • 内连接不重复,可以吗?

    鉴于这两个表 表 A1 有两行具有相同的值 a A1 a a 表 A2 有两行主键值为 A B 它们与 a 关联 A2 PK col2 A a B a 我想要的是 A1 和 A2 的连接并得到这个结果 a A a B 显然内连接在这里不起作
  • 为什么我可以像调用实例方法一样调用类方法?

    我正在查看这个例子 class SQLObject def self columns return columns if columns columns DBConnection execute2 lt lt SQL first SELEC
  • django.db.utils.ProgrammingError:关系“django_content_type”不存在

    我有一个项目 我在我的电脑上慢慢建立起来 并且运行良好 我只是想将它放到服务器上 但收到此错误 django db utils ProgrammingError 关系 django content type 确实 不存在 我无法解决这个问题
  • Java JDBC:更改表

    我希望对此表进行以下修改 添加 状态列 varchar 20 日期列 时间戳 我不确定该怎么做 String createTable Create table aircraft aircraftNumber int airLineCompa
  • 使用具有外键的模型序列化器在 Django Rest Framework 中的父表上执行 CRUD

    在我的 API 中 我有两个模型Question and Option如下所示 class Question models Model body models TextField class Options models Model que
  • Google BQ:运行参数化查询,其中参数变量是 BQ 表目标

    我正在尝试从 Linux 命令行为 BQ 表目标运行 SQL 此 SQL 脚本将用于多个日期 客户端和 BQ 表目标 因此这需要在我的 BQ API 命令行调用中使用参数 标志 parameter 现在 我已经点击此链接来了解参数化查询 h
  • 在 postgres 查询中使用列表

    我有一个动态列表 list a b c d 所以长度可能会改变 我想在查询中比较这些列表值 select from student where name in all the list values 我想将列表值传递到此查询中 我怎样才能做
  • 如何使用 LAMBDA 表达式在 LINQ 中执行 IN 或 CONTAINS?

    我有以下 Transact Sql 我正在尝试将其转换为 LINQ 并且很挣扎 SELECT FROM Project WHERE Project ProjectId IN SELECT ProjectId FROM ProjectMemb
  • SQL不允许表中有重复记录

    如何使其不添加重复项 我想让它通过 ID 之外的所有其他列进行检查 我希望这个无效 ID col1 col2 col3 1 first middle last ID col1 col2 col3 2 first middle last 我希
  • SQL - != 'NULL' 的解释

    我的SSMS代码如下 Select top 50 From FilteredContact Where statuscode 1 and emailaddress1 NULL and telephone1 NULL and address1
  • 如何获得顶部带有千位分隔符的数字?

    SELECT count FROM table A 假设结果是8689 我怎样才能将它转换为8 689在 SQL Server 上 尝试这样 select replace convert varchar convert Money coun
  • SQL Server 上的语法错误

    这可能是一个愚蠢的语法错误 但我只是继续阅读我的程序 但我无法弄清楚我的错误在哪里 消息 156 第 15 级 状态 1 第 41 行关键字附近的语法不正确 为了 这是我的代码 alter procedure LockReservation
  • 发送用户注册密码,django-allauth

    我在 django 应用程序上使用 django alluth 进行身份验证 注册 我需要创建一个自定义注册表单 其中只有一个字段 电子邮件 密码将在服务器上生成 这是我创建的表格 from django import forms from

随机推荐

  • 无法解析方法 getMap()

    我试图让地图片段在我的应用程序中工作 但在尝试获取 GoogleMap 对象时仍然出现错误 FragmentWithMap java import android Manifest import android app Activity i
  • string::size_type 而不是 int

    const std string size type cols greeting size pad 2 2 Why string size type int应该可以工作 它包含数字 空头也能容纳数字 与签名字符一样 但这些类型都不能保证足够
  • 当委托构造函数抛出异常时,内存是否会自动回收?

    从此 当委托构造函数抛出异常时 析构函数是否被调用 https stackoverflow com q 17657761 14065 class X public X X int X throw std exception X double
  • 将 GitHub 文件(和更新)获取到 Ubuntu Web 服务器上

    我正在设置一个多用户 多服务器环境 所有开发人员都将使用 Git 并从 GitHub 等克隆各种存储库 在我控制的一个帐户中 现在 我如何将文件从 GitHub 获取到服务器 大约 5 个 首先 我正在考虑某种自动化方式将更新从 GutHu
  • SSRS 报告查看器 + ASP.NET 凭据 401 异常

    我在 SQL2005 报告服务器上保存了一份报告 我想返回该报告的渲染 PDF 我在使用本地 rdlc 文件时发现了这一点 我已经在博客上介绍过它 http www jarrettmeyer com 2009 09 reports in a
  • Jest - “child_process”包中的模拟函数

    我正在编写单元测试 并模拟包 child process 中的 exec 方法 mocks child process js const child process jest genMockFromModule child process
  • C 将内存部分移动到位

    我正在实现几个数据结构 我想要使用的一个原语如下 我有一个内存块 A N 它的长度可变 但我在示例中使用 100 在这个块内 有一个较小的部分C 长度为 K 假设为 30 我想在不使用任何额外内存的情况下移动它 额外的困难是 A 换行 即C
  • 打印地图中包含的集合的内容

    我正在编写一个程序 从文件中读取团队名称并将其分组 每组尺寸为 4 我使用的是 map
  • Eclipse 错误:解析 ...\android-22\android-wear\armeabi-v7a\devices.xml 时出错

    最近 我将 android SDK 升级为Android M API 22 MNC 预览版 之后每个项目打开Eclipse都报错 的错误为logcat and of 弹出窗口 says 错误 解析错误 sdk system images a
  • TaskScheduler.UnobservedTaskException 事件处理程序从未被触发

    我正在阅读一本有关 C 任务并行库的书 并有以下示例 但 TaskScheduler UnobservedTaskException 处理程序从未被触发 谁能给我任何线索来解释为什么 TaskScheduler UnobservedTask
  • 如何在 iOS 上启用新的 Objective-C 对象文字?

    当我使用 Xcode 4 4 创建一个新项目并添加以下行时 NSDictionary test key test value NSString value test key NSLog value is value 它编译时没有警告并按预期
  • jQuery 快速问题:停止事件传播?

    我有一组响应 mouseUp 事件的元素 其中的子元素也响应 mouseUp 事件 全部通过 jQuery 当用户鼠标单击发生子级的 mouseUp 事件时 如何才能使子级的父级 mouseUp 事件在 jQuery 中也不会发生 你要ev
  • 如何打印类型向量 > 来屏幕 C++?

    我有一个返回值向量 gt 的方法 但我不知道如何打印该向量的内容 我试图循环浏览内容 但出现编译器错误 这是我尝试过的一个例子 vector
  • SwiftUI 中根据文本高度自动调整视图高度

    我正在尝试在 SwiftUI 中创建一个视图 其中左侧图像的背景应根据右侧文本的高度垂直缩放 我尝试了很多不同的方法 从GeometryReader to layoutPriority 但我还没有设法让它们中的任何一个工作 当前状态 期望的
  • 向服务器发送 http 请求而不期待响应

    我需要向服务器发送 POST http 请求 但它不应该期待响应 我应该使用什么方法呢 我一直在使用 WebRequest request2 WebRequest Create http local ape project org 6969
  • 如何显示事务隔离级别(MySQL)

    我想知道当前Mysql数据库设置的隔离级别是什么 怎么才能查出来呢 我尝试在谷歌上搜索但没有找到 检查会话事务级别 mysql8 SELECT transaction ISOLATION 检查全局事务级别 mysql8 SELECT glo
  • IO 绑定操作的并行执行

    我已从头到尾阅读了 TPL 和任务库文档 但是 我仍然无法非常清楚地理解以下案例 现在我需要实现它 我将简化我的情况 我有一个IEnumerable
  • Java - 为什么将数组声明为接口类型?

    这是马克 韦斯教授在他的书中说的Java数据结构与算法分析 public class BinaryHeap
  • 如何使用pymssql获取sql打印消息

    我正在运行一些查询 从其执行中打印运行时统计信息 这是通过完成的print message 在sql脚本中使用 我希望在通过 pymssql 调用过程 脚本时看到这些消息 conn pymssql connect server user p
  • Django 中大表的内存效率(恒定)和速度优化迭代

    我有一张非常大的桌子 它当前位于 MySQL 数据库中 我用的是Django 我需要迭代each表的元素来预先计算一些特定的数据 也许如果我更好的话我可以这样做 但这不是重点 我希望在不断使用内存的情况下尽可能快地保持迭代 因为它已经明确地