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

ทำให้ serializable แล้ว​ได้​อะไร​กลับ​มา

บท​ที่ 5 จบ​ลง​ด้วย​รายงาน​ที่​ทุก​บรรทัด​ดู​ปกติ transaction ทั้ง​สอง​ตัว commit ผ่าน ไม่มี error สัก​ตัว และ​ไม่​มี​ไร​เด​อร์เหลือ​อยู่​เวร​เลย​แม้แต่​คน​เดียว

กฎ​ที่​ทั้ง​ระบบ​ตั้งใจ​รักษา​ไว้​พัง​ไป​แล้ว โดย​ไม่มี​อะไร​ส่งเสียง

บท​นี้​เพิ่ม​ด่าน​ตรวจ​หนึ่ง​ชุด​เข้าไป แล้ว​กฎ​ก็​รอด แต่​สิ่ง​ที่​ได้​กลับ​มา​ไม่ใช่​คำ​ตอบ​ที่​ถูก

ประโยค​ที่​ต้อง​เอา​กลับ​ไป​จาก​บท​นี้ serializable ไม่​ได้​แก้​คำ​ตอบ​ให้​คุณ มัน​ปฏิเสธ​งาน แล้ว​คืน abort กลับ​มา​ให้​คุณ​ลอง​ใหม่​เอง

📦 รัน​บท​นี้​เอง

บท​นี้​เพิ่ม file เดียว​คือ src/ssi.rs แล้ว​แทรก​ด่าน​ตรวจ​หนึ่ง​ชุด​เข้าไป​ใน commit ของ src/txn.rs ไม่มี​โครงสร้าง​ข้อมูล​ใหม่​เลย​สัก​ตัว เพราะ​ทุก​อย่าง​ที่​ด่าน​ต้อง​ใช้​ถูก​เก็บ​ไว้​ตั้งแต่​บท​ที่ 4 แล้ว

รัน​ด้วย cargo run --quiet -- 06 ใน scripts/transaction-engine/ ทั้ง​คอร์ส​ยัง​เป็น Rust standard library ล้วน ไม่มี external crate สัก​ตัว

หัวข้อ​นี้​ให้​นิยาม​ของ serializability แล้ว​ชี้​สอง​ช่อง​ใน​นิยาม​ที่​หัวข้อ​ท้าย​บท​จะ​กลับ​มา​ใช้

SerializabilitySerializabilityเกณฑ์​ความ​ถูกต้อง​ที่​บอกว่า ผล​ของ​การ​รัน transaction พร้อม​กัน​ต้อง​เท่ากับ​ผล​ของ​การ​รัน​เรียง​ที​ละ​ตัว​ใน​ลำดับ​ใด​ลำดับ​หนึ่ง ไม่​ได้​เรียกร้อง​ให้ transaction รัน​ที​ละ​ตัว มัน​เรียกร้อง​แค่​ว่า ผล ที่​ได้ ต้อง​เท่ากับ​ผล​ของ​การ​รัน​เรียง​ที​ละ​ตัว​ใน​ลำดับ​ใด​ลำดับ​หนึ่ง

ช่อง​แรก​อยู่​ที่​คำ​ว่า​ลำดับ​ใด​ลำดับ​หนึ่ง นิยาม​ไม่​ได้​ระบุ​ว่า​ลำดับ​ไหน และ​ไม่​ได้​สัญญา​ว่า​จะ​เป็น​ลำดับ ที่​คุณ​อยาก​ได้

ช่อง​ที่​สอง​อยู่​ที่​สิ่ง​ที่​นิยาม​ไม่​พูด​ถึง​เลย คือ transaction ที่​ไม่​ได้ commit เกณฑ์​นี้​วัด​เฉพาะ ตัว​ที่ commit สำเร็จ ตัว​ที่​ถูก abort ไม่​ได้​อยู่​ใน​ลำดับ​ตั้งแต่​ต้น

สอง​ช่อง​นี้​ฟัง​ดู​เป็น​รายละเอียด​ของ​นิยาม หัวข้อ​เรื่อง lost update ข้าง​ล่าง​จะ​แสดง​ว่า​มัน​คือ​ทั้ง​เรื่อง

ด่าน​ที่​ถาม​ว่า​เขียน​ชน​กัน​ไหม ไม่มี​อะไร​ให้​จับ​เลย

