9 คะแนน โดย GN⁺ 2025-04-21 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ตารางเสมือนของ 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 ขั้นสุดท้ายและ cleanup
    • xRollback: ทำ 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.c

    disable_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 ความคิดเห็น

 
GN⁺ 2025-04-21
ความคิดเห็นจาก Hacker News
  • ชอบบทความเกี่ยวกับ vtabs นี้มาก ฉันได้ implement การรองรับ vtab ตอนที่กำลัง reimplement SQLite ด้วย Rust ด้วย ดังนั้นช่วงหลังมานี้เลยได้เรียนรู้อะไรเกี่ยวกับ vtab เยอะมาก vtab ทรงพลังมากและอาจยังถูกใช้งานไม่เต็มที่
  • น่าสนใจ แต่สิ่งนี้ใช้แพ็กเกจ go-sqlite3 ของ mattn ซึ่งเป็น CGO
    • สงสัยว่านี่เป็นข้อกำหนดที่พบได้ทั่วไปหรือเป็นสิ่งที่คาดหวังกันใน GO ยุคใหม่หรือไม่