Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/87.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/database/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql Django中大型表上的内存效率(恒定)和速度优化迭代_Sql_Database_Django - Fatal编程技术网

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

Sql Django中大型表上的内存效率(恒定)和速度优化迭代,sql,database,django,Sql,Database,Django,我有一张很大的桌子。 它目前在一个MySQL数据库中。 我用django 我需要迭代表中的每个元素,以预计算某些特定的数据(如果我更好,我可以做其他事情,但这不是重点) 我希望在不断使用内存的情况下尽可能快地进行迭代 正如已经在和中明确指出的那样,对django中的所有对象进行简单的迭代将杀死机器,因为它将从数据库中检索所有对象 走向解决方案 首先,为了减少内存消耗,您应该确保DEBUG为False(或monkey patch the cursor:),以确保django没有在连接中存储用于DE

我有一张很大的桌子。 它目前在一个MySQL数据库中。 我用django

我需要迭代表中的每个元素,以预计算某些特定的数据(如果我更好,我可以做其他事情,但这不是重点)

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

正如已经在和中明确指出的那样,对django中的所有对象进行简单的迭代将杀死机器,因为它将从数据库中检索所有对象

走向解决方案 首先,为了减少内存消耗,您应该确保DEBUG为False(或monkey patch the cursor:),以确保django没有在
连接中存储用于DEBUG的内容

但即便如此

for model in Model.objects.all()
这是不可能的

即使是稍微改进的形式:

for model in Model.objects.all().iterator()
使用将通过不在内部存储缓存结果(尽管不一定在PostgreSQL上!)来节省一些内存;但显然,它仍将从数据库中检索整个对象

天真的解决办法 方法是根据计数器按
块大小对结果进行切片。有几种编写方法,但基本上都归结为SQL中的
OFFSET+LIMIT
查询

比如:

qs = Model.objects.all()
counter = 0
count = qs.count()
while counter < count:     
    for model in qs[counter:counter+count].iterator()
        yield model
    counter += chunk_size
这已开始令人满意。现在内存=O(C),速度~=O(N)

“更好”解决方案的问题 更好的解决方案只有在QuerySet中有PK时才有效。 不幸的是,情况并非总是如此,尤其是当QuerySet包含distinct(groupby)和/或value(ValueQuerySet)的组合时

在这种情况下,不能使用“更好的解决方案”

我们能做得更好吗? 现在我想知道我们是否可以更快,避免关于没有PK的QuerySet的问题。 可能使用了我在其他答案中找到的东西,但仅限于纯SQL:使用游标

由于我对原始SQL非常不熟悉,特别是在Django中,所以真正的问题来了:

如何为大型表构建更好的Django QuerySet迭代器

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

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

这可以在django中使用原始SQL完成吗?还是应该根据数据库连接器编写特定的python代码

中和中的服务器端游标

那是我目前能做的最远的了

Django
分块迭代器()
当然,最好的方法是将此方法作为
queryset.iterator()
,而不是
iterate(queryset)
,并成为django core的一部分或至少是一个可插入的应用程序

更新感谢评论中的“T”找到了包含一些附加信息的。连接器行为的差异使得最好的解决方案可能是创建一个特定的
分块的
方法,而不是透明地扩展
迭代器
(对我来说这是个不错的方法)。 一个实现存根,但一年内没有任何工作,而且作者似乎还没有准备好开始这项工作