หัวข้อ​ที่​มีชื่อ​ว่า “ด่าน​ที่​ถาม​ว่า​เขียน​ชน​กัน​ไหม ไม่มี​อะไร​ให้​จับ​เลย”

หัวข้อ​นี้​บอกว่า​ทำไม write skew หลุด​ด่าน​เขียน​ชน และ​ความ​สัมพันธ์​แบบ​ไหน​ที่​มอง​เห็น​ได้​แทน

ใน scenario ของ​บท​ที่ 5 t1 เขียน rider:anan ส่วน t2 เขียน rider:bunma สอง​คีย์​นี้​ไม่​ทับ​กัน​เลย

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

คำถาม​ที่​ถูก​อยู่​ฝั่ง​การ​อ่าน Read-Write AntidependencyRead-Write Antidependencyความ​สัมพันธ์​ที่ transaction หนึ่ง​อ่าน​ค่า​ไว้ แล้ว​อีก​ตัว​เขียน​ทับ​ค่า​นั้น​ทีหลัง SSI ใช้​การ​พบ​ความ​สัมพันธ์​นี้​ต่อ​กัน​สอง​ทอด​เป็น​สัญญาณ​ว่า anomaly กำลัง​จะ​เกิด คือ​ความ​สัมพันธ์​ที่​ฉัน​อ่าน​คีย์​หนึ่ง​ไว้ แล้ว​มี​คน​อื่น​เขียน​ทับ​คีย์​นั้น​ทีหลัง

ตอน​ที่​ฉัน​อ่าน ค่า​นั้น​จริง ตอน​ที่​ฉัน commit ค่า​นั้น​ไม่​จริง​แล้ว คำ​ตอบ​ที่​ฉัน​คิด​มา​ทั้ง​ก้อน จึง​ยืน​อยู่​บน​ของ​ที่​ไม่มี​อยู่​แล้ว

src/ssi.rs · คีย์​ที่​ทำให้​เรา​ชน​กับ​คน​ที่ commit ไป​ก่อน
/// คีย์ที่ทำให้ `me` ชนกับตัวที่ commit ไปแล้ว — คืนรายการเพื่อให้รายงานบอกได้ว่าชนที่ไหน
pub fn conflict_keys(me: &Txn, store: &Store, concurrent: &[&Txn]) -> Vec<String> {
let mut keys: Vec<String> = Vec::new();
for other in concurrent {
if other.xid == me.xid || !store.committed.contains(&other.xid) {
continue;
}
for key in me.reads.intersection(&other.writes) {
if !keys.contains(key) {
keys.push(key.clone());
}
}
}
keys
}

หัวใจ​ของ​ด่าน​อยู่​ที่ me.reads.intersection(&other.writes) คือ​คีย์​ที่​เรา​อ่าน​ไว้ ซ้อน​กับ​คีย์​ที่​คน​อื่น​เขียน

มัน​คืน​เป็น​รายการ​คีย์ ไม่ใช่​แค่ true หรือ false เพราะ​ตอน​ตามหา​สาเหตุ​จริง สิ่ง​เดียว​ที่​ใช้ได้ คือ​ชื่อ​คีย์​ที่​ชน

flowchart LR
  T1["t1<br/>อ่านทั้งสองแถว<br/>เขียน rider:anan = 0<br/>commit ก่อน"]
  T2["t2<br/>อ่านทั้งสองแถว<br/>เขียน rider:bunma = 0<br/>commit ทีหลัง"]
  A["abort"]
  T1 -.->|"t1 อ่าน rider:bunma ไว้ · แล้ว t2 เขียนทับ"| T2
  T2 -.->|"t2 อ่าน rider:anan ไว้ · แล้ว t1 เขียนทับ"| T1
  T2 ==>|"ด่านจับได้ที่คีย์ rider:anan"| A

คำ​บรรยาย​ภาพ: ความ​สัมพันธ์​แบบ read-write antidependency เกิด​พร้อม​กัน​ทั้ง​สอง​ทิศ จึง​ไม่มี​ลำดับ เรียง​ที​ละ​ตัว​ลำดับ​ไหน​ให้​ผล​แบบ​นี้​ได้ · และ​คน​ที่​ถูก​จับ​คือ​คน​ที่ commit ทีหลัง

หัวข้อ​นี้​แสดง​ว่า​ด่าน​แทรก​อยู่​ตรง​ไหน​ของ commit แล้ว​บอก​ราคา​ของ​การ​ตรวจ​ที่​ปลายทาง

