این یک مثال واقعی از بهبود یک پروژه است. query اولیه به شکل زیر بود:
1 2 3 4 5 6 7 |
bss_branch = Branch.objects.get(name__iexact="bss").office_set.all() for el in bss_branch: for svc in el.services.all(): if svc in my_services: setattr(el, 'is_activated', True) break |
با ابزار django debug toolbar تعداد query های این تکه کد را مرور کردم و جاهایی که به سرور query زده میشود به این شکل است:
- در خط Branch.objects.get(name__iexact=”bss”).office_set.all() یک query به دیتابیس زده میشود.
2. در خط for el in bss_branch: یک query به دیتابیس زده می شود.
3. در خط for svc in el.services.all(): یک query به دیتابیس زده میشود. یعنی اگر در bss_branch تعداد N المان وجود داشته باشد به ازای هر کدام یک query در این قسمت زده خواهد شد.
یعنی برای هر el که تعداد آن به اندازه bss_branch هست یک query جدید داریم
بنا براین در کد بالا یک query زده شده و branch ها استخراج شده (در قسمت 1) که ترجمه آن:
1 2 3 4 5 6 7 8 9 |
SELECT `branch`.`id`, `branch`.`name`, `branch`.`name_fixed`, `branch`.`title_en`, `branch`.`title_fa`, `branch`.`is_active` FROM `branch` WHERE `branch`.`name` LIKE 'bss' LIMIT 21 |
در قسمت دوم یک query زده شده و office های آن branch استخراج شده:
1 2 3 4 5 6 7 8 9 |
SELECT `office`.`id`, `office`.`name`, `office`.`name_fixed`, `office`.`title_en`, `office`.`title_fa`, `office`.`branch_id`, `office`.`is_active` FROM `office` WHERE `office`.`branch_id` = 2 |
در قسمت سوم به ازای هر کدام از office ها یک query زده شده و service های آن استخراج شده به عنوان مثال یکیش رو آوردم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
SELECT `service`.`id`, `service`.`name`, `service`.`name_fixed`, `service`.`st`, `service`.`branch_id`, `service`.`office_id`, `service`.`company_object_id`, `service`.`managed_service_provider_id`, `service`.`development_service_provider_id`, `service`.`product`, `service`.`developer_id`, `service`.`persian_names`, `service`.`other_names`, `service`.`service_account_id`, `service`.`service_account_email`, `service`.`technician_id`, `service`.`service_manager_id`, `service`.`expert_id`, `service`.`arc_manager_id`, `service`.`arc_expert_id`, `service`.`description`, `service`.`active`, `service`.`domain`, `service`.`aspect`, `service`.`scope`, `service`.`service_nature`, `service`.`other_technical_representatives`, `service`.`other_vendor_emails`, `service`.`document_sender`, `service`.`document_sender_email`, `service`.`modules`, `service`.`service_status`, `service`.`description_and_approval`, `service`.`approver` FROM `service` WHERE `service`.`office_id` = 6 |
پس در مجموع N+1 درخواست دیتابیس query زدن شد.
حالا اینو بهینه میکنیم به کد زیر:
1 2 3 4 |
bss_branch = Branch.objects.get(name__iexact="bss").office_set.prefetch_related('services') for el in bss_branch: el.is_activated = any(svc in my_services for svc in el.services.all()) |
در این نمونه کد query های زیر را داریم:
- در خط bss_branch = Branch.objects.get(name__iexact=”bss”).office_set.prefetch_related(‘services’) یک query به دیتابیس داریم.
- در خط for el in bss_branch: یک query زده شده و office های branch را استخراج کرده.
- دوباره در همان خط یک query زده شده و service های تمامی office ها یک جا استخراج شده در این قسمت دیگر N بار query زده نشده
query قسمت 1
1 2 3 4 5 6 7 8 9 |
SELECT `branch`.`id`, `branch`.`name`, `branch`.`name_fixed`, `branch`.`title_en`, `branch`.`title_fa`, `branch`.`is_active` FROM `branch` WHERE `branch`.`name` LIKE 'bss' LIMIT 21 |
query قسمت 2
1 2 3 4 5 6 7 8 9 |
SELECT `office`.`id`, `office`.`name`, `office`.`name_fixed`, `office`.`title_en`, `office`.`title_fa`, `office`.`branch_id`, `office`.`is_active` FROM `office` WHERE `office`.`branch_id` = 2 |
query قسمت 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
SELECT `service`.`id`, `service`.`name`, `service`.`name_fixed`, `service`.`st`, `service`.`branch_id`, `service`.`office_id`, `service`.`company_object_id`, `service`.`managed_service_provider_id`, `service`.`development_service_provider_id`, `service`.`product`, `service`.`developer_id`, `service`.`persian_names`, `service`.`other_names`, `service`.`service_account_id`, `service`.`service_account_email`, `service`.`technician_id`, `service`.`service_manager_id`, `service`.`expert_id`, `service`.`arc_manager_id`, `service`.`arc_expert_id`, `service`.`description`, `service`.`active`, `service`.`domain`, `service`.`aspect`, `service`.`scope`, `service`.`service_nature`, `service`.`other_technical_representatives`, `service`.`other_vendor_emails`, `service`.`document_sender`, `service`.`document_sender_email`, `service`.`modules`, `service`.`service_status`, `service`.`description_and_approval`, `service`.`approver` FROM `service` WHERE `service`.`office_id` IN (6, 7, 8, 9, 10) |
همانطور که توی کد بالا میبینید توی خط آخر تمامی query ها ادغام شده اند
بنابراین N+1 دستور query به 3 query تبدیل شد