ข้าม​ไป​ยัง​เนื้อหา

ของ​จริง​เลือก​ทาง​ไหน — และ​ทำไม anomaly ต้อง​มา​จาก​เอนจินของ​เรา​เอง

บท​นี้​ไม่มี​โครงสร้าง​ใหม่​ให้​สร้าง มี​แต่​การ​เอา​ของ​ที่​ประกอบ​มา​เจ็ด​บท​ไป​วาง​ข้าง​ของ​จริง​แล้ว​อ่าน​ผล

คำถาม​ที่​ต้อง​ตอบ​ให้​ได้​ไม่ใช่​ว่า​ใคร​ถูก แต่​คือ ของ​จริง​เลือก​จ่าย​ด้วย​อะไร​แทน

และ​คำ​ตอบ​ของ SQLite นั้น​ตรง​ข้าม​กับ​ที่​คน​ส่วน​ใหญ่​คาด มัน​ไม่​ได้​แก้​ปัญหา​ที่​ทั้ง​คอร์ส​นี้​อธิบาย​อยู่ มัน​ตัด​ฉาก​ที่​ทำให้​ปัญหา​เกิด​ทิ้ง​ไป​เลย

📦 code ของ​บท​นี้

บท​นี้​อ่าน​ตัวเลข​จาก​เครื่อง​มือสอง​ตัว​ที่​คนละ​ภาษา​กัน และ​เป็น​เรื่อง​ที่​ต้อง​รู้​ก่อน​อ่าน​ตัวเลข​ทุก​ตัว

ตัว​แรก​คือ crate ของ​คอร์ส​ที่ scripts/transaction-engine/ เขียน​ด้วย Rust standard library ล้วน ผล​รัน​ของ​มัน​ตรึง​ไว้​ที่ expected/08-what-real-engines-do.txt และ​มี​เทสต์เทียบ​ที​ละ byte

ตัว​ที่​สอง​คือ scripts/isolation-spike/probe.py ซึ่ง​เป็น Python เพราะ crate เป็น std ล้วน จึง link กับ SQLite ไม่​ได้ ตรวจ​ผล​ของ​มัน​ด้วย npm run verify:sqlite-isolation

หัวข้อ​นี้​เอา​ผล​ของ​สอง​การ​ทดลอง​ที่​ระดับ snapshot กับ serializable มา​วาง​เทียบ​กัน​บรรทัด​ต่อ​บรรทัด

บท​ที่ 5 กับ​บท​ที่ 6 รัน​การ​ทดลอง​เดียวกัน​คนละ​ระดับ แล้ว​รายงาน​คนละ​หน้า บท​นี้​เรียก​ทั้ง​สอง​ระดับ ใน​การ​รัน​เดียว เพื่อ​ให้​ความ​ต่าง​อยู่​ใน​สายตา​พร้อม​กัน