วิธี​ที่​พา snapshot isolation ขึ้น​ไป​ถึง serializable ด้วย​การ​เฝ้า​ดู​ความ​สัมพันธ์​แบบ​นี้ มีชื่อ​ว่า Serializable Snapshot IsolationSerializable Snapshot Isolation (SSI)วิธี​ทำให้ snapshot isolation ไป​ถึง​ระดับ serializable ด้วย​การ​เฝ้า​ดู​ความ​สัมพันธ์​ระหว่าง transaction ที่​รัน​อยู่ แล้ว abort ตัว​ที่​ทำให้​เกิด​รูปแบบ​อันตราย

ใน​เอนจิน​นี้​มัน​ไม่​ได้​เพิ่ม​โครงสร้าง​ข้อมูล​ใหม่​เลย ทุก​อย่าง​ที่​ด่าน​ต้อง​ใช้​ถูก​เก็บ​มา​ตั้งแต่​บท​ที่ 4 คือ​รายการ​คีย์​ที่​แต่ละ transaction อ่าน​และ​เขียน​ไว้

src/txn.rs · ด่าน​ที่​แทรก​อยู่​ใน​จังหวะ commit
/// พยายาม commit · คืน `false` เมื่อถูก abort
///
/// ที่ระดับ snapshot ด่านนี้ไม่ตรวจอะไรเลย ทุก transaction ผ่านหมด
/// ที่ระดับ serializable เราตรวจ read-write antidependency กับตัวที่ commit ไปแล้ว
/// รายละเอียดของด่านนั้นอยู่ใน `ssi.rs`
pub fn commit(&self, store: &mut Store, concurrent: &[&Txn]) -> bool {
if self.level == Level::Serializable && crate::ssi::conflicts(self, store, concurrent) {
store.aborts += 1;
return false;
}
store.committed.insert(self.xid);
true
}

ที่​ระดับ snapshot เงื่อนไข​แรก​ไม่​ผ่าน ด่าน​จึง​ไม่​ทำงาน​เลย​สัก​ครั้ง ทุก​ตัว​ผ่าน​หมด​เหมือน​บท​ที่ 5

ที่​ระดับ serializable transaction ทำงาน​จน​จบ​ก่อน แล้ว​ค่อย​รู้​ตอน commit ว่า​ถูก​ปฏิเสธ งาน​ที่​ทำ​ไป​ทั้งหมด​ถูก​ทิ้ง

นี่​คือ​ราคา​ของ​การ​ไม่ lock อะไร​เลย​ระหว่าง​ทาง เรา​แลก​การ​รอ​ที่​บท​ที่ 3 วัด​ไว้ ด้วย​งาน​ที่​เสียเปล่า เมื่อ​ชน​กัน​จริง

หัวข้อ​นี้​ชี้​เงื่อนไขข้อ​ที่​สอง​ของ​ด่าน ซึ่ง​ทำให้​ผล​ขึ้น​กับ​ลำดับ​การ commit ไม่ใช่​ลำดับ​การ​เปิด

src/ssi.rs · เงื่อนไข​สาม​ข้อ​ที่​ต้อง​ครบ​พร้อม​กัน
/// `true` เมื่อ `me` ต้องถูก abort
///
/// เงื่อนไขสามข้อต้องครบพร้อมกัน ตัวอื่นต้องไม่ใช่ตัวเราเอง ตัวอื่นต้อง commit ไปแล้วจริง
/// และสิ่งที่เราอ่านต้องซ้อนกับสิ่งที่ตัวนั้นเขียน
///
/// ข้อที่สองคือเหตุผลที่ผลลัพธ์ขึ้นกับ *ลำดับการ commit* ไม่ใช่ลำดับการเปิด
/// คนที่ commit ก่อนจะรอด คนที่มาทีหลังเป็นคนจ่าย
pub fn conflicts(me: &Txn, store: &Store, concurrent: &[&Txn]) -> bool {
!conflict_keys(me, store, concurrent).is_empty()
}

store.committed.contains(&other.xid) คือ​เงื่อนไข​ที่​อ่าน​ผ่าน​ง่าย​ที่สุด และ​เป็น​ข้อ​ที่​ตัดสิน​ผล​จริง

ตอน t1 เรียก commit หมายเลข​ของ t2 ยัง​ไม่​อยู่​ใน​ทะเบียน committed ด่าน​จึง​ข้าม t2 ไป​ทั้ง​ตัว t1 ผ่าน​ฉลุย

ตอน t2 เรียก commit หมายเลข​ของ t1 อยู่​ใน​ทะเบียน​แล้ว คีย์​ที่ t2 อ่าน​ไว้​ซ้อน​กับ​คีย์​ที่ t1 เขียน ด่าน​จึง​จับ​ได้

คน​ที่​ถึง​เส้น commit ก่อน​คือ​คน​ที่​รอด ไม่ใช่​คน​ที่​เปิด transaction ก่อน ใน​การ​ทดลอง​นี้​ทั้ง​คู่ เปิด​ใน​สอง​บรรทัด​ที่​ติด​กัน

หัวข้อ​นี้​อ่าน​ผล​รัน​ของ write skew ที่​ระดับ serializable แล้ว​เทียบ​กับ​ผล​ของ​บท​ที่ 5

cargo run --quiet -- 06
== บทที่ 6 ทำให้ serializable แล้วได้อะไรกลับมา ==
ระดับ serializable
commit ได้ ["t1"]
abort 1
ไรเดอร์เหลืออยู่เวร 1 คน
กฎยังอยู่ไหม อยู่
version ที่ค้าง 4

ผล​ที่​ด่าน​ทำให้​เกิด​ขึ้น​โดยตรง​มี​สอง​บรรทัด รายชื่อ​ที่ commit ได้​เหลือ​ตัว​เดียว และ abort ขึ้น​จาก​ศูนย์ ของ​บท​ที่ 5 มา​เป็น​หนึ่ง

ผล​ที่​ตาม​มา​คือ​ไร​เด​อร์เหลือ​อยู่​เวร 1 คน กฎ​ที่​ทั้ง​ระบบ​ต้อง​รักษา​ไว้​จึง​ยัง​อยู่

cargo run --quiet -- 06
t2 ถูก abort เพราะอ่านคีย์ที่ t1 เขียนทับ ["rider:anan"]
t1 รอดเพราะ commit ก่อน · ลำดับการ commit เป็นตัวตัดสินว่าใครจ่าย

เอนจินบอก​ได้​ด้วย​ว่า​ชน​ที่​คีย์​ไหน rider:anan คือ​คีย์​ที่ t2 อ่าน​ไว้ แล้ว t1 เขียน​ทับ

บรรทัด​ที่​ควร​หยุด​อ่าน​คือ​บรรทัด​สุดท้าย​ของ​รายงาน version ที่​ค้าง 4 ตัว เท่ากับ​ของ​บท​ที่ 5 พอดี

การ abort ไม่​ได้​เก็บกวาด​อะไร​เลย ของ​ที่ t2 เขียน​ลง​ไป​ยัง​กิน​ที่​อยู่​ใน​ที่​เก็บ เหมือน​ของ​ที่ ghost ทิ้ง​ไว้​ใน​บท​ที่ 1

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

เอา​ด่าน​เดิม​ไป​ใส่​การ​ทดลอง​ของ​บท​ที่ 2 ซึ่ง​สอง​คน​อ่าน​ยอด​เดียวกัน​แล้ว​บวก​คนละ​ก้อน

cargo run --quiet -- 06
lost update ที่ระดับ serializable
commit ได้ ["t1"]
abort 1
ยอดสุดท้าย 150
คำตอบเรียงลำดับ 180

t2 ถูก abort ตาม​ที่​คาด​ไว้ แต่​สอง​บรรทัด​ล่าง​คือ​ประเด็น​ของ​ทั้ง​บท

cargo run --quiet -- 06
**serializable ไม่ได้แก้ยอดให้ถูก** มันได้ 150 ไม่ใช่ 180
สิ่งที่มันให้คือ abort ที่คุณต้องเขียน retry เอง

ยอด​สุดท้าย​คือ 150 ซึ่ง​เป็น​ผล​ของ​การ​รัน t1 ตัว​เดียว ไม่ใช่ 180 ที่​เป็น​ผล​ของ​การ​รัน​ทั้ง​สอง​ตัว​เรียง​กัน

และ​ตาม​นิยาม​ใน​หัวข้อ​แรก 150 ถูกต้อง มัน​ตรง​กับ​ลำดับ​ที่​มี t1 อยู่​ตัว​เดียว เพราะ t2 ไม่​ได้ commit มัน​จึง​ไม่​เคย​อยู่​ใน​ลำดับ​ไหน​เลย

เทียบ​กันตรงๆ ที่​ระดับ snapshot บท​ที่ 2 ได้​ยอด 130 โดย​ไม่มี​ใคร​ถูก​ปฏิเสธ ที่​ระดับ serializable ได้​ยอด 150 พร้อม abort 1 ครั้ง

ไม่มี​ระดับ​ไหน​ได้ 180 ระดับ​ที่​สูง​ขึ้น​ไม่​ได้​ซื้อ​คำ​ตอบ​ที่​ถูก​ให้​คุณ มัน​ซื้อ​สิทธิ์​ที่​จะ​ปฏิเสธ​งาน

หัวข้อ​นี้​บอกว่า​เมื่อ commit คืน​ค่า​ปฏิเสธ งาน​ที่​เหลือ​เป็น​ของ​ผู้​เขียน​โปรแกรม ไม่ใช่​ของ​เอนจิน

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

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

ทาง​ที่​เหลือ​มี​ทาง​เดียว​คือ RetryRetryการ​เริ่ม transaction ที่​ถูก abort ใหม่​ตั้งแต่​ต้น งาน​นี้​ตก​มา​ถึง​คน​เขียน​โปรแกรม เพราะ​เอนจินบอก​ได้​แค่​ว่า​ทำ​ไม่​ได้ ไม่​ได้​บอกว่า​คำ​ตอบ​ที่​ถูก​คือ​อะไร เปิด transaction ใหม่​ทั้ง​ก้อน อ่าน​ค่าที่​สด แล้ว​คิด​ใหม่ บน​ของ​ที่​จริง

ถ้า​เขียน retry ให้ t2 มัน​จะ​เปิด​หลัง​จาก t1 commit ไป​แล้ว snapshot ของ​มัน​จึง​มี t1 อยู่​ด้วย และ​มัน​จะ​เห็น​ยอด​ที่ t1 เขียน​ไว้

แต่​วงจร​นั้น​ไม่​ได้​อยู่​ใน​เอนจิน มัน​เป็น code ของ​คุณ และ​นี่​คือ​ความหมาย​จริง​ของ​ประโยค​ว่า serializable โยน​งาน​กลับ​มา

🔁 วงจร​เดียวกัน​นี้​อยู่​ใน​คอร์ส EF Core แล้ว

คอร์ส​เก็บ aggregate ลง​ฐาน​ข้อมูล​ด้วย EF Core บท​ที่ 5 เรื่อง concurrency สอน​วงจร​นี้​ที่​ชั้น ORM ตรงๆ จับ DbUpdateConcurrencyException แล้ว​เลือก​ว่า​จะ reload ค่า​ล่าสุด​มา​ลอง​ใหม่ หรือ​ส่ง​ให้​ผู้​ใช้​ตัดสิน

กลไก​ที่​ใช้​ตรวจ​ต่าง​กัน บท​นั้น​ตรวจ​ด้วย concurrency token ประจำ​แถว ส่วน​บท​นี้​ตรวจ​ว่า​คีย์​ที่​เรา​อ่าน ซ้อน​กับ​คีย์​ที่​คน​อื่น​เขียน​หรือ​ไม่

แต่​รูป​ของ​ทางออก​เหมือน​กัน​ทุก​จุด ฐาน​ข้อมูล​ปฏิเสธ แล้ว​โปรแกรม​ของ​คุณ​ตัดสิน​ใจ​ว่า​จะ​ทำ​อะไร​ต่อ ถ้า​คุณ​เคย​เขียน​วงจร​นั้น​มา​ก่อน คุณ​เขียน​มัน​เพราะ​เหตุผล​ใน​บท​นี้

ด่าน​ของ​เรา​หยาบ​กว่า​ของ​จริง และ​มัน​ปฏิเสธ​งาน​ที่​ปลอดภัย​ด้วย

หัวข้อ​ที่​มีชื่อ​ว่า “ด่าน​ของ​เรา​หยาบ​กว่า​ของ​จริง และ​มัน​ปฏิเสธ​งาน​ที่​ปลอดภัย​ด้วย”

หัวข้อ​นี้​บอก​ข้อ​แลก​ที่​ด่าน​ใน​คอร์ส​นี้​เลือก​ไว้ แล้ว​เทียบ​กับ SSI ที่ PostgreSQL ใช้​จริง

