0

۸ نکته از جنگو برای کار با پایگاه داده (قسمت دوم)

قسمت اول

 

Select for update … of

در حال حاضر (نوشتن این مطلب) ، این ویژگی فقط برای بک اندهای PostgreSQL و Oracle در دسترس است.

یک الگوی معمول برای جمع آوری تراکنش ها در کد جنگو به شرح زیر است:

from django.db import transaction as db_transaction...with db_transaction.atomic(): transaction = ( Transaction.objects  .select_related( 'user', 'product', 'product__category', )  .select_for_update() .get(uid=uid) ) ...

دستکاری در تراکنش ها معمولاً شامل خصوصیاتی از مدل های کاربر و محصول است بنابراین ما اغلب از select_related استفاده می کنیم تا join شویم و برخی از کوئری ها  را ذخیره کنیم.

به روزرسانی تراکنش ها همچنین شامل  قفل کردن است زیرا باید مطمئن بود که در یک لحظه توسط شخص دیگری دستکاری نمی شود.

ما برخی فرآیندهای ETL را داریم که در شب اجرا می شوند و تعمیر و نگهداری را روی جداول محصولات و کاربرران انجام می دهند. این ETL ها  روزرسانی ها و درج جدول ها را انجام می دهند بنابراین جدول ها را قفل میکنند.

خب مشکل چیست؟ هنگامی که از select_for_update همراه با select_related استفاده می شود ، جنگو سعی می کند قفل تمام جداول موجود را در کوئری بدست آورد.

کدی که ما برای واکشی معاملات استفاده کردیم سعی در قفل کردن  جدول تراکنش ها و جداول کاربران ، محصول و دسته بندی ها داشت. هنگامی که ETL سه جدول آخر را در اواسط شب قفل کرد ، تراکنش ها شروع به fail شدن کردند.

هنگامی که درک بهتری از مشکل پیدا کردیم ، به دنبال راه هایی  بودیم که  قفل کردن  را فقط برای  تیبل لازم – تیبل تراکنش – انجام دهد. خوشبختانه گزینه جدیدی برای select_for_update در Django 2.0 در دسترس قرار گرفت:

from django.db import transaction as db_transaction
with db_transaction.atomic(): transaction = ( Transaction.objects .select_related( 'user', 'product', 'product__category', ) .select_for_update( of=('self',) ) .get(uid=uid) ) 
 

گزینه بالا به select_for_update اضافه شد. با استفاده از آن ما می توانیم صریحاً بگوییم که کدام جدول ها را می خواهیم قفل کنیم. self کلمه کلیدی خاصی است که نشان می دهد ما می خواهیم مدلی را که روی آن کار می کنیم قفل کنیم ،

در این مثال transaction.

 

 

FK Indexes

هنگام ایجاد یک مدل ، جنگو به طور خودکار یک شاخص B-Tree روی هر کلید خارجی ایجاد می کند. شاخص های B-Tree می توانند بسیار سنگین شوند و گاهی اوقات در واقع لازم نیستند.

یک مثال کلاسیک ،یک مدل  برای رابطه M2M است:

class Membership(Model):
 group = ForeignKey(Group)
 user = ForeignKey(User)

در مدل بالا ، جنگو به طور ضمنی دو ایندکس ایجاد می کند – یکی برای کاربر و دیگری برای گروه.

 

الگوی معمول دیگر در مدل های M2M افزودن محدودیت منحصر به فرد در این دو فیلد است. در مورد مثال ما به این معنی است که کاربر فقط یک بار می تواند عضو یک گروه باشد:

class Membership(Model): group = ForeignKey(Group) user = ForeignKey(User) class Meta: unique_together = ( 'group', 'user', )
 

unique_together در هر دو فیلد ایندکس ایجاد می کند. بنابراین یک مدل با دو فیلد و سه نمایه دریافت می کنیم.

بسته به کاری که با این مدل انجام می دهیم ، بارها می توانیم ایندکس های FK را کنار بگذاریم  و فقط ایندکس ایجاد شده توسط محدودیت منحصر به فرد را حفظ کنیم:

class Membership(Model): group = ForeignKey(Group, db_index=False) user = ForeignKey(User, db_index=False) class Meta: unique_together = ( 'group', 'user', )
 

حذف ایندکس های زائد باعث  درج و به روزرسانی سریع تر می شود ، به علاوه ، اکنون پایگاه داده ما سبک تر است که همیشه چیز خوبی است 🙂

 

ترتیب ستون ها در composite index

به اینذکس های دارای بیش از یک ستون ، composite indexes گفته می شود. در شاخص های کامپوزیت B-Tree ستون اول با استفاده از ساختار درخت نمایه می شود. از برگهای سطح اول درخت جدیدی برای سطح دوم  ایجاد می شود و به همین صورت ادامه

می یابد.

ترتیب ستون ها در اینذکس قابل توجه است.

در مثال بالا ، ابتدا یک درخت برای گروه ها و برای هر گروه یک درخت دیگر برای تمامی کاربران آن به دست می آید.