src/demos.rs · เรียก​การ​ทดลอง​เดิม​สอง​รอบ รอบ​ละ​ระดับ
pub fn ch08() {
println!("== บทที่ 8 สรุปทั้งคอร์ส แล้วเทียบกับของจริง ==");
println!(" เอนจินของเรา · write skew");
for level in [Level::Snapshot, Level::Serializable] {
let r = write_skew(level);
println!(
" {:<13} เหลืออยู่เวร {} คน · abort {} · กฎ{}",
r.level.label(),
r.on_shift,
r.aborts,
if r.invariant_held { "อยู่" } else { "พัง" }
);
}

ไม่มี branch ไหน​แยก​ตาม​ระดับ​ใน code ที่​พิมพ์​รายงาน ระดับ​ถูก​ส่ง​เข้าไป​เป็น​ค่าตัว​เดียว ความ​ต่าง​ทั้งหมด​ที่​เห็น​ข้าง​ล่าง​จึง​มา​จาก​เอนจิน ไม่​ได้​มา​จาก​ผู้​รายงาน

cargo run --quiet -- 08
== บทที่ 8 สรุปทั้งคอร์ส แล้วเทียบกับของจริง ==
เอนจินของเรา · write skew
snapshot เหลืออยู่เวร 0 คน · abort 0 · กฎพัง
serializable เหลืออยู่เวร 1 คน · abort 1 · กฎอยู่
เอนจินของเรา · lost update (คำตอบเรียงลำดับ 180)
snapshot ยอด 130 · abort 0
serializable ยอด 150 · abort 1

สอง​บรรทัด​บน​คือ​ธีสิส​ของ​คอร์ส​ใน​รูป​ที่​สั้น​ที่สุด ที่​ระดับ snapshot ไม่มี​ใคร​ถูก​ปฏิเสธ​สัก​คน และ​ไร​เด​อร์หาย​ไป​จาก​เวร​ทั้ง​สอง​คน ที่​ระดับ serializable มี​คน​ถูก​ปฏิเสธ​หนึ่ง​คน แล้ว​กฎ​ก็​รอด

สอง​บรรทัด​ล่าง​คือ​บรรทัด​ที่​ขาย​ยาก​กว่า คำ​ตอบ​ที่​ถูก​ถ้า​รัน​เรียง​ที​ละ​ตัว​คือ 180 ระดับ snapshot ให้ 130 ส่วน​ระดับ serializable ให้ 150 ไม่มี​ระดับ​ไหน​ให้ 180

นี่​คือ​จุด​ที่​คำ​ว่า “ระดับ​สูง​กว่า​คือ​ถูกต้อง​กว่า” พัง​ลง สิ่ง​ที่ serializable เปลี่ยน​ไม่ใช่​ยอด มัน​เปลี่ยน​คำ​ตอบ​ที่​ผิด​เงียบๆ ให้​กลาย​เป็น​งาน​ที่​ถูก​ปฏิเสธ​และ​มี​คน​รู้ตัว

ค่า 150 ไม่ใช่ bug ของ​เอนจิน มัน​คือ​ผล​ของ​การ​รัน​เรียง​ลำดับ​ที่​มี transaction เดียว​รอด งาน​ที่​เหลือ​คือ retry ซึ่ง​เอนจิน​ไม่มี​ทาง​เขียน​แทนคุณ​ได้ เพราะ​มัน​ไม่รู้​ว่า​คุณ​ตั้งใจ​จะ​บวก​อะไร

ตัวเลข​ฝั่ง SQLite ไม่​ได้​ออก​มา​จาก​โปรแกรม​เดียวกัน

หัวข้อ​ที่​มีชื่อ​ว่า “ตัวเลข​ฝั่ง SQLite ไม่​ได้​ออก​มา​จาก​โปรแกรม​เดียวกัน”

หัวข้อ​นี้​บอกว่า​ตัวเลข​สอง​ชุด​ใน​บท​นี้​มา​จาก​เครื่องมือ​คนละ​ตัว​คนละ​ภาษา และ​โปรแกรม​พูด​ข้อ​นี้​เอง

ทุก​บรรทัด​ข้าง​บน​ออก​มา​จาก crate ของ​คอร์ส บรรทัด​สุดท้าย​ที่​โปรแกรม​เดียวกัน​พิมพ์ คือ​การ​ประกาศ​ว่า​มัน​วัด​ฝั่ง SQLite ให้​ไม่​ได้

src/demos.rs · โปรแกรม​ประกาศ​ขอบเขต​ของ​ตัวเอง
println!();
println!(" ตัวเลขฝั่ง SQLite ไม่ได้อยู่ในโปรแกรมนี้ · crate นี้เป็น std ล้วน คุยกับ SQLite ไม่ได้");
println!(" ของฝั่งนั้นวัดด้วย scripts/isolation-spike/ กับ scripts/write-amp/ ซึ่ง commit แยกไว้");
}
cargo run --quiet -- 08
ตัวเลขฝั่ง SQLite ไม่ได้อยู่ในโปรแกรมนี้ · crate นี้เป็น std ล้วน คุยกับ SQLite ไม่ได้
ของฝั่งนั้นวัดด้วย scripts/isolation-spike/ กับ scripts/write-amp/ ซึ่ง commit แยกไว้

คำ​สัญญา​ข้อ​แรก​ของ​คอร์ส​คือ crate นี้​ไม่มี external crate สัก​ตัว ซึ่ง​แปล​ว่า​มัน link กับ SQLite ไม่​ได้ ทาง​เลือก​จึง​มี​สอง​ทาง คือ​ผิด​คำ​สัญญา หรือ​แยก​เครื่องมือ

คอร์ส​นี้​เลือก​ทาง​ที่​สอง ตัวเลข​ฝั่ง SQLite ทุก​ตัว​ใน​หน้า​นี้​มา​จาก scripts/isolation-spike/probe.py ซึ่ง​เป็น Python และ​ตรวจ​ซ้ำ​ด้วย npm run verify:sqlite-isolation

⚠ อย่า​อ่าน​ตัวเลข​สอง​ชุด​นี้​เป็น​ชุด​เดียวกัน

ตัวเลข​ของ​เอนจินกับ​ตัวเลข​ของ SQLite ไม่​ได้​ผลิต​จาก​โปรแกรม​เดียวกัน และ​วัด​คนละ​สิ่ง

ตัวเลข​ของ​เอนจินตอบ​ว่า ของ​ที่​เรา​สร้าง​ทำ​อะไร​ได้​บ้าง ส่วน​ตัวเลข​ของ SQLite ตอบ​ว่า ของ​จริง​ยอม​ให้​เกิด​อะไร​บ้าง เอา​มา​ลบ​กัน​หรือ​หาร​กัน​ไม่​ได้

ภาษา​ของ​เครื่องมือ​วัด​ไม่ใช่​ข้อ​อ้าง​ของ​คอร์ส probe.py วัด SQLite มัน​ไม่​ได้​วัด​เอนจินของ​เรา เทสต์ประจำ​คอร์ส​บังคับ​เฉพาะ crate ส่วน​บล็อก​ผล​รัน​ของ probe ตรวจ​ด้วย​มือ​กับ scripts/isolation-spike/expected/sqlite-isolation.txt

หัวข้อ​นี้​แสดง​ผล​วัด​จริง​ว่า​ผู้​เขียน​คน​ที่​สอง​ถูก​ปฏิเสธ​ทั้ง​สอง journal mode จึง​ตั้ง​ฉาก​ของ anomaly ไม่​ขึ้น

probe เปิด​สอง connection ค้าง​ไว้​พร้อม​กัน แล้ว​ถาม​คำถาม​เดียวกัน​สอง​รอบ รอบ​ละ journal mode คำถาม​คือ SQLite ยอม​ให้​เกิด​อะไร​บ้าง​ระหว่าง​สอง​คน​นั้น

npm run verify:sqlite-isolation
journal_mode = delete
writer ขณะ reader เปิดค้าง OperationalError: database is locked
reader เห็น (2, 2)
non-repeatable read False
สองคนเขียนพร้อมกัน OperationalError: database is locked
journal_mode = wal
writer ขณะ reader เปิดค้าง ผ่าน
reader เห็น (2, 2)
non-repeatable read False
สองคนเขียนพร้อมกัน OperationalError: database is locked

สอง​ทาง​นี้​ต่าง​กัน​จริง​หนึ่ง​บรรทัด ที่ delete ผู้​เขียน​ที่มา​ระหว่าง​มี​ผู้​อ่าน​เปิด​ค้าง​ถูก​ปฏิเสธ ส่วน​ที่ wal มัน​ผ่าน นี่​คือ​สิ่ง​ที่ WAL ซื้อ​มา​ให้ ผู้​อ่าน​ไม่​บล็อก​ผู้​เขียน​อีก​ต่อ​ไป

แต่​บรรทัด​สุดท้าย​เหมือน​กัน​ทั้ง​สอง​ทาง สอง​คน​เขียน​พร้อม​กัน​ได้ database is locked เท่า​กัน และ​เอกสาร​ของ SQLite เอง​อธิบาย​เหตุผลไว้ตรงๆ ว่า “since there is only one WAL file, there can only be one writer at a time”

write skew ต้องการ​ฉาก​ที่​เจาะจง​มาก คือ​สอง​คน​อ่าน​ภาพ​เดียวกัน เขียน​คนละ​แถว แล้ว commit ได้​ทั้ง​คู่ บน SQLite คน​ที่​สอง​ไม่​เคย​ไป​ถึง​ขั้น​เขียน ฉาก​นั้น​จึง​ตั้ง​ไม่​ขึ้น​ตั้งแต่​ต้น

นี่​ไม่ใช่​ข้อ​จำกัด​ของ probe และ​ไม่ใช่ bug ของ SQLite มัน​คือ​การ​ออกแบบ SQLite เลือก​จ่าย ด้วย​การ​มี​ผู้​เขียน​ได้ที​ละ​คน​เดียว แล้ว​ได้​ความ​ง่าย​ทั้ง​ก้อน​กลับ​มา

ข้อ​สรุป​ที่​กลับ​ด้าน​กับ​คอร์ส​ก่อนหน้า​ใน​เสา​เดียวกัน

หัวข้อ​ที่​มีชื่อ​ว่า “ข้อ​สรุป​ที่​กลับ​ด้าน​กับ​คอร์ส​ก่อนหน้า​ใน​เสา​เดียวกัน”

หัวข้อ​นี้​เทียบ​รูปทรง​ของ capstone สอง​บท และ​อธิบาย​ว่า​ทำไม anomaly ทุก​ตัว​ใน​คอร์ส​นี้​ต้อง​มา​จาก​เอนจินที่​เรา​สร้าง​เอง

คอร์ส​ก่อนหน้า​ใน​เสา​นี้​ปิด​ด้วย​การ​เทียบ​กับ SQLite เหมือน​กัน แต่​มัน​เทียบ​เพื่อ​ถาม​ว่า planner ของ​เรา​เลือก​แผน​เดียว​กับ SQLite ไหม ป้อน​คำถาม​ชุด​เดียวกัน​สอง​ฝั่ง แล้ว​อ่าน​แผน​ทั้ง​สอง

ที่​นั่น​สอง​ฝั่ง​ตอบ​ได้​ทั้ง​คู่ ทุก​จุด​ที่​ไม่​ตรง​กัน​ย้อน​กลับ​ไป​หา​บท​ที่​อธิบาย​มัน​ได้ อ่าน​ได้ที่ capstone — planner ของ​คุณ เทียบ​กับ sqlite3

คอร์ส​นี้​เทียบ​ด้วย​รูปทรง​เดียวกัน แล้ว​ได้​ผลกลับ​ด้าน ฝั่ง SQLite ไม่มี​คำ​ตอบ​ให้​เทียบ เพราะ​มัน​ปฏิเสธ​การ​ทดลอง​ตั้งแต่​ก้าว​ที่​สอง

นั่น​คือ​คำ​ตอบ ไม่ใช่​ความ​ล้มเหลว​ของ​การ​ทดลอง SQLite เลือก​จ่าย​ด้วย​ผู้​เขียน​คน​เดียว ปัญหา​ที่​ทั้ง​คอร์ส​นี้​อธิบาย​จึง​ไม่​เคย​เกิด​กับ​มัน

และ​ข้อ​นี้​อธิบาย​ว่า​ทำไม​เจ็ด​บท​ที่​ผ่าน​มา​ต้อง​สร้าง​เอนจินขึ้นมา​เอง จะ​ยืม​ฐาน​ข้อมูล​จริง​มา​โชว์ anomaly ไม่​ได้ เพราะ​ฐาน​ข้อมูล​ที่​ยอม​ให้ anomaly เกิด คือ​ฐาน​ข้อมูล​ที่​ต้อง​มี​สอง​คน​เขียน​พร้อม​กัน​ได้​ก่อน

flowchart TD
  subgraph QE["คอร์ส query engine · เทียบ planner"]
    Q1["query ชุดเดียวกัน"] --> Q2["planner ของเรา"]
    Q1 --> Q3["planner ของ SQLite"]
    Q2 --> Q4["ได้แผนสองฝั่ง<br/>อธิบายทุกจุดที่ต่างกันได้"]
    Q3 --> Q4
  end
  subgraph TE["คอร์สนี้ · เทียบ isolation"]
    T1["ไรเดอร์สองคนถอนตัวพร้อมกัน"] --> T2["เอนจินของเรา"]
    T1 --> T3["SQLite"]
    T2 --> T4["write skew เกิดจริง<br/>เหลืออยู่เวร 0 คน · กฎพัง"]
    T3 --> T5["database is locked<br/>ผู้เขียนคนที่สองไม่ได้เขียน"]
  end
  T5 -.->|"ไม่มีอะไรให้เทียบ จึงต้องสร้างเอง"| T2

คำ​บรรยาย​ภาพ: คอร์ส query engine ได้​คำ​ตอบ​จาก​สอง​ฝั่ง​แล้ว​เอา​มา​เทียบ​กัน​ได้ · คอร์ส​นี้​ได้​คำ​ตอบ​จาก​ฝั่ง​เดียว เพราะ SQLite ปฏิเสธ​ผู้​เขียน​คน​ที่​สอง · เส้น​ประ​คือ​เหตุผล​ที่ anomaly ทุก​ตัว​ใน​คอร์ส​นี้​ต้อง​มา​จาก​เอนจินที่​เรา​สร้าง​เอง

🔁 รู​ที่​คอร์ส​ก่อนหน้า​ประกาศ​ไว้​เอง

คอร์ส query engine เขียน​เส้น​ขอบเขต​ของ​ตัวเอง​ไว้​ใน​หน้า​แรกว่า “ไม่​ได้​สร้าง​ฐาน​ข้อมูล​ที่​ใช้งาน​ได้ ไม่มี SQL parser ไม่มี transaction ไม่มี WAL ไม่มี MVCC ไม่มี concurrency”

คอร์ส​นี้​คือ​ชั้น​ที่​ประโยค​นั้น​ตัด​ออก และ​ตอน​นี้​คุณ​เห็น​แล้ว​ว่า​ทำไม​มัน​ต้อง​เป็น​คอร์ส​แยก สาม​คำ​ที่​มัน​ตัด​ทิ้ง​คือ​สิ่ง​ที่​กิน​เวลา​ทั้ง​แปดบท​ของ​ที่​นี่

สอง​คอร์ส​ไม่​ได้​ใช้ code ร่วม​กัน​สัก​บรรทัด อ่าน​สลับ​ลำดับ​กัน​ได้ อ่าน​ที่ สร้าง query engine ตั้งแต่​ศูนย์

หัวข้อ​นี้​ยก​คำ​ใน​เอกสาร​ของ PostgreSQL ที่​ระบุ​ว่า Repeatable Read ของ​มัน​คือ snapshot isolation และ Serializable ของ​มัน​คือ SSI

SQLite ตัด​ฉาก​ทิ้ง PostgreSQL ไม่​ได้​ตัด มัน​ยอม​ให้​หลาย​คน​เขียน​พร้อม​กัน แล้ว​รับ​ปัญหา​ทั้ง​ชุด ที่​คอร์ส​นี้​อธิบาย​มา​เป็น​งาน​ของ​ตัวเอง

เอกสาร​ของ​มัน​บอก​วิธี​แก้ไว้ตรงๆ สอง​ประโยค ประโยค​แรก​คือ “The Repeatable Read isolation level is implemented using a technique known in academic database literature and in some other database products as Snapshot Isolation”

ประโยค​ที่​สอง​คือ “The Serializable isolation level is implemented using a technique known in academic database literature as Serializable Snapshot Isolation, which builds on Snapshot Isolation by adding checks for serialization anomalies”

อ่าน​สอง​ประโยค​นี้​แล้ว​ย้อน​ดู​บท​ที่ 5 กับ​บท​ที่ 6 จะ​เห็น​ว่า​เรา​กับ​เขา​เดินทาง​เดียวกัน ชื่อ​ระดับ​ต่าง​กัน แต่​กลไก​ใต้​ชื่อ​อยู่​ใน​ตระกูล​เดียวกัน

“ตระกูล​เดียวกัน” ไม่​ได้​แปล​ว่า​เหมือน​กัน และ​สอง​ช่องว่าง​ที่​เหลือ​คือ​ของ​ที่​บท​ก่อนหน้า บอก​ไว้​แล้ว

  • Level::Snapshot ของ​เรา ไม่มี​ด่าน​กัน​ผู้​เขียน​สอง​คน​ที่​คีย์​เดียวกัน​เลย ส่วน PostgreSQL ปฏิเสธ​ผู้​เขียน​คน​ที่​สอง​ด้วย​ข้อความ could not serialize access due to concurrent update — ตาราง​ที่​ให้​ยอด 130 โดย​ไม่มี abort จึง​เป็น​ผล​ของ​เอนจิน​เรา ไม่ใช่​ผล​ของ Repeatable Read
  • SSI ของ​เรา abort ตั้งแต่​พบ​ความ​สัมพันธ์​ทอด​แรก ส่วน​ของ​จริง​รอ​ให้​ครบ​สอง​ทอด เรา​จึง​ปฏิเสธ​งาน​บาง​ชิ้น​ที่​ปลอดภัย​อยู่​แล้ว

ตาราง​ใน​เอกสาร​เดียวกัน​ระบุ​ผล​ของ​สอง​ระดับ​นั้น​ไว้​ชัด และ​ช่อง​ขวา​สุด​คือ​ช่อง​ที่​ทั้ง​คอร์ส​นี้​พูด​ถึง

ระดับ​ของ PostgreSQLdirty readnon-repeatable readphantom readserialization anomaly
Repeatable Readเกิด​ไม่​ได้เกิด​ไม่​ได้มาตรฐาน​ยอม​ให้​เกิด แต่​ไม่​เกิด​ใน PostgreSQLเกิด​ได้
Serializableเกิด​ไม่​ได้เกิด​ไม่​ได้เกิด​ไม่​ได้เกิด​ไม่​ได้

ช่อง เกิด​ได้ ช่อง​เดียว​นั้น​คือ​ที่นั่ง​ของ write skew ฐาน​ข้อมูล​ที่​ใช้​กัน​อยู่​จริง​ประกาศ​ไว้​ใน​ตาราง ของ​ตัวเอง​ว่าที่​ระดับ​นั้น​มัน​ปล่อย​ผ่าน

และ​เมื่อ​ขึ้น​ไป​ระดับ Serializable ราคา​ก็​มา​ใน​รูป​เดิม​กับ​ที่​เอนจินของ​เรา​เก็บ คือ​การ​ปฏิเสธ​งาน เอกสาร​ระบุ​ข้อความ​ที่​มัน​คืน​กลับ​มา​ไว้​ว่า could not serialize access due to read/write dependencies among transactions พร้อม​รหัส SQLSTATE 40001

โปรแกรม​ที่​ตั้ง Serializable แล้ว​ไม่​ได้​เขียน​ทาง​รับ​ข้อความ​นี้ ก็​แค่​ย้าย​จุด​ที่​จะ​พัง​จาก​ยอด​ที่​ผิด ไป​เป็น exception ที่​ไม่มี​ใคร​จับ

งาน​ปิด​คอร์ส — รัน​สอง​ระดับ แล้ว​อธิบาย​ทุก​ความ​ต่าง

หัวข้อ​ที่​มีชื่อ​ว่า “งาน​ปิด​คอร์ส — รัน​สอง​ระดับ แล้ว​อธิบาย​ทุก​ความ​ต่าง”

หัวข้อ​นี้​ให้​งาน​ที่​ตรวจ​ตัวเอง​ได้ คือ​รัน​ทั้ง​ชุด​แล้ว​อธิบาย​ที่มา​ของ​ทุก​บรรทัด​ที่​ต่าง​กัน

เกณฑ์​ผ่าน​ของ​คอร์ส​นี้​ไม่ใช่​การ​อ่าน​จบ มัน​คือ​การ​อธิบาย​ทุก​ตัวเลข​ใน​ตาราง​สรุป​ได้​ว่า​มา​จาก​ไหน ทำ​สี่​ข้อ​นี้​ตาม​ลำดับ

  1. รัน cargo run --quiet -- 08 แล้ว​เทียบ​กับ expected/08-what-real-engines-do.txt ที​ละ​บรรทัด ถ้า​ไม่​ตรง แปล​ว่า​มี​อะไร​ผิด ไม่ใช่​แล้ว​แต่​เครื่อง
  2. เขียน​คำ​อธิบาย​สี่​บรรทัด​ของ​ตาราง​นั้น บรรทัด​ละ​หนึ่ง​ประโยค ตอบ​ว่า​บท​ไหน​ผลิต​มัน อะไร​เปลี่ยน​ระหว่าง​สอง​ระดับ และ​ใคร​เป็น​คน​จ่าย​ราคา
  3. เปิด src/demos.rs ที่ write_skew แล้ว​สลับ​ลำดับ commit ของ t1 กับ t2 ทำนาย​ก่อน​ว่า​ใคร​จะ​ถูก abort แล้ว​ค่อย​รัน​บท​ที่ 6 เพื่อ​ตรวจ​คำ​ทำนาย
  4. รัน npm run verify:sqlite-isolation บน​เครื่อง​ตัวเอง บรรทัด [env] จะ​ไม่​ตรง​กับ​ของ​เรา เพราะ SQLite คนละ​รุ่น ตัว​ตรวจ​ตัด​บรรทัด​นั้น​ทิ้ง​ก่อน​เทียบ​อยู่​แล้ว

ข้อ 3 คือ​ข้อ​ที่​วัด​ความ​เข้าใจ​จริง ถ้า​ทำนาย​ถูก แปล​ว่า​คุณ​เข้าใจ​แล้ว​ว่า SSI ไม่​ได้​เลือก​เหยื่อ จาก​ความ​ถูกต้อง มัน​เลือก​จาก​ลำดับ

ถ้า​อธิบาย​บรรทัด​ไหน​ไม่​ได้ อย่า​ข้าม บรรทัด write skew ย้อน​ไป​บท​ที่ 5 กับ​บท​ที่ 6 ส่วน​บรรทัด lost update ย้อน​ไป​บท​ที่ 2

หัวข้อ​นี้​ระบุ​สาม​อย่าง​ที่​จงใจ​ตัด​ออก และ​บอกว่า​แต่ละ​อย่าง​ไป​อ่าน​ต่อ​ที่ไหน

เอนจินที่​คุณ​เพิ่ง​สร้าง​รัน​ได้​จริง​และ​สาธิต anomaly ได้​จริง แต่​มัน​ไม่ใช่​ฐาน​ข้อมูล สาม​ข้อ​ข้าง​ล่าง​คือ​ของ​ที่​หาย​ไป และ​ไม่มี​ข้อ​ไหน​เป็น​บท​ที่ 9

  • ไม่มี distributed transaction ไม่มี two-phase commit และ​ไม่มี consensus ทุก​อย่าง​ใน​คอร์ส​นี้ เกิด​บน​เครื่อง​เดียว process เดียว เรื่อง​ข้าม​เครื่อง​อยู่​ที่ ระบบ​กระจาย​ด้วย Rust
  • ไม่มี SQL parser เรา​เรียก API ของ​เอน​จินตรงๆ ทุก​ครั้ง ชั้น​ที่​แปล SQL เป็น​แผน​แล้ว​เลือก​ทาง​อ่าน อยู่​ที่ สร้าง query engine ตั้งแต่​ศูนย์
  • ไม่มี​การ​กู้​คืน​แบบ​เต็ม​รูป เรา​แตะ write-ahead log เท่า​ที่​บท​ที่ 7 ต้อง​ใช้​เพื่อ​อธิบาย​ราคา ส่วน durability กับ​การ​กู้​หลัง​เครื่อง​ดับ​อยู่​ที่ ทน crash แล้ว​ยัง​ตอบ​ถูก

และ​ของ​ที่​ใช้ได้​พรุ่งนี้​เลย​คือ​สิ่ง​ที่​บท​ที่ 6 กับ​บท​นี้​พูด​ตรง​กัน ระดับ​ที่​สูง​ขึ้น​คืน​งาน​กลับ​มา​ให้​คุณ ใน​รูป abort ทาง​รับ abort ที่​เขียน​ใน application จริง​อยู่​ที่ จัดการ​การ​แก้ไข​พร้อม​กัน


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้
  • Write-Ahead Logging — เอกสาร​ทางการ​ของ SQLite (ตรวจ​แล้ว 2026-08-13) — หัวข้อ 2.2 Concurrency คือ​ที่มา​ของ​ประโยค “since there is only one WAL file, there can only be one writer at a time” ซึ่ง​อธิบาย​ผล​วัด​ของ probe ทั้ง​สอง journal mode
  • PostgreSQL 18 Documentation — 13.2 Transaction Isolation (ตรวจ​แล้ว 2026-08-13) — ที่มา​ของ​ประโยค​ว่า Repeatable Read คือ snapshot isolation และ Serializable คือ Serializable Snapshot Isolation ของ​ตาราง​ระดับ isolation และ​ของ​ข้อความ could not serialize access due to read/write dependencies among transactions

เช็กความเข้าใจ — บทที่ 8

ข้อ 1 / 3

ในตารางสรุปของบทนี้ การทดลอง lost update ให้ยอด 150 ที่ระดับ serializable โดยมี abort 1 ครั้ง ทั้งที่คำตอบถ้ารันเรียงทีละตัวคือ 180 ข้อใดคือการอ่านผลนี้ที่ซื่อสัตย์ที่สุด