ด่าน​ที่​เรา​เพิ่ง​สร้าง​เป็น​รูปแบบ​ที่​ย่อ​ลง​มา​จาก SSI ของ​จริง ซึ่ง​มา​จาก​งาน​ของ Cahill, Röhm และ Fekete ที่ SIGMOD 2008 และ​เป็น​ฐาน​ของ​ระดับ Serializable ใน PostgreSQL

คอมเมนต์​ที่​หัว​ของ src/ssi.rs เขียน​ข้อ​แลก​นั้น​ไว้​เอง ก่อน​ถึง​บรรทัด code บรรทัด​แรก​ด้วย​ซ้ำ

src/ssi.rs · ข้อ​แลก​ที่​เอนจิน​นี้​ยอมรับ​ไว้​โดย​ตั้งใจ
//! ของจริงจะรอให้พบความสัมพันธ์นี้ *ต่อกันสองทอด* ก่อนจึงตัดสินใจ abort
//! ส่วนของเรา abort ตั้งแต่ทอดแรก แปลว่าเราปฏิเสธงานบางชิ้นที่จริงๆ แล้วปลอดภัย
//! **นั่นเป็นข้อแลกที่ยอมรับไว้โดยตั้งใจ ไม่ใช่ความบังเอิญ** และคอร์สต้องพูดข้อนี้ตรงๆ

SSI ของ​จริง​ไม่​ได้ abort ทันที​ที่​พบ​ความ​สัมพันธ์​เส้น​เดียว มัน​รอ​ให้​เส้น​แบบ​นี้​ต่อ​กัน​สอง​ทอด จนมี transaction ตัวกลาง​ที่​มี​ทั้ง​เส้น​เข้า​และ​เส้น​ออก แล้ว​จึง​เลือก​ตัว​ที่​จะ abort

ด่าน​ของ​เรา​ตัดสิน​ตั้งแต่​เส้น​แรก​ที่​เจอ​ตอน commit ผล​คือ​มัน​ปฏิเสธ​งาน​บาง​ชิ้น​ที่จริงๆ แล้ว รัน​พร้อม​กัน​ได้​อย่าง​ปลอดภัย

ราคา​ของ​ความ​หยาบ​นี้​จ่าย​เป็น​จำนวน abort กับ retry ที่​มาก​เกิน​จำเป็น ไม่​ได้​จ่าย​เป็น​คำ​ตอบ​ที่​ผิด

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

Serializable ของ PostgreSQL คือ SSI ตัว​เต็ม และ​เมื่อ​มัน​ปฏิเสธ มัน​คืน​ข้อความ​นี้​กลับ​มา could not serialize access due to read/write dependencies among transactions

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

บท​ที่ 7 จะ​ไป​นับ​ราคา​อีก​ด้าน​ที่​ยัง​ไม่​ได้​แตะ​เลย คือ​จำนวน​ที่ transaction หนึ่ง​ทำให้​ระบบ​ต้อง​เขียน ลง disk จริง


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้
  • Serializable Isolation for Snapshot Databases — Cahill, Röhm, Fekete (ACM SIGMOD 2008 หน้า 729–738 — ตรวจ​แล้ว 2026-08-13) ต้นทาง​ของ SSI และ​ของ​กฎ​ที่​ว่า ต้อง​พบ​ความ​สัมพันธ์ read-write antidependency ต่อ​กัน​สอง​ทอด​ก่อน​จึง​ตัดสิน​ใจ abort
  • PostgreSQL 18 Documentation — 13.2 Transaction Isolation (ตรวจ​แล้ว 2026-08-13) ยืนยัน​ว่า​ระดับ Serializable ของ PostgreSQL ทำ​ด้วย​เทคนิค​ที่​วง​วิชาการ เรียก​ว่า Serializable Snapshot Isolation และ​เป็น​ที่มา​ของ​ข้อความ error ที่​ยก​มา​ใน​บท​นี้ รวม​ทั้ง​ประโยค​เรื่อง predicate lock

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

ข้อ 1 / 3

ที่ระดับ serializable การทดลอง lost update ให้ยอดสุดท้าย 150 ทั้งที่คำตอบของการรันเรียงทีละตัวคือ 180 ข้อใดอธิบายตัวเลขนี้ได้ถูกต้อง