پیشفرضهای SQLite غلطن؛ راهحل نسخههای سالانهست
خلاصهٔ کاملتر
نویسنده اول تعارف رو کنار میذاره: SQLite یه موتور پایگاه دادهٔ عالیه و اغراق نیست اگه بهش بگیم استاندارد صنعتی ذخیرهسازی محلی؛ حتی نرمافزارهای سروری مثل lobste.rs هم روش اجرا میشن. برخلاف RDBMSهای سنتی یه پروسهٔ جداگانه نیست بلکه یه کتابخونهست، و برخلاف فرمتهای فایل هم لازم نیست خودت سریالایزر و پارسر بنویسی. «فقط یه مشکل بزرگ هست: پیشفرضهاش همه غلطن.»
پیشفرض بد اول: قیدهای کلید خارجی نادیده گرفته میشن. کلید خارجی مهمترین ابزار ماست برای اینکه دیتابیس ناسازگار و پر از ارجاع معلق نشه، و به گفتهٔ نویسنده SQLite تنها موتوریه که بهصورت پیشفرض اعمالش نمیکنه.
بدتر اینکه SQLite تمایل به بازاستفاده از ROWID داره. توی مثال خود مقاله، باب حساب میسازه و پست میذاره، بعد حسابش حذف میشه ولی پستش میمونه و هیچ خطایی هم نمیگیریم؛ بعد آلیس ثبتنام میکنه و همون شناسهٔ ۱ رو میگیره و ناگهان پست باب به اسم آلیس ظاهر میشه. یعنی ارجاع معلق تبدیل میشه به ارجاع به ردیف اشتباه که حتی خطا هم نمیده. راهحل: PRAGMA foreign_keys = ON;
پیشفرض بد دوم: ستونها نوع اشتباه رو قبول میکنن. ستونی که INTEGER تعریف شده در واقع فقط «تمایل عددی» داره؛ اگه رشتهای بدی که عدد نیست، همونطور ذخیره میشه. در مثال مقاله، توی ستون duration_sec یه جملهٔ کامل انگلیسی میشینه. راهحل، جدولهای strictه:
CREATE TABLE music (
id INTEGER PRIMARY KEY,
name TEXT,
duration_sec INTEGER
) strict;
-- Runtime error: cannot store TEXT value in INTEGER column music.duration_secولی هیچ پراگمایی نیست که همهٔ جدولها رو سراسری strict کنه و باید تکتک یادت باشه. نویسنده دو دفاع رایج رو هم بررسی میکنه: نوشتهٔ خود سازندههای SQLite دربارهٔ «تایپ انعطافپذیر» رو ضعیف میدونه چون نوع ANY همون نیاز رو برطرف میکنه؛ ولی استدلال بهتری از کاربری در lobste.rs میپذیره: strict قواعد پارس نام نوع رو هم عوض میکنه و اسمهای سفارشی مثل DATETIME یا COLOR رو که کانکتورها برای سریالسازی خودکار استفاده میکنن از بین میبره.
راهحل پیشنهادیش نامهای مستعار نوع بود، و در بهروزرسانی پست اشاره شده که استاندارد SQL-99 از قبل همین رو با CREATE DOMAIN تعریف کرده — پس کافیه SQLite همون دستور استاندارد رو پشتیبانی کنه.
پیشفرض بد سوم: خطای SQLITE_BUSY. SQLite چند خوانندهٔ همزمان رو قبول میکنه ولی فقط یه نویسنده؛ و اگه دو پروسه همزمان دنبال قفل نوشتن باشن، یکی بلافاصله خطا میگیره. نویسنده میگه انتظار طبیعی اینه که تا یه تایماوت صبر کنه و همین پیشفرض باعث باگهای واقعی و کرش شده. راهحل PRAGMA busy_timeout = 5000;ه که تا ۵ ثانیه تلاش میکنه.
پیشفرض بد چهارم: کارایی. SQLite با تنظیم درست میتونه واقعاً سریع باشه، ولی مهمترین پیشفرض بد اینه که Write-Ahead Log خاموشه. روشنکردن WAL در بیشتر شرایط جهش بزرگی در سرعت نوشتن میده و کنارش synchronous = NORMAL تعداد همگامسازیهای دیسک رو بدون ریسک خرابی داده کم میکنه.
دلیل همیشگی موندن این پیشفرضها سازگاری با گذشتهست و نویسنده هم قبولش داره؛ پیشنهادش یه «سوپر پراگما»ست که همه رو یکجا درست کنه، یعنی PRAGMA edition = 2026; معادل این مجموعه باشه (بهعلاوهٔ strict شدن پیشفرض جدولها):
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;ایده مستقیماً از نسخههای Rust گرفته شده. مزیت شمارهگذاری سالانه نسبت به چیزی مثل "use strict"; جاوااسکریپت اینه که تعریف «پیشفرض عاقلانه» با گذر سالها عوض میشه؛ مثلاً اگه روزی WAL2 به شاخهٔ اصلی برسه، edition = 2034 میتونه یعنی journal_mode = WAL2. اینطوری موتور جلو میره بدون اینکه نرمافزارهای قدیمی بشکنن.
نکات کلیدی:
- کلید خارجی بهصورت پیشفرض خاموشه و با بازاستفاده از ROWID، ارجاع به ردیف اشتباه میسازه
- ستونها بدون strict نوع اشتباه رو قبول میکنن و پراگمای سراسری برای strict وجود نداره
- درگیری دو نویسنده بهجای انتظار، بلافاصله SQLITE_BUSY میده؛ چاره busy_timeoutه
- WAL بهصورت پیشفرض خاموشه و روشنکردنش جهش بزرگی در سرعت نوشتن میده
- پیشنهاد نویسنده: PRAGMA edition = 2026; به سبک نسخههای Rust، بدون شکستن سازگاری