其他参考资料:
  • MySQL中的服务器端游标
  • 编辑: Django 1.6正在添加持久数据库连接

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


    此外,“更好的解决方案”肯定不能在所有情况下都起作用,也不能用作通用方法,只有存根可以根据具体情况进行调整…

    还有另一个选项可用。它不会使迭代更快(事实上,它可能会减慢迭代速度),但会使它使用更少的内存。根据您的需要,这可能是合适的

    large_qs = MyModel.objects.all().values_list("id", flat=True)
    for model_id in large_qs:
        model_object = MyModel.objects.get(id=model_id)
        # do whatever you need to do with the model here
    
    只将ID加载到内存中,并根据需要检索和丢弃对象。注意增加的数据库负载和较慢的运行时间,这两种情况都是为了减少内存使用


    我在工作实例上运行异步计划任务时使用过此方法,对于工作实例,速度是否慢并不重要,但如果它们试图使用太多内存,则可能会使实例崩溃,从而中止进程。

    基本答案是:使用原始SQL和服务器端游标

    遗憾的是,在Django 1.5.2之前,还没有正式的方法来创建服务器端MySQL游标(不确定其他数据库引擎)。所以我写了一些神奇的代码来解决这个问题

    对于Django 1.5.2和MySQLdb 1.2.4,以下代码可以工作。而且,评论很好

    注意:这不是基于公共API的,因此它可能会在未来的Django版本中崩溃

    # This script should be tested under a Django shell, e.g., ./manage.py shell
    
    from types import MethodType
    
    import MySQLdb.cursors
    import MySQLdb.connections
    from django.db import connection
    from django.db.backends.util import CursorDebugWrapper
    
    
    def close_sscursor(self):
        """An instance method which replace close() method of the old cursor.
    
        Closing the server-side cursor with the original close() method will be
        quite slow and memory-intensive if the large result set was not exhausted,
        because fetchall() will be called internally to get the remaining records.
        Notice that the close() method is also called when the cursor is garbage 
        collected.
    
        This method is more efficient on closing the cursor, but if the result set
        is not fully iterated, the next cursor created from the same connection
        won't work properly. You can avoid this by either (1) close the connection 
        before creating a new cursor, (2) iterate the result set before closing 
        the server-side cursor.
        """
        if isinstance(self, CursorDebugWrapper):
            self.cursor.cursor.connection = None
        else:
            # This is for CursorWrapper object
            self.cursor.connection = None
    
    
    def get_sscursor(connection, cursorclass=MySQLdb.cursors.SSCursor):
        """Get a server-side MySQL cursor."""
        if connection.settings_dict['ENGINE'] != 'django.db.backends.mysql':
            raise NotImplementedError('Only MySQL engine is supported')
        cursor = connection.cursor()
        if isinstance(cursor, CursorDebugWrapper):
            # Get the real MySQLdb.connections.Connection object
            conn = cursor.cursor.cursor.connection
            # Replace the internal client-side cursor with a sever-side cursor
            cursor.cursor.cursor = conn.cursor(cursorclass=cursorclass)
        else:
            # This is for CursorWrapper object
            conn = cursor.cursor.connection
            cursor.cursor = conn.cursor(cursorclass=cursorclass)
        # Replace the old close() method
        cursor.close = MethodType(close_sscursor, cursor)
        return cursor
    
    
    # Get the server-side cursor
    cursor = get_sscursor(connection)
    
    # Run a query with a large result set. Notice that the memory consumption is low.
    cursor.execute('SELECT * FROM million_record_table')
    
    # Fetch a single row, fetchmany() rows or iterate it via "for row in cursor:"
    cursor.fetchone()
    
    # You can interrupt the iteration at any time. This calls the new close() method,
    # so no warning is shown.
    cursor.close()
    
    # Connection must be close to let new cursors work properly. see comments of
    # close_sscursor().
    connection.close()
    
    简单回答 如果您只需要迭代表本身而不做任何花哨的事情,Django提供了一个:

    这会导致Django清理自己的缓存以减少内存使用。请注意,对于真正大的表,这可能还不够


    复杂答案 如果要对每个对象执行更复杂的操作,或者有大量数据,则必须编写自己的。下面是一个queryset迭代器,它将queryset分割成块,速度并不比基本迭代器慢多少(它将是一个线性的数据库查询数,而不是1个,但每1000行只有一个查询)。此函数按主键分页,这是高效实现所必需的,因为在大多数SQL数据库中,偏移量是线性时间操作

    def queryset_iterator(queryset, page_size=1000):
        if not queryset:
            return
        max_pk = queryset.order_by("-pk")[0].pk
        # Scale the page size up by the average density of primary keys in the queryset
        adjusted_page_size = int(page_size * max_pk / queryset.count())
        
        pages = int(max_pk / adjusted_page_size) + 1
        for page_num in range(pages):
            lower = page_num * adjusted_page_size
            page = queryset.filter(pk__gte=lower, pk__lt=lower+page_size)
            for obj in page:
                yield obj
    
    使用类似于:

    for obj in queryset_iterator(Model.objects.all()):
        # do stuff
    
    该准则有三个假设:

  • 您的主键是整数(这不起作用)
    def queryset_iterator(queryset, page_size=1000):
        if not queryset:
            return
        max_pk = queryset.order_by("-pk")[0].pk
        # Scale the page size up by the average density of primary keys in the queryset
        adjusted_page_size = int(page_size * max_pk / queryset.count())
        
        pages = int(max_pk / adjusted_page_size) + 1
        for page_num in range(pages):
            lower = page_num * adjusted_page_size
            page = queryset.filter(pk__gte=lower, pk__lt=lower+page_size)
            for obj in page:
                yield obj
    
    for obj in queryset_iterator(Model.objects.all()):
        # do stuff