Django:执行许多查询的脚本从管理员视图执行时比从 shell 执行时运行速度要慢得多
Django: Script that executes many queries runs massively slower when executed from Admin view than when executed from shell
我有一个脚本循环遍历外部 csv 文件的行(大约 12,000 行)并执行单个 Model.objects.get() 查询以从数据库中检索每个项目(最终产品会更多很复杂,但现在它被精简为可能尝试解决这个问题的最简单的功能。
目前,本地 csv 文件的路径已硬编码到脚本中。当我 运行 脚本通过 shell 使用 py manage.py runscript update_products_from_csv
它 运行 大约需要 6 秒。
最终目标是能够通过管理员上传 csv,然后从那里获得脚本 运行。我已经能够做到这一点,但是 运行我这样做的时间大约需要 160 秒。管理员中的视图看起来像...
from .scripts import update_products_from_csv
class CsvUploadForm(forms.Form):
csv_file = forms.FileField(label='Upload CSV')
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
# list_display, list_filter, fieldsets, etc
def changelist_view(self, request, extra_context=None):
extra_context = extra_context or {}
extra_context['csv_upload_form'] = CsvUploadForm()
return super().changelist_view(request, extra_context=extra_context)
def get_urls(self):
urls = super().get_urls()
new_urls = [path('upload-csv/', self.upload_csv),]
return new_urls + urls
def upload_csv(self, request):
if request.method == 'POST':
# csv_file = request.FILES['csv_file'].file
# result_string = update_products_from_csv.run(csv_file)
# I commented out the above two lines and added the below line to rule out
# the possibility that the csv upload itself was the problem. Whether I execute
# the script using the uploaded file or let it use the hardcoded local path,
# the results are the same. It works, but takes more than 20 times longer
# than executing the same script from the shell.
result_string = update_products_from_csv.run()
print(result_string)
messages.success(request, result_string)
return HttpResponseRedirect(reverse('admin:products_product_changelist'))
现在脚本的实际 运行ning 部分就这么简单...
import csv
from time import time
from apps.products.models import Product
CSV_PATH = 'path/to/local/csv_file.csv'
def run():
csv_data = get_csv_data()
update_data = build_update_data(csv_data)
update_handler(update_data)
return 'Finished'
def get_csv_data():
with open(CSV_PATH, 'r') as f:
return [d for d in csv.DictReader(f)]
def build_update_data(csv_data):
update_data = []
# Code that loops through csv data, applies some custom logic, and builds a list of
# dicts with the data cleaned and formatted as needed
return update_data
def update_handler(update_data):
query_times = []
for upd in update_data:
iter_start = time()
product_obj = Product.objects.get(external_id=upd['external_id'])
# external_id is not the primary key but is an indexed field in the Product model
query_times.append(time() - iter_start)
# Code to export query_times to an external file for analysis
update_handler()
有一堆其他代码检查字段值以查看是否需要更改任何内容,并在不存在匹配项时构建对象,但现在都被注释掉了。如您所见,我还为每个查询计时并记录这些值。 (我一整天都在不同的地方放弃 time()
调用,并确定查询是唯一明显不同的部分。)
当我从 shell 中 运行 时,平均查询时间为 0.0005 秒,所有查询时间的总和每次大约为 6.8 秒。
当我通过管理视图 运行 然后检查 Django 调试工具栏中的查询时,它按预期显示了 12,000 多个查询,并且显示总查询时间仅为大约 3900 毫秒。但是当我查看 time()
调用收集的查询时间日志时,平均查询时间为 0.013 秒(比我通过 运行 通过 shell 查询时间长 26 倍),并且所有查询时间的总和总是在 156-157 秒之间。
当我通过管理员 运行 时,Django 调试工具栏中的查询看起来都像 SELECT ••• FROM "products_product" WHERE "products_product"."external_id" = 10 LIMIT 21
,根据工具栏,它们大多都是 0-1 毫秒。我不确定当 运行 从 shell 查询时如何检查查询的样子,但我无法想象它们会有所不同?我在 django-extensions 运行script 文档中找不到任何关于它进行查询优化或类似的东西。
另一个有趣的方面是,当 运行 从管理员那里获取它时,从我在终端中看到 result_string
打印时起,又过了 1-3 分钟才会出现成功消息在浏览器中 window.
我不知道还要检查什么。我显然缺少一些基本的东西,但我不知道是什么。
Reddit 上有人建议 运行从 shell 中调用脚本可能会自动启动一个新线程,其中的逻辑可以 运行 不受其他 Django 服务器进程的阻碍,这似乎就是答案。如果我从管理员视图 运行 新线程中的脚本,它 运行 与我从 shell.[=10 运行 它时一样快=]
我有一个脚本循环遍历外部 csv 文件的行(大约 12,000 行)并执行单个 Model.objects.get() 查询以从数据库中检索每个项目(最终产品会更多很复杂,但现在它被精简为可能尝试解决这个问题的最简单的功能。
目前,本地 csv 文件的路径已硬编码到脚本中。当我 运行 脚本通过 shell 使用 py manage.py runscript update_products_from_csv
它 运行 大约需要 6 秒。
最终目标是能够通过管理员上传 csv,然后从那里获得脚本 运行。我已经能够做到这一点,但是 运行我这样做的时间大约需要 160 秒。管理员中的视图看起来像...
from .scripts import update_products_from_csv
class CsvUploadForm(forms.Form):
csv_file = forms.FileField(label='Upload CSV')
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
# list_display, list_filter, fieldsets, etc
def changelist_view(self, request, extra_context=None):
extra_context = extra_context or {}
extra_context['csv_upload_form'] = CsvUploadForm()
return super().changelist_view(request, extra_context=extra_context)
def get_urls(self):
urls = super().get_urls()
new_urls = [path('upload-csv/', self.upload_csv),]
return new_urls + urls
def upload_csv(self, request):
if request.method == 'POST':
# csv_file = request.FILES['csv_file'].file
# result_string = update_products_from_csv.run(csv_file)
# I commented out the above two lines and added the below line to rule out
# the possibility that the csv upload itself was the problem. Whether I execute
# the script using the uploaded file or let it use the hardcoded local path,
# the results are the same. It works, but takes more than 20 times longer
# than executing the same script from the shell.
result_string = update_products_from_csv.run()
print(result_string)
messages.success(request, result_string)
return HttpResponseRedirect(reverse('admin:products_product_changelist'))
现在脚本的实际 运行ning 部分就这么简单...
import csv
from time import time
from apps.products.models import Product
CSV_PATH = 'path/to/local/csv_file.csv'
def run():
csv_data = get_csv_data()
update_data = build_update_data(csv_data)
update_handler(update_data)
return 'Finished'
def get_csv_data():
with open(CSV_PATH, 'r') as f:
return [d for d in csv.DictReader(f)]
def build_update_data(csv_data):
update_data = []
# Code that loops through csv data, applies some custom logic, and builds a list of
# dicts with the data cleaned and formatted as needed
return update_data
def update_handler(update_data):
query_times = []
for upd in update_data:
iter_start = time()
product_obj = Product.objects.get(external_id=upd['external_id'])
# external_id is not the primary key but is an indexed field in the Product model
query_times.append(time() - iter_start)
# Code to export query_times to an external file for analysis
update_handler()
有一堆其他代码检查字段值以查看是否需要更改任何内容,并在不存在匹配项时构建对象,但现在都被注释掉了。如您所见,我还为每个查询计时并记录这些值。 (我一整天都在不同的地方放弃 time()
调用,并确定查询是唯一明显不同的部分。)
当我从 shell 中 运行 时,平均查询时间为 0.0005 秒,所有查询时间的总和每次大约为 6.8 秒。
当我通过管理视图 运行 然后检查 Django 调试工具栏中的查询时,它按预期显示了 12,000 多个查询,并且显示总查询时间仅为大约 3900 毫秒。但是当我查看 time()
调用收集的查询时间日志时,平均查询时间为 0.013 秒(比我通过 运行 通过 shell 查询时间长 26 倍),并且所有查询时间的总和总是在 156-157 秒之间。
当我通过管理员 运行 时,Django 调试工具栏中的查询看起来都像 SELECT ••• FROM "products_product" WHERE "products_product"."external_id" = 10 LIMIT 21
,根据工具栏,它们大多都是 0-1 毫秒。我不确定当 运行 从 shell 查询时如何检查查询的样子,但我无法想象它们会有所不同?我在 django-extensions 运行script 文档中找不到任何关于它进行查询优化或类似的东西。
另一个有趣的方面是,当 运行 从管理员那里获取它时,从我在终端中看到 result_string
打印时起,又过了 1-3 分钟才会出现成功消息在浏览器中 window.
我不知道还要检查什么。我显然缺少一些基本的东西,但我不知道是什么。
Reddit 上有人建议 运行从 shell 中调用脚本可能会自动启动一个新线程,其中的逻辑可以 运行 不受其他 Django 服务器进程的阻碍,这似乎就是答案。如果我从管理员视图 运行 新线程中的脚本,它 运行 与我从 shell.[=10 运行 它时一样快=]