قانون کلی برای شاخص های کامپوزیت B-Tree این است که شاخص های ثانویه را تا حد ممکن کوچک کنند. به عبارت دیگر ، ستونهایی با کاردینالیتی بالا (مقادیر مشخص تر) باید در درجه اول قرار گیرند.

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

class Membership(Model): group = ForeignKey(Group, db_index=False) user = ForeignKey(User, db_index=False) class Meta: unique_together = (  'user', 'group', )
 

این فقط یک قانون کلی است . نمایه سازی نهایی باید برای موارد خاص بهینه شود. نکته اصلی در اینجا آگاهی از شاخص های ضمنی و اهمیت ترتیب ستون در شاخص های ترکیبی است.

 

BRIN indexes

شاخص B-Tree مانند یک درخت ساخته شده است. هزینه جستجوی یک مقدار  ،( ارتفاع درخت + 1) برای دسترسی تصادفی به جدول است. این باعث می شود که شاخص های B-Tree برای محدودیت های منحصر به فرد و  برخی کوئری های محدوده(

range)مناسب باشد.

عیب شاخص B-Tree اندازه آن است – شاخص های B-Tree می توانند بزرگ شوند.

BRIN برای کار با جداول بسیار بزرگ طراحی شده است که در آن برخی ستونها با مکان فیزیکی آنها در جدول رابطه طبیعی دارند.

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

بگوید که مقدار مشخصی در محدوده نیست یا ممکن است در محدوده بلوک های فهرست شده باشد.

بیایید یک مثال ساده از نحوه کار BRIN برای کمک به ما در درک مطلب انجام دهیم.

ما این مقادیر را در یک ستون داریم ، هر کدام یک بلوک است:

1, 2, 3, 4, 5, 6, 7, 8, 9

بیایید برای هر 3 بلوک مجاور یک محدوده ایجاد کنیم:

[1,2,3], [4,5,6], [7,8,9]

برای هر محدوده ما  حداقل و حداکثر مقدار را در محدوده حفظ خواهیم کرد:

[1–3], [4–6], [7–9]

با استفاده از این فهرست ، سعی کنید مقدار 5 را جستجو کنید:

  • [1–3] – قطعاً اینجا نیست.
  • [4-6] – ممکن است اینجا باشد.
  • [7–9] – قطعاً اینجا نیست.

با استفاده از ایندکس ، جستجوی خود را به بلوکهای 4-6 محدود کردیم.

بیایید مثالی دیگر بزنیم ، این بار مقادیر موجود در ستون به خوبی مرتب نمی شوند:

[2,9,5], [1,4,7], [3,8,6]

و این شاخص ما با حداقل و حداکثر مقدار در هر محدوده است:

[2–9], [1–7], [3–8]

بیایید سعی کنیم مقدار 5 را جستجو کنیم:

  • [2–9] – ممکن است اینجا باشد.
  • [1-7] – ممکن است اینجا باشد.
  • [3–8] – ممکن است اینجا باشد.

این فهرست بی فایده است – نه تنها به هیچ وجه محدودیت جستجو را ایجاد نکرد ، بلکه در واقع مجبور بودیم  تعداد بیشتری بخوانیم زیرا هم شاخص و هم کل جدول را می آوریم.

طبق  مستندات:

columns have some natural correlation with their physical location within the table.

این برای ایندکس های BRIN کلیدی است. برای استفاده حداکثر از آن ، تمام مقادیر موجود در ستون باید تقریباً بر روی دیسک مرتب شده یا دسته بندی شوند.

حالا به جنگو برگردیم ، چه فیلدی ای داریم که اغلب ایندکس می شود و به احتمال زیاد بر روی دیسک مرتب می شود؟ درست است ، auto_now_add .

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

class SomeModel(Model): 
 created = DatetimeField(
 auto_now_add=True,
 )

هنگامی که auto_now_add استفاده می شود ، جنگو به طور خودکار فیلد را با زمان فعلی هنگام ایجاد ردیف پر می کند.  فیلد created معمولاً یک گزینه مناسب برای کوئری است بنابراین اغلب ایندکس می شود.

بیایید یک ایندکس BRIN در مورد created اضافه کنیم:

from django.contrib.postgres.indexes import BrinIndexclass SomeModel(Model): created = DatetimeField( auto_now_add=True, ) class Meta:  indexes = ( BrinIndex(fields=['created']), )
 

برای درک تفاوت اندازه ، من یک جدول با حدودا 2 میلیون ردیف با یک فیلد تاریخ ایجاد کردم که به طور طبیعی بر روی دیسک مرتب شده است:

  • BRIN index: 49 KB

درست است ، هیچ اشتباهی نکردیم.

هنگام ایجاد ایندکس ها موارد بسیار بیشتری نسبت به اندازه ایندکس باید در نظر گرفته شود. اما اکنون ، با پشتیبانی Django 1.11 از فهرست ها ، می توانیم انواع جدیدی از نمایه ها را به راحتی در برنامه های خود ادغام کرده و سبک و سریعتر  اجرا کنیم.

 

 

 

ارسال دیدگاه

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *