کار کردن بهینه با دیتابیس جنگو

راه های بهینه استفاده کردن دیتابیس:

1.توی query هایی که با استفاده از ORM بر روی دیتابیس اجرا میکنیم بهتر است خود SQL آن را ببینیم با استفاده از پرینت کردن query

2.از Indexing در ORM استفاده کنیم

3.وضعیت بهینگی query ها را ببینیم با استفاده از ابزارهایی مثل django-debug-tools

4.مراقب مشکلاتی مثل N+1 problems باشیم

5.از caching استفاده کنیم

6. از قابلیت query laziness در QuerySet های Django نهایت استفاده را بکنیم

7.Query تکراری اجرا نکنیم

8.QuerySet.explain() بهینگی و زمان اجرا query را به من نشان می دهد

9.به حداقل رساندن DB hit با استفاده از prefetch_related و select_related

در لینک زیر مثالهایی از اجرای query اضافی در دیتابیس میبینیم

https://docs.djangoproject.com/en/4.2/topics/db/optimization/

کاربرد nested method در پایتون

کد زیر رو می تونید توی django.forms.fields.py ببینید. یه نکته جالب داره. متد split_url رو توی متد to_python تعریف کرده:

سوال اینجاست که چرا باید یه متد رو توی متدی دیگه تعریف کرد؟ کاربردش چیه؟ مزایاش چیه؟ و چجوری باید صدا زد اونها رو:

دلیل استفاده از این روش توی OOP گفته شده که اگر قرار باشه که یک کار رو چندین بار توی یک متد انجام داد باید اون رو یک به صورت یک تابع بنویسیم تا اینجا که واضحه (دلیل تابع). حالا فرض کنیم بخایم از یک تابع فقط توی یک متد از یک کلاس استفاده کنیم، یعنی اصلا حتی به متدهای دیگه همون کلاس ربطی نداره. اون وقته که منطقی میشه از این روش استفاده کرد و فقط هم باید توی همون متد to_python صدا زده بشه.

مزایاش که مشخصه، اگه کدی لازم نباشه به scope های دیگه نشون داده نمیشه و مزایای دیگه…

ترتیب اجرای متدهای فیلدها توی django.forms

در مورد صحتسنجی فیلدهایی که هنگام اجرای یک form توی جنگو طی میشه ترتیب زیر طی میشه که هر کدوم از اونها رو میشه overwrite کرد که رفتار form وقتی که دیتا میگیره تغییر بدیم

این توابع رو توی کلاس Field از django.forms میتونید ببینید. لینک زیر بدرد بخوره:

https://docs.djangoproject.com/en/4.2/ref/forms/validation/

F() expression توی جنگو

تفاوت دو کد زیر چیست؟

استفاده از F() expression توی کد جنگوی بالا این امکان رو میده که تغییر فیلد و increment کردنش سمت دیتابیس آماده انجام بشه بجای اینکه پایتون یک واحد بهش اضافه کنه. یعنی یک query میره سمت دیتابیس و مقدار فیلد هرچی هست همونجا یه واحد اضافه میشه. اینجوری دیگه لازم نیست یک بار از دیتابیس بگیره و اضافه کنه و آپدیت شده رو بفرسته سمت دیتابیس و دو query اجرا کنه. اما برای اینکه مقدار آپدیت شده سمت دیتابیس را داشته باشیم:

چالش ORM جنگو – spanning multivalued relationship

در رابطه های ManyToMany یا OneToMany استفاده کردن از spanning داخل filter به model دیگر این سوال رو ایجاد میکنه که هر دو شرط باید روی یک آبجکت اجرا شه یا هر کدام از شرط ها جدا؟ کد زیر توی مستندات جنگو روشن این موضوع رو نشون داده:

تکه کد ORM جنگو – Queryset slicing – سوال مصاحبه

آیا ORM جنگو با اجرای کد زیر به دیتابیس query میزند؟ اگر آره چند Hit دیتابیس داریم؟

ORM جنگو lazy است یعنی در هر حالتی به دیتابیس دسترسی نمیگیرد مگر اینکه بخواهد queryset را کامل از دیتابیس بگیرد. درحالتی که از slicing پایتون برای یک queryset استفاده می کنیم ORM یک queryset جدید برمیگردونه و اون رو اجرا نمیکنه. یعنی توی خط اول به دیتابیس Hit نداریم. قاعدتا باید توی خط دوم هم همین باشه اما slicing پایتون با step استثنا هست و توی خط دوم یه Hit به دیتابیس داریم.

