ทำไมสอง Session จึงเห็นข้อมูลไม่เหมือนกัน?
ใน READ COMMITTED แต่ละ query เห็นข้อมูลที่ commit ก่อน query นั้นเริ่ม
ทำไม Session A อ่านค่าได้ 100 แต่ Session B กลับอ่านได้ 120 ทั้งที่ใช้คำสั่ง SELECT ชุดเดียวกัน และรันคิวรีในเวลาไล่เลี่ยกัน? Oracle อ่านข้อมูลผิดพลาดหรือเปล่า? บทความนี้จะพามาไล่ดูพฤติกรรมจริงตามลำดับเวลาของการ UPDATE และ COMMIT กันครับ
Read Committed Isolation Level: คือระดับการแยกทรานแซกชันเริ่มต้น (Default Isolation Level) ของ Oracle Database ซึ่งกำหนดไว้ว่า แต่ละคำสั่ง SELECT จะมองเห็นเฉพาะข้อมูลที่ตนเองแก้ไข (Uncommitted Changes) และข้อมูลที่ทรานแซกชันอื่นทำการ COMMIT เรียบร้อยแล้วก่อนที่คำสั่ง SELECT นั้นจะเริ่มต้นขึ้นเท่านั้น (Statement-Level Read Consistency)

ภาพอธิบายแนวคิด: ในตัวอย่าง READ COMMITTED นั้น A อ่านค่า 100 ก่อน B commit และ query ใหม่หลัง commit อ่าน 120
ขั้นที่ 1: เตรียมตารางและข้อมูลเริ่มต้น
รันชุดคำสั่งนี้ครั้งเดียวใน Schema ทดลองเพื่อเตรียมข้อมูล:
CREATE TABLE demo_balance (
account_id NUMBER PRIMARY KEY,
balance NUMBER
);
INSERT INTO demo_balance VALUES (1, 100);
COMMIT;
ทดลองให้ Session A รันคิวรีอ่านข้อมูลเริ่มต้น:
SELECT balance FROM demo_balance WHERE account_id = 1;
BALANCE
------------
100
1 row selected.
ขั้นที่ 2: ให้ Session B แก้ไขข้อมูลโดยยังไม่สั่ง COMMIT
Session B: สั่งอัปเดตยอดเงินเป็น 120 แต่ ยังไม่สั่ง COMMIT
UPDATE demo_balance SET balance = 120 WHERE account_id = 1;
1 row updated.
จากนั้นให้ Session A รันคำสั่ง SELECT เดิมอีกครั้ง:
SELECT balance FROM demo_balance WHERE account_id = 1;
BALANCE
------------
100
1 row selected.
แต่เมื่อ Session B รันคำสั่ง SELECT เดียวกันภายในเซสชันของตนเอง:
SELECT balance FROM demo_balance WHERE account_id = 1;
BALANCE
------------
120
1 row selected.
วิเคราะห์ผลลัพธ์:
- Session A ยังคงมองเห็นยอดเงินเป็น
100เท่าเดิม เพราะ Oracle ป้องกันปัญหา Dirty Read (การอ่านข้อมูลที่ยังไม่ถูก Commit) โดยดึงข้อมูลภาพเดิมก่อนแก้ไขจาก Undo Segment มาแสดง - Session B มองเห็นยอดเงินเป็น
120เพราะ Oracle อนุญาตให้เซสชันมองเห็นความเปลี่ยนแปลงที่เกิดจากทรานแซกชันของตนเองได้เสมอ
ขั้นที่ 3: ให้ Session B สั่ง COMMIT แล้ว Session A เริ่มคิวรีใหม่
สลับไปที่ Session B แล้วสั่งจบ Transaction:
COMMIT;
Commit complete.
จากนั้นให้ Session A รันคำสั่ง SELECT อีกครั้ง:
SELECT balance FROM demo_balance WHERE account_id = 1;
BALANCE
------------
120
1 row selected.
วิเคราะห์ผลลัพธ์:
ครั้งนี้ Session A อ่านได้ค่า 120 เนื่องจากเป็นคำสั่ง SELECT คำสั่งใหม่ที่เริ่มต้นขึ้น หลังจาก ที่ Session B ได้ทำการ COMMIT เรียบร้อยแล้ว
ขั้นที่ 4: ความแตกต่างระหว่าง Statement-Level และ Transaction-Level Consistency
- Statement-Level Read Consistency: Oracle รับประกันความสอดคล้องของข้อมูลภายใน "แต่ละคำสั่ง SELECT" เสมอ หากมีคนแก้ไขและ Commit ข้อมูลระหว่างที่คิวรีเดิมกำลังทำงาน คิวรีนั้นก็จะยังคงเห็นภาพข้อมูล ณ วินาทีที่คิวรีนั้นเริ่มรันเท่านั้น
- ความเข้าใจผิดเรื่องการรันหลาย SELECT: ภายใต้
READ COMMITTEDแต่ละคำสั่งSELECTจะเริ่มจุดอ้างอิงภาพข้อมูล (Snapshot) ใหม่เสมอ ดังนั้น หากในหนึ่งรายงานมีการรันSELECTหลายคำสั่งต่อกัน ค่าที่ได้จากSELECTแรกกับSELECTถัดไปอาจไม่เท่ากันได้หากมีเซสชันอื่นมา Commit ข้อมูลคั่นกลาง - การทำรายงานที่ต้องอิงภาพข้อมูลเดียวกัน: หากต้องการให้การรันหลาย
SELECTอ่านข้อมูลภาพเดียวกันตลอดทั้งกระบวนการ (Transaction-Level Read Consistency) จะต้องใช้คำสั่งSET TRANSACTION READ ONLY;หรือปรับ Isolation Level เป็นSERIALIZABLE
สรุปแนวทางไล่จับปัญหา (Troubleshooting Tips)
เมื่อพบปัญหาว่าทำไมแต่ละ Session หรือหน้าจอแอปพลิเคชันมองเห็นข้อมูลไม่ตรงกัน ให้จดลำดับเหตุการณ์ตามเวลาจริง (Chronological Order) ดังนี้:
- Session ไหนเป็นผู้แก้ไข? (และแก้ไข ณ เวลาใด)
- Session ไหนเป็นผู้อ่าน? (และคำสั่ง
SELECTนั้นเริ่มต้นขึ้น ณ เวลาใด) - การ
COMMITเกิดขึ้นก่อนหรือหลังการเริ่มรันคำสั่งSELECTนั้น?
การไล่ลำดับเวลาทั้ง 3 ข้อนี้จะช่วยให้ระบุได้อย่างแม่นยำว่าระบบทำงานถูกต้องตามหลักการของ Isolation Level หรือไม่ โดยไม่ต้องคาดเดาครับ
อ่านหัวข้อ Oracle อื่นต่อได้ที่ บทความ Oracle บน teeDBA.com
บทความที่เกี่ยวข้อง

คอลัมน์เป็นข้อความ แต่เงื่อนไขเป็นตัวเลข: Oracle เปรียบเทียบอย่างไร?
ดูการเปรียบเทียบข้อความกับตัวเลขใน Oracle และเลือกชนิดข้อมูลให้ตรงก่อนแก้ query

ตรวจข้อมูลในแอปแล้ว ยังต้องมี Constraint ในฐานข้อมูลไหม?
เปรียบเทียบการตรวจข้อมูลในแอปกับ constraint ใน Oracle พร้อมตัวอย่างกฎที่ฐานข้อมูลบังคับใช้

วิธีทำเลขรันอัตโนมัติที่ไม่ต้องเขียน trigger ด้วยการใช้ Identity Column
Oracle 12c ขึ้นไปทำ auto-increment ได้ด้วย Identity Column ไม่ต้องสร้าง sequence + trigger เอง — วิธีใช้ 3 โหมด พร้อมข้อควรระวังตอน migrate