ทรานแซกชันและตารางเสมือนใน SQLite
(misfra.me)- ตารางเสมือนของ SQLite รองรับการเขียนและทรานแซกชันได้เช่นกัน โดยใช้งานผ่านการ implement hook อย่าง
xUpdate,xSync,xCommit,xRollbackเป็นต้น - โดยพื้นฐานแล้ว SQLite รับประกันความเป็นอะตอมมิกด้วย rollback journal และเมื่อจัดการไฟล์ฐานข้อมูลหลายไฟล์ จะใช้ super-journal เพื่อควบคุมการ commit ทั้งหมด
- ตารางเสมือนก็เป็นส่วนหนึ่งของโปรโตคอลทรานแซกชันของ SQLite ดังนั้นหาก
xSyncล้มเหลว จะ rollback ทั้งทรานแซกชัน - การ commit แบ่งเป็น 2 ขั้น โดย
xSyncเป็นงานที่มีโอกาสล้มเหลวได้ ส่วนxCommitควรทำเพียงงาน cleanup เท่านั้น xCommitและxRollbackอาจถูกเรียกได้เสมอ จึงควรเขียนให้เป็นฟังก์ชันสำหรับ cleanup ที่ทำงานได้โดยไม่ล้มเหลว
ตารางเสมือนและการจัดการทรานแซกชันใน SQLite
ในบทความก่อนหน้า ได้แนะนำวิธีพื้นฐานในการลงทะเบียนและ query ตารางเสมือนของ SQLite ผ่านภาษา Go ไปแล้ว บทความนี้จะพูดถึง วิธี implement ตารางเสมือนที่เขียนได้และรองรับทรานแซกชัน
การรองรับการเขียนและทรานแซกชันของตารางเสมือน
-
อินเทอร์เฟซตารางเสมือนของ SQLite ไม่ได้เป็นแบบอ่านอย่างเดียว
-
หาก implement hook
xUpdateก็จะ เขียนไปยังแหล่งข้อมูลภายนอกได้ด้วย -
หากต้องการความสอดคล้องของทรานแซกชันอย่างแท้จริง จำเป็นต้องมี transaction hook ต่อไปนี้:
xBegin: แจ้งเริ่มทรานแซกชันxSync: เตรียมความพร้อมเพื่อ commit ลงดิสก์อย่างปลอดภัย (ถ้าล้มเหลวที่นี่จะ rollback ทั้งหมด)xCommit: commit ขั้นสุดท้ายและ cleanupxRollback: ทำ rollback เมื่อทรานแซกชันถูกยกเลิก
-
แม้จะมีการแก้ไขพร้อมกับตารางปกติหรือตารางเสมือนอื่น SQLite ก็ยัง ประสาน hook ทั้งหมดเพื่อรับประกันความเป็นอะตอมมิก
กลไกภายในของทรานแซกชันใน SQLite
Rollback journal
- โดยพื้นฐานแล้ว SQLite จะ บันทึกหน้าเพจไว้ในไฟล์สำรอง (journal) ก่อนเขียนทับ
- หากเกิดปัญหา ก็จะกู้คืนจาก journal เพื่อ รับประกันความเป็นอะตอมมิก
หมายเหตุ: SQLite รองรับโหมด WAL เช่นกัน แต่ไม่อยู่ในขอบเขตของบทความนี้
Super-journals
-
เมื่อมีการเชื่อมต่อฐานข้อมูลหลายตัว การมี journal แยกในแต่ละ DB เพียงอย่างเดียวทำให้ซิงก์กันได้ยาก
-
จึงใช้ไฟล์ระดับบนที่เรียกว่า super-journal เพื่อประสานการ commit ข้ามหลายไฟล์
-
แต่หากจัดการเฉพาะหลายตารางเสมือนภายในไฟล์ DB เดียว ก็ ซิงก์กันได้โดยไม่ต้องใช้ super-journal
-
ไม่ว่าในกรณีใด SQLite จะ เรียก hook
xSync,xCommit,xRollbackให้โดยอัตโนมัติภายใน flow ของทรานแซกชัน
Two-phase commit เมื่อใช้ร่วมกับตารางเสมือน
กระบวนการ commit ของ SQLite แบ่งเป็น 2 ขั้น:
ขั้นที่ 1: xSync (รับประกัน Durability)
- ซิงก์ทุกหน้าเพจหรือ journal ของทุก B-Tree และไฟล์ DB ลงดิสก์อย่างปลอดภัย
- ตารางเสมือนแต่ละตัวก็จะถูกเรียก hook
xSyncเช่นกัน - หาก
xSyncตัวใดตัวหนึ่งล้มเหลว จะ rollback ทั้งทรานแซกชัน → คงความเป็นอะตอมมิกไว้
ขั้นที่ 2: cleanup (xCommit)
-
เมื่อบันทึกลงดิสก์เสร็จแล้ว ก็จะ ลบไฟล์ journal และทำ cleanup ให้ตารางเสมือน
-
ด้านล่างคือโค้ดบางส่วนจาก
vdbeaux.cdisable_simulated_io_errors(); sqlite3BeginBenignMalloc(); for(i=0; i<db->nDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ sqlite3BtreeCommitPhaseTwo(pBt, 1); } } sqlite3EndBenignMalloc(); enable_simulated_io_errors(); sqlite3VtabCommit(db); -
ภายใน
sqlite3VtabCommit()นั้น แม้การเรียกxCommitทั้งหมดจะล้มเหลวก็จะถูกมองข้าม → เป็นขั้น cleanup ล้วน ๆint sqlite3VtabCommit(sqlite3 *db){ callFinaliser(db, offsetof(sqlite3_module,xCommit)); return SQLITE_OK; } -
เนื่องจาก durability ได้รับการรับประกันไปแล้วด้วย
xSyncจึงมองข้ามความล้มเหลวของxCommitและxRollbackได้
ข้อควรระวังสำหรับผู้พัฒนาตารางเสมือน
- งานที่มีผลต่อความคงทนของข้อมูลต้องอยู่ใน
xSyncเท่านั้น- งานอย่าง network I/O, การเขียนไฟล์ และงานอื่นที่อาจล้มเหลว ควรถูกจัดการที่นี่เพื่อให้ยกเลิกทรานแซกชันได้อย่างปลอดภัย
- แม้หลัง
xSyncแล้วxRollbackก็ยังอาจถูกเรียกได้- หาก
xSyncของตารางอื่นล้มเหลว ก็จะ rollback ทั้งหมด
- หาก
xCommitและxRollbackควรถูกเขียนให้เป็นฟังก์ชัน cleanup ที่ไม่ล้มเหลว- ควรเป็นแบบ idempotent (ให้ผลเดิมเสมอ) คือถูกเรียกหลายครั้งแล้วไม่ทำให้สถานะเปลี่ยน
สรุป
- กลไก journaling ของ SQLite รับประกัน atomic commit ขององค์ประกอบทั้งหมด รวมถึงตารางปกติและตารางเสมือน
- transaction hook ของตารางเสมือนถูก ผสานเข้ากับ flow ทรานแซกชันของ SQLite อย่างเป็นธรรมชาติ
- นักพัฒนาที่ implement ตารางเสมือนควร โฟกัสที่
xSyncเพื่อรับประกันความถูกต้องของข้อมูล และแยกงาน cleanup ไปไว้ในxCommitกับxRollback
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News