如果是用的django自带的admin,在后台数据较多的情况下,就会导致sql查询比较慢的情况出现,从而导致页面反应比较慢。我这边是通过框架层面对其进行了一些简单的调优。
这边主要问题就是数据过多的情况,count查询也特别慢
通过show full processlist;查询出慢查询语句
再通过查看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
虽然减少了一个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