21 June 2021

Undo Segment and error Snapshot Too Old

เมื่อผู้ใช้งานอ่านข้อมูลใน Database เป็นระยะเวลานาน อาจจะเกิดข้อผิดพลาดแจ้งว่าข้อมูลที่ต้องการกลายเป็นข้อมูลที่เก่าเกินไป

เหตุการณ์
Session A รัน SELECT อ่านได้ค่า X
Session B รัน UPDATE เพื่อเปลี่ยนค่าจาก X เป็น Y
โดยเก็บค่าก่อนเปลี่ยนแปลงไว้ที่ UNDO Segment

รูปอธิบาย การทำงานไม่มีปัญหา เพราะ Session A รัน SELECT ข้อมูลในส่วนของ Block 0 จนถึง 19 แล้ว Session B จึงจะมา UPDATE เริ่มที่ block 0 ภายหลัง
จาก https://support.delphix.com/Delphix_Masking_Engine/Resolving_ORA-01555_Error%3A_Snapshot_too_old_(KBA1827)

รูปแสดงขั้นตอนการทำงาน
จาก https://support.delphix.com/Delphix_Masking_Engine/Resolving_ORA-01555_Error%3A_Snapshot_too_old_(KBA1827)
Error ORA-01555 เพราะ Session A ไม่พบข้อมูลที่ต้องการใน UNDO นั่นคือ(ตัวอักษรเอ็ก ไม่ใช่กากบาท)
จาก https://support.delphix.com/Delphix_Masking_Engine/Resolving_ORA-01555_Error%3A_Snapshot_too_old_(KBA1827)

สาเหตุ
เมื่อ Session เริ่ม SELECT ข้อมูล จะต้องได้ข้อมูลที่มีค่าขณะปัจจุบันเสมอ แม้ว่าข้อมูลนั้นจะถูกเปลี่ยนแปลงก่อนที่ SELECT จะอ่านถึง ซึ่งกรณีที่ข้อมูลนั้นถูกเปลี่ยนแปลงภายหลังนี้ จะต้องเก็บข้อมูลเก่าไว้ที่ UNDO เพื่อให้ SELECT นี้สามารถอ่านข้อมูลเก่าได้ แต่ก็สามารถถูกทับได้ ถ้าพื้นที่ใช้งานของ UNDO ไม่เพียงพอ ร่วมกับมีการใช้งาน UNDO จำนวนมาก และ SELECT ทำงานช้า

ขั้นตอนการเปลี่ยนสถานะของพื้นที่ที่ใช้งานใน UNDO
1. Session ทำการ update ข้อมูล จะมีสถานะ active
2. เมื่อ Session จบการทำงานแล้ว จะมีสถานะ inactive และ unexpired (สามารถถูก session อื่น นำไปใช้งานได้ แต่อาจเกิด error ORA-01555)
3. เมื่อหมดอายุตามที่กำหนดใน UNDO_RETENTION แล้ว จะมีสถานะ expired (สามารถถูก session อื่น นำไปใช้งานได้ตามปกติ)
4. ถ้ากำนดค่า retention guarantee ไว้ จะไม่ถูก Session ใดนำไปใช้งานได้ จนกว่าจะหมดระยะเวลาของ retention guarantee

ตัวอย่างการเกิด error ORA-01555
0:00 เซสชัน A เริ่ม SELECT
0:01 เซสชัน B รัน UPDATE ที่บล็อก 1000000 ซึ่งข้อมูล UNDO สำหรับบล็อกนี้จะถูกบันทึกในส่วน UNDO
0:01 เซสชัน B รัน COMMIT เปลี่ยนสถานะเป็น unexpired และเริ่มนับอายุตามที่กำหนดใน UNDO_RETENTION
เซสชันนี้สร้างข้อมูล UNDO แต่ข้อมูลยังคงอยู่ เนื่องจากสามารถใช้เมื่อใดก็ได้
1:00 เซสชัน A สืบค้นถึงบล็อก 200000
1:01 มีกิจกรรมมากมายเกิดขึ้นและสร้างข้อมูล UNDO จำนวนมาก และอายุที่กำหนดใน UNDO_RETENTION หมด ทำให้สถานะคือ expired และสามารถถูกเขียนทับได้
3:00 เซสชัน A สืบค้นถึงบล็อก 600000
4:00 ส่วนของข้อมูล UNDO เริ่มถูกเขียนทับและนำพื้นที่ที่มีการใช้งาน (อยู่ระหว่างดำเนินการ transactions in progress) กลับมาใช้ใหม่เมื่อมีคิวรี่ใดๆเริ่มต้นทำงาน ในที่นี้คือบล็อก 1000000 ถูกเขียนทับ
5:00 เซสชัน A สืบค้นถึงบล็อก 1000000 และต้องไปตรวจสอบการอ่านที่สอดคล้องกัน consistent read ที่ UNDO ด้วย เพราะข้อมูลบล็อก 1000000 นี้มีการเปลี่ยนแปลง แต่เซสชัน A รัน SELECT ก่อนการเปลี่ยนแปลง จึงต้องการค่าเก่า  แต่พบว่าข้อมูล UNDO ส่วนนี้ถูกเปลี่ยนไปแล้วจึงไม่สามารถหาค่าเก่าได้ จึงแสดง error ORA-01555 และเซสชัน A จะสืบค้นโดยแสดงข้อผิดพลาดนี้

วิธีแก้ไข

ให้ Session ใดๆที่กำลัง SELECT ข้อมูลอยู่นั้น สามารถจบการ SELECT ได้ทันก่อนที่ข้อมูลบน Undo จะถูกเขียนทับ

วิธีที่ 1 เพิ่มประสิทธิภาพการ query select เพื่อให้ใช้เวลาน้อยลง
จาก https://www.xtivia.com/ora-01555-snapshot-old/

วิธีที่ 2 พยายามจัดตารางเวลาการ query select ที่รันเป็นเวลานาน ให้อยู่ในช่วงเวลาที่ระบบมีการใช้คำสั่ง DML ที่น้อย
จาก https://www.xtivia.com/ora-01555-snapshot-old/

วิธีที่ 3 เพิ่มขนาดพื้นที่ Undo
จาก https://www.xtivia.com/ora-01555-snapshot-old/
เพื่อให้ไม่ให้ session อื่น มาใช้พื้นที่ที่กำลังถูกใช้โดย session ที่รันนาน

วิธีที่ 4 เพิ่มขนาดของพารามิเตอร์ UNDO_RETENTION
จาก https://www.xtivia.com/ora-01555-snapshot-old/
เพื่อให้มีเวลานานพอ ที่จะให้ session ใดๆที่กำลังอ่านอยู่นั้น สามารถจบการอ่านได้ทันก่อนที่ข้อมูลบน Undo จะ expire เพราะข้อมูลนี้อาจจะถูกทับได้

วิธีที่ 5 ตั้งค่า Undo เป็น GUARANTEE mode
จาก https://www.xtivia.com/ora-01555-snapshot-old/
เพื่อให้พื้นที่ข้อมูลบน Undo ไม่ถูก session อื่นเอาไปใช้ แต่ให้แสดง error พื้นที่ของ Undo ไม่เพียงพอแทน

วิธีที่ 6 อย่า commit ใน cursor loop
จาก https://www.xtivia.com/ora-01555-snapshot-old/