مهسا فرهادی سامانه اقساط یک فروشگاه لوازم خانگی در ساری را نگه میدارد. گزارش ماهانهای که میگوید کدام قسط عقب افتاده، روی ۱۸٬۰۰۰ سطر ۳۸ ثانیه طول میکشید، روی ماشینی که کار دیگری نمیکرد. یک خط اضافه شدن به پایگاه داده آن را به ۷۰ میلیثانیه رساند، و آن خط یک نویسه از خود پرسمان را عوض نکرد.
پایگاه داده جایی برای گذاشتن چیزها نیست. مجموعهای از قولهاست درباره اینکه بعد از یک قطعی چه چیزی هنوز درست است، و مجموعهای از معاملهها درباره اینکه چه چیزی سریع خواهد بود. پنج خوشه زیر این صفحه یکی از این دو هستند. و یک ستون در جدول مهسا هست که هر دو را با هم خراب میکند و در هیچ منبع انگلیسیزبانی نامش نمیآید: ستون تاریخ.
خطی که سی و هشت ثانیه را برداشت
پرسمان مهسا اقساطی را میخواهد که سررسیدشان گذشته است. بدون کمک، پایگاه داده یک راه دارد: هر ۱۸٬۰۰۰ سطر را بخواند و تاریخ هرکدام را نگاه کند. ایندکس یک کپی دوم از همان ستون است که مرتب نگه داشته میشود، و با آن پایگاه داده یکراست میپرد سر مرز و فقط سطرهای بعد از آن را میخواند. کل تعمیر همین بود: CREATE INDEX ON aqsat (sarresid).
مجانی نبودنش دلیل روشنی دارد و همان دلیلی است که پایگاه دادهها بهطور پیشفرض روی همهچیز ایندکس نمیسازند: آن کپی باید درست بماند، پس هر درج و هر بهروزرسانی روی آن ستون از این به بعد دو بار مینویسد. روی جدولی که مدام خوانده میشود و کم نوشته، معامله عالی است. روی جدولی که ثانیهای یک نوشتن میگیرد و ماهی یک بار خوانده میشود برعکس است، و ایندکس مالیاتی است که تمام روز داده میشود برای صرفهجوییای که ماهی یک بار برداشته میشود.
دستوری که بحث را تمام میکند EXPLAIN است، که پیش از اجرا از پایگاه داده میپرسد قصد دارد چه کند. جوابش یک نقشه است: پویش ترتیبی ۱۸٬۰۰۰ سطر، یا پویش ایندکس روی ۴۱۰ سطر. خواندن همین خروجی قابلانتقالترین مهارت این صفحه است و در نهایت همان چیزی است که زبانی که با آن از پایگاه داده سوال میپرسند برای آن وجود دارد.
کنارش قاعدهای مینشیند که بیشتر از هر چیز دیگری ایندکسها را بیمصرف میکند: هر تابعی که روی ستون بگذارید، ایندکس آن ستون را از کار میاندازد. نسخه اول مهسا ماه را با بریدن رشته درمیآورد و ایندکس هیچ کمکی نمیکرد، چون چیزی که ایندکس شده ستون است نه نتیجه آن تابع. نسخهای که کار کرد به جای تابع یک بازه میپرسد، از اول ماه تا اول ماه بعد.
WHERE SUBSTR(sarresid, 1, 7) = '1404/06'WHERE sarresid >= '1404/06/01' AND sarresid < '1404/07/01'
ستون تاریخ، و شش ماهی که بعد از آن میآید
تقریباً هر پروژه فارسی روز اول همین کار را میکند: تاریخ شمسی را همانطور که در رابط دیده میشود، به شکل رشته، در ستون میگذارد. تا ماه ششم هیچچیز خراب نیست. بعد اولین گزارشی میرسد که سه ماه گذشته را میخواهد و معلوم میشود پایگاه داده درباره آن ستون هیچچیز نمیداند جز اینکه یک رشته است. تاریخ را به شکل لحظه ذخیره کنید و شمسی را فقط موقع نمایش بسازید: یک ستون زمان واقعی به وقت جهانی، و تبدیل در لایه نمایش، همان جایی که رقم فارسی هم ساخته میشود.
دو دلیلش را میشود در یک دقیقه بررسی کرد. اول اینکه مرتب شدن یک تاریخ رشتهای فقط تا وقتی درست است که همه سطرها یک قالب داشته باشند: یک سطر که با '1404/6/11' و بدون صفر نوشته شده جای اشتباهی مینشیند و در هیچ بازهای پیدا نمیشود، و چون فقط یکی است هیچکس شکایت نمیکند. دوم اینکه حساب کردن روی آن رشته غلط درمیآید، چون شش ماه اول سال ۳۱ روز است، پنج ماه بعد ۳۰، و اسفند ۲۹ یا ۳۰ روز، پس یک ماه بعد هرگز سی روز بعد نیست و فاصله دو قسط تیر با فاصله دو قسط بهمن یکی نیست.
و قولی که همه این کار روی آن سوار است: جابهجا کردن یک قسط میان دو پرونده دو نوشتن است، و یک قطعی میان آن دو، قسطی میسازد که مال هیچکس نیست. داخل BEGIN و COMMIT یا هر دو مینشینند یا هیچکدام، و حالتی وسط این دو نیست که کسی بتواند ببیندش. همین قول است که تصمیم گرفتن اینکه جدولها چه هستند، پیش از پر کردنشان را پیش از هر ابزاری میگذارد.
ترتیب، و اینکه اینجا پله سوم را خودتان انتخاب نمیکنید
ترتیبی که جواب میدهد زبان است، بعد شکل، بعد موتور. در ایران یک تفاوت هست که این ترتیب را سادهتر میکند و کسی نمیگویدش: پله سوم معمولاً انتخاب شما نیست. روی میزبانی اشتراکی ایرانی چیزی که تحویلتان میدهند تقریباً همیشه MySQL یا MariaDB است، پس پایگاهدادههایی که واقعاً به شما تحویل داده میشوند یک تصمیم نیست، یک واقعیت است، و وقتی که برای انتخاب موتور صرف نمیشود جایش را به دو پله پایینتر میدهد.
برعکس گرفتن این ترتیب امضای مشخصی دارد. کسی ORM را پیش از SQL یاد میگیرد، برنامهای میسازد که کار میکند، و بعد به صفحهای کند میرسد که نمیتواند دربارهاش فکر کند، چون لایهای که پرسمانها را برایش مینویسد همان لایهای است که پنهان میکند چه چیزی کند است. ایراد از ORM نیست و لایه میان اشیای برنامه و جدولها موضوع واقعیای است؛ فقط برای کسی ناخواناست که هرگز چیزی را که تولید میکند ندیده باشد.
ترتیب
چهار پله، و هر پلهای که از بالایش شروع کنید نامرئی میماند
تقریباً همه از پله سوم شروع میکنند، چون تنها پلهای است که نامش در یک آگهی شغلی نوشته میشود.
- ۰۴شکل دیگرسند و کلید، وقتی یک ردیف ثابت واقعاً ظرف درستی نیست.سه پله اول را رد کنید و این یک حدس است
- ۰۳موتورکدام محصول، کدام گویش، و زیر یک قطعی چه میکند.اینجا معمولاً میزبان انتخابش کرده
- ۰۲شکلجدولها چه هستند، چه چیزی یکتاست، و چه چیزی حق ندارد خالی بماند.ردش کنید و هیچ ایندکسی نجاتتان نمیدهد
- ۰۱زبانمستقیم سوال پرسیدن، و خواندن نقشهای که پایگاه داده با آن جواب میدهد.ردش کنید و ORM مات میشود
پلهها سختی نیستند. اولی آسانترین برای یاد گرفتن است و بیشتر از همه به تعویق میافتد، چون یک ORM برای تازهکار پرسمان قابل قبول مینویسد و دقیقاً همان جایی از این کار دست میکشد که اهمیت پیدا میکند.
جایی که کار میایستد: ستونی که روز اول رشته شد
ایست این موضوع در فارسی همان ستون است، و شکل ایستادنش این است که پروژه اصلاً نمیایستد. شش ماه هیچچیز خراب نیست، بعد گزارشها یکییکی سخت میشوند، و چون هر جدول و هر پرسمان به آن قالب وابسته شده، عوض کردنش دیگر یک ستون نیست بلکه یک مهاجرت است. هشدارش را هم کسی نمیدهد، چون هر آموزش فارسی از همان جایی شروع میکند که تاریخ باید در رابط دیده شود، و از آنجا رشته ذخیره کردن بدیهی به نظر میرسد.
ایست دوم کوچکتر است. آدمها پایگاه داده را دپارتمان کس دیگری حساب میکنند تا روزی که صفحهای کند شود، و بعد کشف میکنند تعمیرش اصلاً در کد برنامه نبود. اینکه شکل داده اصلاً ردیف و ستون هست یا نه هم پرسش واقعیای است و وقتی ردیف و ستون شکل درستی نیستند جوابش را میدهد؛ برای مهسا نبود، چون قسط به پرونده وصل است و پرونده به مشتری، و هر گزارشی که مینویسد یک پیوند است.
چهار چیزی که در ۲۰۲۶ باز میکنید
SQLite جای درست یاد گرفتن است و روزبهروز بیشتر جای درست منتشر کردن هم هست: یک فایل، بدون سرور، در مالکیت عمومی، و جای عادت اختراع کردن یک قالب فایل و نوشتن تحلیلگر برای آن را گرفت. فروشگاهی به اندازه فروشگاه مهسا میتواند برای همیشه رویش بماند، و برای خوانندهای که نصب کردن گرانترین قدمش است این تنها موتوری در این صفحه است که چیزی برای دانلود ندارد.
PostgreSQL پیشفرض جایی است که سرور توجیه دارد، رایگان و متنباز است، و این جایگاه را طی حدود یک دهه از MySQL گرفت. باید صادق بود که این جمله برای شما شاید عملی نباشد: روی میزبانی اشتراکی ایرانی معمولاً MySQL یا MariaDB تحویل میگیرید و انتخاب دیگری در کار نیست. خبر خوب این است که SQL این صفحه با تفاوتهای کوچک گویشی میانشان جابهجا میشود، نه با بازنویسی.
EXPLAIN در هر موتوری که ارزش استفاده داشته باشد هست و جای کرنومتر گرفتن روی یک پرسمان و حدس زدن علت را گرفت. رایگان است چون بخشی از خود پایگاه داده است. یک نکته که در پنلهای میزبانی اشتراکی گم میشود: phpMyAdmin هم آن را دارد، زیر دکمهای که کسی نمیزند.
Intl.DateTimeFormat تاریخ شمسی را در خود مرورگر و در Node میسازد و جای بستههای تبدیل تاریخ را گرفت که هر پروژه فارسی یکیشان را داشت. رایگان است و چیزی نصب نمیشود: new Intl.DateTimeFormat('fa-IR-u-ca-persian').format(d) همان کاری را میکند که ستون رشتهای میخواست بکند، در لایهای که جایش همانجاست.
آنچه هیچکدام از اینها جواب نمیدهند این است که وقتی یک ماشین دیگر برای نگه داشتن این چیز کافی نباشد چه میشود؛ آنجا همان قولهای بالا عمداً یکییکی پس گرفته میشوند و موضوع دیگری است. پرسش نزدیکتر این است که این پایگاه داده هر شب کجا پشتیبان گرفته میشود و برگرداندنش چقدر طول میکشد، که بیرون بردن کد از دستگاه خودتان، و زنده نگه داشتنش جوابش را دارد، و شکل خود داده در ایدههایی که با عوض شدن زبان از بین نمیروند همان معاملهای است که اینجا روی دیسک تکرار میشود.