تکه کد – سوال مصاحبه

کدهای زیر با هم چه تفاوتی دارند؟

در شرط اول QuerySet اجرا نشده و فقط یک دستور از دیتابیس گرفته شده که آیا این queryset عضوی دارد یا خیر. جنگو این بررسی رو در بهینه ترین و کوتاه ترین حالت انجام میده و از حالت دوم که queryset رو اجرا کرده کمی سریع تره. نکته اینه که در حالتی که میدونیم کهم در هر صورت در ادامه برنامه قراره my_queryset رو اجرا کنیم (چه عضو داشته باشه چه نه) بهتره که از حالت دوم استفاده کنیم که دیگه یک بار اضافه از exists استفاده نکرده باشیم. چون همینطور که میدونیم اگر my_queryset رو از دیتابیس بیگیریم دفعات بعدی دیگه از دیتابیس نمیگیریم و cache میشه.

همین داستان رو برای تابع contains() هم داریم.

مشکل N+1 – سوال مصاحبه (دوم)

کد زیر چند Hit دیتابیس دارد؟

همچنان N+2!!!

حتی با وجود اینکه از prefetch_related استفاده شده است دوباره در داخل حلقه به N دسترسی مجدد به دیتابیس وجود دارد. چرا که prefetch_related بالا برای Pizaa.objects.all بوده ولی پایین از filter استفاده کردیم که ORM جنگو دسترسی بالایی را در نظر نمیگیرد و دوباره query میزند. اتفاقا بدتره چرا که یه query بالا هم اضافی هست. راه حل زیر درست میشه:

یا

ارثبری از مدلهای جنگو چه حالاتی دارد

ارثبری از مدلهای جنگو بستگی به این نکته دارد که آیا مدل فرزند و مدل پدر هر دو دارای جدولهای دیتابیس جداگانه ای هستند یا خیر؟ آیا کلاس پدر فقط برای این است که یک سری فیلد و متد و استاندارد برای فرزندان تعریف کند و اصلا خودش جدول دیتابیس نیست یا نه؟ آیا پدر و فرزند هر دو جدول دیتابیس هستند و فقط ارتباط یک به یک یا چند به چند دارند؟ با توجه به این سوالات حالات متفاوتی وجود دارد

Abstract Base Model Inheretence

ارثبری abstract در این روش کلاس پدر یک سری پارامتد و متد تعریف میکند و فقط برای به ارث برده شدن توسط فرزندان است و اصلا خودش جدول دیتابیس ندارد و اصلا استفاده دیگری ندارد و از آن هیچ instance ای هم ساخته نمی شود.

Multi-table inheritance

در این روش هر دو کلاس دارای جدول دیتابیس هستند و جداگانه Manager دارند و استفاده می شوند. به صورت implicit بین آنها ارتباط OneToOne شکل میگیرد و می توان به همدیگر هم دسترسی داشت. می توان به صورت explicit آن ارتباط به ManyToMany تغییر داد.

Proxy Model

در این حالت کلاس فرزند پارامتد و داده جدیدی به کلاس پدر اضافه نمی کند و فقط رفتار کلاس پدر را تغییر می دهد یعنی متدهای جدیدی تعریف می کند نه اینکه فیلد اضافه کند. مثلا Manager پیشفرض را تغییر میدهد یا یک Manager دوم هم برای پدر تعریف می کند. در این حالت کلاس Proxy به دیتای پدر دسترسی دارد.

Multiple Inheretence

چطوری توی جنگو مدل رابطه IS-A ایجاد کنیم؟

فرض کنید یک مدل ساختیم به اسم Place که داری آدرس و مشخصات یک مکان رو داره اون وقت یک مدل میخایم بسازیم به اسم Restaurant خوب بنظر منطقیه که تمام اون فیلدهایی که توی Place تعریف کردیم رو اینجا هم تعریف کنیم؟ نه میشه از Inheritance استفاده کرد. یک راه بهتر ایجاد رابطه IS-A هست چون restaurant is a place و این کار رو با django.db.models.OneToOneField انجام میدیم. که البته با ارث‌بری هم میشد انجام داد در واقع Inheritance به صورت implicit یک IS-A relationship هست.