django后台数据数量过多导致查询速度过慢

django后台数据数量过多导致查询速度过慢

    • 背景
    • 问题解决
      • 问题1:count 数据过多
      • 问题2:如何从框架层面优化count速度

背景

如果是用的django自带的admin,在后台数据较多的情况下,就会导致sql查询比较慢的情况出现,从而导致页面反应比较慢。我这边是通过框架层面对其进行了一些简单的调优。
django后台数据数量过多导致查询速度过慢_第1张图片

问题解决

问题1:count 数据过多

这边主要问题就是数据过多的情况,count查询也特别慢
通过show full processlist;查询出慢查询语句
查询sql语句执行情况
再通过查看django admin代码查看是哪些地方导致了count的语句多次执行

	# 源码路径:Python35\Lib\site-packages\django\contrib\admin\options.py
    def get_urls(self):
		...
        urlpatterns = [
        	# 这边是django admin的路由列表,changeList页面就是通过changelist_view实现的
            url(r'^$', wrap(self.changelist_view), name='%s_%s_changelist' % info),
            ...
        ]
        return urlpatterns
    ......
    @csrf_protect_m
    def changelist_view(self, request, extra_context=None):
    	...
    	ChangeList = self.get_changelist(request) # 获取changelist的数据
    	...
   	······
    def get_changelist(self, request, **kwargs):
	    """
	    Returns the ChangeList class for use on the changelist page.
	    """
	    # 再跳转到views.main中查看具体实现
	    from django.contrib.admin.views.main import ChangeList
	    return ChangeList
	# 源码路径:Python35\Lib\site-packages\django\contrib\admin\views\main.py
	class ChangeList(object):
    def __init__(self, request, model, list_display, list_display_links,
                 list_filter, date_hierarchy, search_fields, list_select_related,
                 list_per_page, list_max_show_all, list_editable, model_admin):
    	···
    	self.query = request.GET.get(SEARCH_VAR, '')
        self.queryset = self.get_queryset(request)
        self.get_results(request)# 通过先获取queryset再将queryset转为实际数据
        ···
    def get_results(self, request):
        paginator = self.model_admin.get_paginator(request, self.queryset, self.list_per_page)
        # Get the number of objects, with admin filters applied.
        result_count = paginator.count # 这里是 count1 优化点

        # Get the total number of objects, with no admin filters applied.
        if self.model_admin.show_full_result_count:
            full_result_count = self.root_queryset.count() # 这里是 count2 优化点
        else:
            full_result_count = None
        ···

因此可以知道实际上count被调用了两次,count2修改主要就是使django后台不显示全部结果数
这边可以在对应的 ModelAdmin 添加如下配置,不进行count2的查询

show_full_result_count = False

问题2:如何从框架层面优化count速度

虽然减少了一个count,但是另外一个count的查询速度还是比较慢
在这里插入图片描述
这边在网上看了其他的解决方案,也会导致结果不精确或者效率存在问题。
例如:

# 重写 ModelAdmin 的 paginator
# 方案一 写死一个值 可能就会导致数据不够精确
def count(self):
    return 100000000
# 方案二 定时缓存值 这里其实数据也还是不够精确 而且也还是会出现偶发性的页面响应比较慢
@cached_property
def count(self):
	key = "count_cache_key"
	count = cache.get(key)
	if not count:
		count = super().count # 这里调用父类的count方法 真实查询数据库获得一个精确的值
		cache.set(key, count, 30 * 60) # 每三小时刷一次
	return count

其实主要就是需要解决,不通过数据库层面 或者 尽可能少的通过数据库层面 进行查询(速度慢)而获取到数据。并且能够通过用户执行 增加删除 操作时实时修改这个总量值。

这里其实就可以用到django自带的 信号机制(signals)。(其实在haystack包中也通过了django的信号机制来监听model的 增加或者修改删除操作 从而进行搜索索引的 增加或者修改删除操作)

# 方案3 通过django的signals 获取用户的增加删除操作。并对"总量"进行自增和自减操作
class TableRowNum(models.Model):
    """记录表的行数"""
    app_label = models.CharField(max_length=100, verbose_name=u'app_label', blank=True, default="")
    model = models.CharField(max_length=100, verbose_name=u'model', blank=True, default="")
    row_nums = models.IntegerField(verbose_name=u'行数', default=0)

    class Meta:
        db_table = "t_table_row_num"
        verbose_name = u"表行数"
        verbose_name_plural = verbose_name


def handle_save(sender, instance, **kwargs):
    app_label = instance._meta.app_label
    object_name = instance._meta.object_name
    if kwargs["created"]: # 判断save()操作是执行的update还是create
        is_exist = TableRowNum.objects.filter(app_label=app_label, model=object_name).exists()
        if is_exist: # 若存在则往记录行数表中的对应数据进行自增
            TableRowNum.objects.filter(app_label=app_label, model=object_name).update(
                row_nums=F("row_nums")+1
            )
        else: # 若不存在则对 对应表 进行首次查询(这里只会查询一次)
            from django.apps import apps
            m = apps.get_model(app_label, model_name=object_name)
            row_nums = m.objects.count()
            TableRowNum(
                app_label=app_label,
                model=object_name,
                row_nums=row_nums
            ).save()

def handle_delete(sender, instance, **kwargs):
    app_label = instance._meta.app_label
    object_name = instance._meta.object_name
    print("{0}.{1}".format(app_label, object_name))
    is_exist = TableRowNum.objects.filter(app_label=app_label, model=object_name).exists()
    if is_exist:
        TableRowNum.objects.filter(app_label=app_label, model=object_name).update(
            row_nums=F("row_nums") - 1
        )
    else:
        from django.apps import apps
        m = apps.get_model(app_label, model_name=object_name)
        row_nums = m.objects.count()
        TableRowNum(
            app_label=app_label,
            model=object_name,
            row_nums=row_nums
        ).save()


models.signals.post_save.connect(handle_save, sender=MyModel)
models.signals.post_delete.connect(handle_delete, sender=MyModel)

···
# 再重写Paginator
class MyPaginator(Paginator):

    @cached_property
    def count(self):
        table_row_num = TableRowNum.objects.filter(app_label="myapp", model="mymodel").first()
        if table_row_num:
            if table_row_num.row_nums > 0:
                return table_row_num.row_nums
        return super().count

这样就可以保证只查询一次数据表的"总量",而后每次增加删除都会进行相应的自增自减。能够减少数据库查询,并且获得非常精确的"总量"数据。
最后,还需要在查询时,获取实时的count数据。可以在对应的 ModelAdmin 添加如下配置

class MyAdmin(admin.ModelAdmin):
    ...
    paginator = MyPaginator
    ...
    def get_paginator(self, request, queryset, per_page, orphans=0, allow_empty_first_page=True):
        # 这边传入request数据,用于查看用户请求是否含有查询参数
        return self.paginator(queryset, per_page, orphans, allow_empty_first_page, request)

...
class MyPaginator(Paginator):

    def __init__(self, object_list, per_page, orphans=0,
                 allow_empty_first_page=True, request=None):
        self.request = request
        super().__init__(object_list, per_page, orphans=orphans, allow_empty_first_page=allow_empty_first_page)

    @cached_property
    def count(self):
        table_row_num = TableRowNum.objects.filter(app_label="myapp", model="mymodel").first()
        # 如果用户请求有查询参数则实时查询
        if table_row_num and not any([v for v in self.request.GET.values()]):
            if table_row_num.row_nums > 0:
                return table_row_num.row_nums
        return super().count

你可能感兴趣的:(blog,python,django,mysql)