.db ဖိုင်တစ်ခုထဲမှာ ဘာတွေရှိနေသလဲ၊ Page Structure ဆိုတာ ဘာလဲ၊ Query တစ်ကြောင်း မ Run ခင် Disk ပေါ်မှာ အရာအားလုံးကို ဘယ်လို စီစဉ်ထားသလဲ။

SQLite ကို ပထမဆုံး စတင်သုံးစွဲဖူးတဲ့ Developer တိုင်း ဒီလို အတွေ့အကြုံမျိုး ရှိဖူးကြပါလိမ့်မယ်။ Application ကို Run လိုက်တယ်၊ .db ဖိုင်တစ်ခု ထွက်လာတယ်၊ ပြီးတော့ “ဒါပဲလား?” ဆိုပြီး အံ့ဩမိတာမျိုးပါ။
တခြား Database တွေလို Server process ကြီးတွေ မရှိဘူး။ အမည်မသိ Binary blob တွေနဲ့ ပြည့်နေတဲ့ Data directory တွေ မရှိသလို၊ Internal ID တွေနဲ့ အမည်ပေးထားတဲ့ Tablespace ဖိုင်တွေလည်း မရှိပါဘူး။ PDF တစ်ခု ဒါမှမဟုတ် ZIP ဖိုင်တစ်ခုလို Disk ပေါ်မှာ တည်ရှိနေတဲ့ ဖိုင်တစ်ခုတည်းပဲ ရှိတာပါ။
ဒီရိုးရှင်းမှုဟာ SQLite ရဲ့ အဓိက အသက်သွေးကြော ဖြစ်ပါတယ်။ “ဖိုင်တစ်ခုတည်း” ဆိုတာ အဆင်ပြေရုံသက်သက် (Convenience) အတွက် Hack လုပ်ထားတာ မဟုတ်သလို၊ “Lite” ဖြစ်ရမယ်ဆိုတဲ့ အမည်ကြောင့် လုပ်ထားတာလည်း မဟုတ်ပါဘူး။ ဒါဟာ ၂၀၀၄ ခုနှစ်ကတည်းက Backward Compatibility ဖြစ်နေစေမယ့် File Format တစ်ခုကို သေချာဂရုတစိုက် ဒီဇိုင်းဆွဲထားတဲ့ ရလဒ်ပဲ ဖြစ်ပါတယ်။ SQLite version 3.0 နဲ့ ဖန်တီးခဲ့တဲ့ Database ဖိုင်ကို နောက်ဆုံးထွက် Version နဲ့ ဖွင့်ကြည့်ရင်တောင် ဘာပြဿနာမှမရှိဘဲ အလုပ်လုပ်နေမှာပါ။ ဒါဟာ တမင်ရည်ရွယ်ပြီး ပေးထားတဲ့ ကတိတစ်ခု (Design Promise) ပဲ ဖြစ်ပါတယ်။
ဒီအပိုင်းမှာတော့ ဒီ file ရဲ့ အတွင်းပိုင်း တည်ဆောက်ပုံကို လေ့လာကြည့်ကြပါမယ်။ Query မ Run ခင်၊ WAL mode မသုံးခင်နဲ့ Index မချခင်မှာ Disk ပေါ်က Byte တွေဟာ ဘယ်လိုပုံစံနဲ့ ရှိနေသလဲဆိုတာ ကြည့်ရအောင်။
File ဆိုတာ Pages တွေ စုစည်းထားတာပါ
Hex Editor တစ်ခုနဲ့ SQLite Database တစ်ခုကို ဖွင့်ကြည့်လိုက်ရင် ပထမဆုံး သတိထားမိမှာက file ကို အရွယ်အစား တူညီတဲ့ Chunk (အပိုင်းအစ) လေးတွေ ခွဲထားတာပါ။ ဒါတွေကို Pages လို့ ခေါ်ပြီး SQLite ရဲ့ အရာအားလုံးအတွက် အခြေခံ Unit တွေ ဖြစ်ပါတယ်။
Page တစ်ခုဟာ 512 ကနေ 65536 bytes ကြားရှိတဲ့ Fixed-size Block တစ်ခုဖြစ်ပြီး အမြဲတမ်း Power of Two (2^n) ဖြစ်ရပါတယ်။ Default ကတော့ 4096 bytes ပါ။ ဒါဟာ OS ရဲ့ Memory Page Size နဲ့ ကိုက်ညီတဲ့အတွက် I/O Layer မှာ အလွန်ထိရောက်မှု ရှိပါတယ်။
SQLite သိမ်းဆည်းတဲ့ Data မှန်သမျှ (Table rows, Index entries, Overflow content, Free space) ဟာ Page တစ်ခုခုထဲမှာပဲ တည်ရှိရပါတယ်။ Pages တွေကို ၁ ကနေ စပြီး နံပါတ်စဉ် တပ်ပါတယ် (၀ ကနေ မစတဲ့အတွက် Source Code ဖတ်ရင် သတိထားရပါတယ်)။ Page Number ဆိုတာ SQLite အတွက် Disk ပေါ်က တည်နေရာကို ညွှန်ပြတဲ့ နည်းလမ်းပါပဲ။ ဥပမာ- Page 7 ကို ဖတ်ချင်ရင် (7 - 1) * page_size ဆိုတဲ့ နေရာကို တိုက်ရိုက် Seek သွားပြီး page_size အတိုင်း ဖတ်လိုက်ရုံပါပဲ။
ဖိုင်တစ်ခုလုံးမှာ ပါဝင်တဲ့ Page အမျိုးအစားတွေကို အောက်ပါအတိုင်း ခွဲခြားနိုင်ပါတယ်- • B-tree pages: Table data တွေနဲ့ Index entries တွေကို သိမ်းဆည်းပါတယ်။ Database တစ်ခုမှာ အများဆုံးတွေ့ရတဲ့ အမျိုးအစားပါ။ • Overflow pages: B-tree page တစ်ခုထဲမှာ မဆံ့တော့တဲ့ ကြီးမားတဲ့ Content တွေ (ဥပမာ- Text blob အရှည်တွေ) ကို သိမ်းဆည်းပါတယ်။ • Freelist pages: ဖျက်လိုက်တဲ့အတွက် ပြန်သုံးလို့ရတဲ့ (Reuse) နေရာလွတ်တွေကို ခြေရာခံတဲ့ Page တွေပါ။ • Pointer map pages: Auto-vacuum mode မှာ ဖိုင်ကို ပြန်ချုံ့နိုင်အောင် Engine ကို ကူညီပေးပါတယ်။ • Lock-byte page: Windows OS ပေါ်က Legacy file locking အတွက် သီးသန့်ထားတဲ့ Special page တစ်ခုပါ။
Page 1: Database တစ်ခုလုံးရဲ့ Control Center
Page 1 ဟာ သာမန် B-tree page တစ်ခု မဟုတ်ပါဘူး။ သူ့ကို SQLite ရဲ့ “Source of Truth” လို့တောင် ခေါ်လို့ရပါတယ်။ ဘာကြောင့်လဲဆိုတော့ SQLite engine ဟာ ဖိုင်ကို စဖွင့်လိုက်တာနဲ့ တခြားဘာမှမလုပ်ခင် Page 1 ရဲ့ ပထမဆုံး 100 bytes ကို အရင်ဆုံး ဖတ်ရတာကြောင့်ပါ။ ဒါကို Database File Header လို့ ခေါ်ပါတယ်။
ဒီ 100-byte header ထဲမှာ file ရဲ့ DNA လို့ ပြောလို့ရမယ့် အရေးကြီး အချက်အလက်တွေကို Big-endian format နဲ့ သိမ်းထားပါတယ်။ အဓိက အစိတ်အပိုင်းတွေကို အသေးစိတ် ကြည့်ရအောင်-
- Bytes 0–15: Magic String
file ဟာ Null terminator ပါဝင်တဲ့ 16-byte ASCII string ဖြစ်တဲ့
SQLite format 3\000နဲ့ စတင်ပါတယ်။ ဒါဟာ ELF binary, PNG image ဒါမှမဟုတ် ZIP file တွေမှာ သုံးတဲ့ Magic Number လိုပါပဲ။ Tool တွေနဲ့ OS က ဒီ Sequence ကို ကြည့်တာနဲ့ File Type ကို ချက်ချင်း သိနိုင်ပါတယ်။ တကယ်လို့ ဒီ Sequence နဲ့ မစတင်ဘူးဆိုရင် SQLite က အဲဒီဖိုင်ကို ဖွင့်ဖို့ ငြင်းဆန်မှာ ဖြစ်ပါတယ်။ - Bytes 16–17: Page Size
Page size ကို သိမ်းထားတဲ့ 2-byte Big-endian integer တစ်ခုပါ။ သတိထားရမှာက Value
1လို့ ပြနေရင် Page size ဟာ65536ဖြစ်ပါတယ်။ (65536 ကို 16-bit အကန့်ထဲ ထည့်လို့မဆံ့တဲ့အတွက် ဒီလို သတ်မှတ်ထားတာပါ)။ ဒီ Value ကို Database ဖန်တီးကတည်းက Fix လုပ်ထားတာဖြစ်ပြီး၊ ပြောင်းလဲချင်ရင် Data တွေကို Dump ထုတ်ပြီး အစကနေ Rebuild ပြန်လုပ်ရမှာပါ။ တော်တော်များများအတွက်တော့ Default ဖြစ်တဲ့4096bytes ဟာ အသင့်တော်ဆုံးပါပဲ။ - Bytes 18–19: File Format Version
Write နဲ့ Read Format version နှစ်ခုအတွက် Single-byte value တွေ ဖြစ်ပါတယ်။ ဥပမာ-
1ဆိုရင် Rollback journal mode ဖြစ်ပြီး၊2ဆိုရင် WAL mode ကို ညွှန်ပြတာပါ။PRAGMA journal_mode=WALလို့ ပြောင်းလိုက်တာနဲ့ SQLite က Byte 19 ကို1ကနေ2ကို ပြောင်းလဲလိုက်ပါတယ်။ Connection အသစ်တစ်ခုက ဒီနေရာကို ကြည့်ရုံနဲ့ ဘယ် Journaling mode သုံးနေလဲဆိုတာ ချက်ချင်း သိနိုင်ပါတယ်။ - Bytes 24–27: File Change Counter Database ဖိုင်ကို Modify တစ်ကြိမ်လုပ်ပြီး Unlock လုပ်တိုင်း တိုးသွားတဲ့ 4-byte integer တစ်ခုပါ။ တခြား Process တစ်ခုက ဖိုင်ကို ပြောင်းလဲလိုက်သလားဆိုတာ သိဖို့ ဒီ Counter ကို သုံးပါတယ်။ ဒါဟာ Cache Invalidation ကို ချောမွေ့စေတဲ့ သက်သာလွယ်ကူတဲ့ နည်းလမ်းတစ်ခု ဖြစ်ပါတယ်။
- Bytes 28–31: Database Size in Pages ဖိုင်ထဲမှာ Page စုစုပေါင်း ဘယ်နှစ်ခုရှိတယ်ဆိုတာကို Header မှာ တိုက်ရိုက်သိမ်းထားပါတယ်။ ဖိုင်တစ်ခုခုကြောင့် Truncate ဖြစ်သွားရင် (ဖိုင်အရွယ်အစား ပျက်စီးသွားရင်) SQLite က ဒီ Value ကို ကြည့်ပြီး ဖော်ထုတ်နိုင်သလို၊ WAL mode Consistency စစ်ဆေးတဲ့အခါမှာလည်း ဒါကို အသုံးပြုပါတယ်။
- Bytes 32–39: Freelist Trunk Page နဲ့ Freelist Count SQLite ဟာ Data တွေကို ဖျက်လိုက်တဲ့အခါ ဖိုင်အရွယ်အစားကို ချက်ချင်း မချုံ့ပစ်ပါဘူး။ အဲဒီအစား ဖျက်လိုက်တဲ့ နေရာလွတ်တွေကို Freelist ထဲ ထည့်ထားပြီး နောက်မှ ပြန်သုံး (Reuse) ဖို့ သိမ်းထားပါတယ်။ ဒီ Field နှစ်ခုက Freelist ရဲ့ ပထမဆုံး Trunk page တည်နေရာနဲ့ Free page စုစုပေါင်း အရေအတွက်ကို ညွှန်ပြတာပါ။ Space အသစ် Allocate လုပ်ဖို့ လိုလာရင် SQLite ဟာ ဖိုင်ကို Extend မလုပ်ခင် ဒီ Freelist ကို အရင် စစ်ဆေးပါတယ်။
- Bytes 40–43: Schema Cookie Table တစ်ခုခုကို ဖန်တီးတာ၊ ဖျက်တာ ဒါမှမဟုတ် ပြောင်းလဲတာ လုပ်တိုင်း တိုးသွားတဲ့ Counter တစ်ခုပါ။ Connection တွေဟာ Performance အတွက် Schema ကို Memory ထဲမှာ Cache လုပ်ထားကြပါတယ်။ Header ထဲက Schema cookie နဲ့ မိမိရဲ့ Cache cookie မတူတော့ဘူးဆိုတာနဲ့ SQLite ဟာ Cache ကို ဖျက်ပြီး Schema ကို ပြန်ဆောက်ပါတယ်။
- Bytes 48–51: Default Page Cache Size
Page cache အတွက် Memory ဘယ်လောက် သုံးသင့်သလဲဆိုတဲ့ Hint တစ်ခုဖြစ်ပြီး
PRAGMA cache_sizeနဲ့ ကိုက်ညီပါတယ်။ Database ထဲမှာ Preferred setting ပါဝင်သွားစေဖို့ ဒီနေရာမှာ သိမ်းထားတာပါ။ - Bytes 56–59: Text Encoding
1ဆိုရင် UTF-8,2ဆိုရင် UTF-16 little-endian,3ဆိုရင် UTF-16 big-endian လို့ သတ်မှတ်ပါတယ်။ ဒါဟာလည်း ဖန်တီးချိန်မှာ Fix လုပ်ထားရတာဖြစ်ပြီး အထူးကိစ္စမရှိရင် UTF-8 ကသာ အသင့်တော်ဆုံး ဖြစ်ပါတယ်။ - Bytes 60–63: User Version ဒါကတော့ SQLite Engine က ဘယ်တုန်းကမှ မထိဘဲ Application Developer တွေအတွက် လုံးဝ လွှဲပေးထားတဲ့ 4-byte integer တစ်ခုပါ။ များသောအားဖြင့် Schema Migration တွေ ပြုလုပ်တဲ့အခါ Version Number မှတ်ဖို့ သုံးကြပါတယ်။
- Bytes 92–95: Application ID User version နဲ့ ဆင်တူပေမဲ့ Schema version ထက် Application Type ကို ခွဲခြားဖို့ ရည်ရွယ်တာပါ။ SQLite ပေါ် အခြေခံပြီး ကိုယ်ပိုင် File format တစ်ခု တည်ဆောက်မယ်ဆိုရင် ဒီ ID ကို ကြည့်ပြီး Tool တွေက ဘယ် Application ရဲ့ Format ဖြစ်တယ်ဆိုတာကို တိတိကျကျ ဖော်ထုတ်နိုင်မှာ ဖြစ်ပါတယ်။
Byte 100 ပြီးတဲ့နောက်မှာတော့ Page 1 ရဲ့ ကျန်တဲ့အပိုင်းဟာ ပုံမှန် B-tree page အဖြစ် ဆက်သွားပြီး တိတိကျကျ ပြောရရင် sqlite_schema table ရဲ့ Root Page အဖြစ် တာဝန်ယူပါတယ်။
Schema Table
Header ဖတ်ပြီးရင် SQLite ပထမဆုံး ရှာဖွေတာက Schema ပါ။ SQLite database တိုင်းမှာ sqlite_schema ဆိုတဲ့ Table တစ်ခု အမြဲပါဝင်ပါတယ်။ (အရင် Version တွေမှာတော့ sqlite_master လို့ ခေါ်ပါတယ်)။ Database ထဲမှာရှိတဲ့ Table, Index, View နဲ့ Trigger တိုင်းရဲ့ Definition တွေကို ဒီ Table ထဲမှာပဲ သိမ်းထားတာပါ။
ဒါဟာ တခြား Table တွေလိုပဲ တကယ့် Table အစစ်တစ်ခုဖြစ်ပြီး သူ့ရဲ့ Structure က ဒီလိုရှိပါတယ်-
CREATE TABLE sqlite_schema (
type TEXT, -- 'table', 'index', 'view', 'trigger'
name TEXT, -- object ရဲ့ နာမည်
tbl_name TEXT, -- ဆိုင်ရာ table
rootpage INTEGER, -- root B-tree page ရဲ့ page number
sql TEXT -- မူရင်း CREATE statement
);
ဒီနေရာမှာ အရေးကြီးဆုံးက rootpage column ပါပဲ။ Table တစ်ခုကို Query လုပ်ချင်တဲ့အခါ SQLite ဟာ sqlite_schema ထဲမှာ အရင်ဆုံး Lookup လုပ်ပြီး အဲဒီ Table ရဲ့ Root Page နံပါတ်ကို ရှာပါတယ်။ အဲဒီနောက်မှ Page နံပါတ်အတိုင်း လိုက်သွားပြီး B-tree ကို Navigate လုပ်ကာ Data တွေကို ဆွဲထုတ်တာ ဖြစ်ပါတယ်။
ဒါဟာ ဒီဇိုင်းအရ Self-describing ဖြစ်တယ်လို့ ဆိုနိုင်ပါတယ်။ SQLite မှာ External Catalog တွေ ဒါမှမဟုတ် အခြား Hardcoded System Table တွေ မလိုအပ်ပါဘူး။ Schema ဟာ Data တွေနဲ့အတူ Database ထဲမှာပဲ တစ်ပါတည်း တည်ရှိနေတာကြောင့် .db ဖိုင်တစ်ခုဟာ အလွန် Portable ဖြစ်ပြီး Self-contained ဖြစ်နေရခြင်း ဖြစ်ပါတယ်။
မှတ်သားထားရမှာက Schema Table ကိုယ်တိုင်ရဲ့ Root Page ဟာ Page 1 မှာ အမြဲရှိနေပါတယ်။ ဒါဟာ တစ်ခုတည်းသော Hardcoded ဖြစ်တဲ့ အချက်ပဲ ဖြစ်ပါတယ်။
Behind the Scenes: SQLite ဖိုင်တစ်ခုကို ဖွင့်လိုက်တဲ့အခါ နောက်ကွယ်မှာ ဘာတွေဖြစ်သွားသလဲ?
ဒါတွေအားလုံးကို ပေါင်းစပ်ကြည့်ရင် SQLite ဟာ Database ဖိုင်တစ်ခုကို Cold State (ဘာမှ မဖတ်ရသေးခင်) မှာ စတင်ဖွင့်လိုက်တဲ့အခါ ဘယ်လိုအဆင့်ဆင့် လုပ်ဆောင်သွားတယ်ဆိုတာကို အတိအကျ Trace လုပ်နိုင်ပါတယ်-
- Page 1 ကို ဖတ်ခြင်း: File ကို ဖွင့်လိုက်တာနဲ့ Page 1 ကို အရင်ဖတ်ပါတယ်။ ပထမဆုံး 100 bytes ကို Header Parser ဆီ ပို့ပြီး Magic String ကို စစ်ဆေးပါတယ်။ Magic String မကိုက်ညီရင် file ကို ငြင်းဆန်လိုက်ပြီး၊ file အသစ်ဖြစ်နေရင်တော့ Default Header Values တွေနဲ့ Initialize လုပ်ပါတယ်။
- Page Size သတ်မှတ်ခြင်း: Header ထဲက Page size ကို ဆွဲထုတ်လိုက်ပါတယ်။ အဲဒီနောက်ပိုင်း ဖတ်သမျှ Read operation အားလုံးကို အဲဒီ Chunk size အတိုင်းပဲ ဆောင်ရွက်သွားတော့မှာပါ။
- Journal Mode စစ်ဆေးခြင်း: Header ရဲ့ Byte 18–19 ကို ကြည့်ပြီး WAL mode ဖြစ်မဖြစ် စစ်ဆေးပါတယ်။ WAL mode ဆိုရင် သက်ဆိုင်ရာ
walဖိုင်ကို ရှာဖွေပြီး Validate လုပ်ပါတယ်။ - Schema ဖတ်ခြင်း: Page 1 (Header ရဲ့ နောက်ပိုင်း) မှာရှိတဲ့
sqlite_schemaB-tree ကို ဖတ်ပါတယ်။ Table တိုင်း၊ Index တိုင်းရဲ့ Root Page တွေကို Memory ထဲမှာ Schema Cache အဖြစ် တည်ဆောက်လိုက်ပါတယ်။ - Change Counter စစ်ဆေးခြင်း: တကယ်လို့ တခြား Connection တစ်ခုက ဖိုင်ကို ပြင်ဆင်သွားခဲ့ရင် Counter မတူတော့တဲ့အတွက် Cache Invalidation လုပ်ပြီး Schema ကို Reload ပြန်လုပ်ပါတယ်။
ဒီအဆင့်တွေ ပြီးသွားတဲ့အခါမှာတော့ Query တိုင်းအတွက် လိုအပ်တဲ့ အချက်အလက်တွေဖြစ်တဲ့ Page size, Schema layout နဲ့ Root page number တွေဟာ SQLite ဆီမှာ အသင့်ရှိနေပြီဖြစ်လို့ B-tree navigation ကို စတင်လုပ်ဆောင်နိုင်ပြီ ဖြစ်ပါတယ်။
Page Size ရွေးချယ်မှု : Performance အတွက် ဘာကို သိထားသင့်သလဲ?
လက်တွေ့ကျကျ မေးစရာတစ်ခု ပေါ်လာတာကတော့ “Page size ကို တကယ်ရော ပြောင်းလဲဖို့ လိုအပ်ပါသလား?” ဆိုတာပါပဲ။
ပုံမှန်အားဖြင့်တော့ Default ဖြစ်တဲ့ 4096 bytes ဟာ Workload အများစုအတွက် အသင့်တော်ဆုံး အဖြေ (Standard Answer) ဖြစ်ပါတယ်။ ဘာကြောင့်လဲဆိုတော့ ခေတ်သစ် Operating System တွေရဲ့ Memory Page Size နဲ့ ကိုက်ညီနေတာကြောင့် Read/Write လုပ်တဲ့အခါ Kernel နဲ့ သဘာဝကျကျ အပေးအယူတည့်ပြီး (Alignment ဖြစ်ပြီး) အလုပ်လုပ်နိုင်လို့ ဖြစ်ပါတယ်။
ဒါပေမဲ့ Page size ကို ပိုကြီးတဲ့ပမာဏ (8192 သို့မဟုတ် 16384 bytes) အဖြစ် ပြောင်းလဲဖို့ စဉ်းစားသင့်တဲ့ Scenario (၂) ခု ရှိပါတယ်-
- Large Sequential Reads: သင့်ရဲ့ Workload ဟာ Table ကြီးတွေကို Full-table scan အမြဲဖတ်နေရတယ်ဆိုရင် Page size ကြီးလေလေ I/O Operation အရေအတွက် နည်းလေလေဖြစ်ပြီး ပိုမြန်လာပါလိမ့်မယ်။ ဒါ့အပြင် B-tree ရဲ့ Depth (အလွှာ) ကလည်း ပိုတိမ်သွားတဲ့အတွက် Spinning Disk တွေမှာ Seek Overhead ကို လျှော့ချပေးနိုင်ပါတယ်။
- Large Blobs: Row တစ်ခုချင်းစီမှာ Overflow Page တွေဆီ ရောက်သွားစေမယ့် ကြီးမားတဲ့ Value တွေ (ဥပမာ- Text အရှည်ကြီးတွေ၊ ပုံတွေ) ခဏခဏ ပါနေတယ်ဆိုရင် Page size ကြီးတာက ပိုကောင်းပါတယ်။ Page တစ်ခုချင်းစီမှာ Data ပိုဆံ့သွားတဲ့အတွက် Overflow Chain တွေ နောက်ကို လိုက်ဖတ်ရတဲ့ အလုပ်ကို သက်သာစေလို့ ဖြစ်ပါတယ်။
တစ်ဖက်မှာလည်း Page size ကို သေးငယ်တဲ့ပမာဏ (512 သို့မဟုတ် 1024 bytes) အဖြစ် သတ်မှတ်တာမျိုးကတော့ RAM အလွန်အမင်း ချွေတာရတဲ့ Embedded System တွေမှာပဲ အဓိပ္ပာယ်ရှိပါတယ်။ Server ဒါမှမဟုတ် Desktop Application တွေအတွက်တော့ ဒါကို မပြုလုပ်သင့်ပါဘူး။
အဓိက သတိပြုရမယ့်အချက်ကတော့ Page size ဟာ Database ဖန်တီးပြီးသွားရင် ပြောင်းလဲလို့ မရတော့ပါဘူး။ ဒါကြောင့် အစကတည်းက မှန်ကန်စွာ ရွေးချယ်ဖို့ လိုအပ်ပါတယ်။ တကယ်လို့ ပြောင်းလဲချင်တယ်ဆိုရင်တော့ VACUUM INTO ကို သုံးပြီး Database ကို အစကနေ ပြန်ဆောက်ယူရမှာ ဖြစ်ပါတယ်။
ကိုယ်တိုင် လက်တွေ့စစ်ဆေးကြည့်လို့ရအောင်
အထက်မှာ ဖော်ပြခဲ့တဲ့ အချက်အလက်တွေကို သီအိုရီအရပဲ ယုံကြည်နေစရာ မလိုပါဘူး။ သင့်ရဲ့ SQLite database ထဲမှာ တကယ်ပဲ ဘယ်လိုစီစဉ်ထားသလဲဆိုတာကို အောက်ပါ Command တွေသုံးပြီး တိုက်ရိုက် စစ်ဆေးကြည့်နိုင်ပါတယ်။
ပထမဆုံးအနေနဲ့ PRAGMA command တွေကို သုံးပြီး Header ထဲက Metadata တွေကို ဆွဲထုတ်ကြည့်ရအောင်-
PRAGMA page_size;
PRAGMA page_count;
PRAGMA freelist_count;
PRAGMA user_version;
PRAGMA application_id;
PRAGMA schema_version;
ဒါမှမဟုတ် sqlite_schema table ကို တိုက်ရိုက် Query လုပ်ပြီး သင့်ရဲ့ Table တွေ ဘယ် Page နံပါတ်ကနေ စတင်သလဲဆိုတာကိုလည်း ကြည့်နိုင်ပါတယ်-
SELECT type, name, tbl_name, rootpage FROM sqlite_schema;
ဒီ Query ကို Run ကြည့်လိုက်ရင် Database ထဲမှာရှိတဲ့ Object တိုင်းနဲ့ သူတို့ရဲ့ Root Page Number တွေကို ရှင်းရှင်းလင်းလင်း မြင်တွေ့ရမှာ ဖြစ်ပါတယ်။
ပိုပြီး နက်နက်နဲနဲ လေ့လာချင်တယ်ဆိုရင်တော့ Hex Editor တစ်ခုခုကို သုံးပြီး ဖိုင်ရဲ့ ပထမဆုံး 100 Bytes ကို ကိုယ်တိုင် ဆင်းစစ်ကြည့်နိုင်သလို၊ SQLite distribution နဲ့အတူပါဝင်တဲ့ sqlite3_analyzer tool ကိုလည်း သုံးနိုင်ပါတယ်။ ဒီ Tool ဟာ Page တစ်ခုချင်းစီရဲ့ Space Usage အခြေအနေကို အသေးစိတ် ပြသပေးနိုင်ပါတယ်။
SQLite ရဲ့ File Format Specification ကိုယ်တိုင်ကလည်း sqlite.org/fileformat2.html မှာ အပြည့်အစုံ Documented ဖြစ်ပြီးသားပါ။ ဒါဟာ Open Source လောကမှာ အကောင်းဆုံးစာရင်းဝင်တဲ့ Documentation တစ်ခုဖြစ်လို့ SQLite ကို အလေးအနက် အသုံးပြုမယ့်သူတိုင်း တစ်ကြိမ်လောက်တော့ အနားယူရင်း ဖတ်ရှုကြည့်ဖို့ အကြံပေးချင်ပါတယ်။
ဒါတွေအားလုံးကို နားလည်ထားဖို့ ဘာကြောင့် အရေးကြီးတာလဲ?
အခုလို File Format အကြောင်းကို အသေးစိတ် လေ့လာတာဟာ ဗဟုသုတ (Trivia) သက်သက် မဟုတ်ပါဘူး။ ဒါဟာ SQLite ရဲ့ Behavior (အပြုအမူ) တိုင်းကို သဘောပေါက်နားလည်ဖို့အတွက် အခြေခံအုတ်မြစ် (Fundamental) ပဲ ဖြစ်ပါတယ်။
ကျွန်တော်တို့ ဒီ Series ရဲ့ နောက်ပိုင်းအဆင့်တွေ ရောက်တဲ့အခါ ဒီအချက်တွေက အခုလိုမျိုး အချိတ်အဆက်မိသွားပါလိမ့်မယ်-
- Part 6 (B-trees): B-tree တွေအကြောင်း လေ့လာတဲ့အခါမှာ Page Number တွေကတစ်ဆင့် ဘယ်လို Navigate လုပ်သလဲဆိုတာနဲ့ Root Page တွေကို
sqlite_schemaထဲမှာ ဘာကြောင့် မှတ်ထားရသလဲဆိုတာကို ဒီနေ့ကတည်းက ကြားဖူးနားဝ ရှိသွားပါပြီ။ - Part 12 (Rollback Journal): Data တွေကို ပြင်ဆင်တဲ့အခါ Page တွေကို ဘယ်လိုမျိုး ဘေးကင်းစွာ Modify လုပ်သလဲဆိုတာနဲ့ System Error တက်တဲ့အခါမှာ မူရင်း Page တွေကို ဘယ်လို Restore ပြန်လုပ်သလဲဆိုတဲ့ Mechanism ကို ရှင်းရှင်းလင်းလင်း မြင်လာပါလိမ့်မယ်။
- Part 13 (WAL Mode): Write-Ahead Log (WAL) အကြောင်း ပြောတဲ့အခါမှာ WAL file ဆိုတာ Page Image တွေ တစ်ခုပြီးတစ်ခု Append ဖြစ်နေတာဆိုတာကို သိထားတဲ့အတွက် Checkpointing (Main file ထဲ ပြန်ကူးတာ) ဆိုတဲ့ အယူအဆကို အလွယ်တကူ သဘောပေါက်သွားမှာပါ။
အနှစ်ချုပ်ရရင် SQLite ရဲ့ Performance ဖြစ်ဖြစ်၊ Concurrency ဖြစ်ဖြစ်၊ Recovery Mechanism ဖြစ်ဖြစ်၊ အရာအားလုံးဟာ နောက်ဆုံးမှာ မေးခွန်းတစ်ခုတည်းကိုပဲ ဖြေဆိုနေတာပါ-
“Disk ပေါ်က Pages တွေကို ဘယ်လိုမျိုး အမြန်ဆုံးနဲ့ စိတ်အချရဆုံး Read/Write လုပ်မလဲ?”
ဒီမေးခွန်းရဲ့ အဖြေဟာ အခုကျွန်တော်တို့ လေ့လာခဲ့တဲ့ Page Structure နဲ့ Header တွေကနေ စတင်တာပဲ ဖြစ်ပါတယ်။
ရှေ့ဆက်သွားမယ့် ခရီးစဉ်
Part-2 မှာတော့ SQLite ရဲ့ Type System ဟာ တခြား SQL Database တွေနဲ့ ဘာကြောင့် လုံးဝမတူတာလဲ? Dynamic Typing က Database တစ်ခုမှာ တကယ်ရော ဘယ်လို အလုပ်လုပ်သလဲ? ဆိုတာတွေကို Deep Dive လုပ်သွားပါမယ်။ ဒါ့အပြင် Developer တိုင်းကို အနည်းဆုံး တစ်ကြိမ်လောက်တော့ “ထောင်ချောက်” ဆင်ဖမ်းနိုင်တဲ့ SQLite ရဲ့ ထူးခြားတဲ့ Edge Cases တွေကိုလည်း အသေးစိတ် ဖော်ပြပေးသွားမှာပါ။