← Back to PostgreSQL course
PHASE 2: DEEP DIVE INTO THE 4 PILLARS PILLAR 2: PROCEDURES ⚙️

Day 6: Pillar 2 - Stored Procedures & Transaction Control (กลไกการสร้าง Stored Procedures, การควบคุม Transaction ภายในด้วย COMMIT และ ROLLBACK, และการทำ Chunked Batch Processing)

ทลายข้อจำกัดร้ายแรงของ Stored Functions ที่ห้ามสั่ง COMMIT ระหว่างทาง! เรียนรู้วิธีสร้าง Stored Procedure (PostgreSQL 11+) เพื่อควบคุม Transaction Boundaries ภายในตัวโปรแกรม สั่ง COMMIT / ROLLBACK กลางลูปการประมวลผลเพื่อทำ Chunked Batch Processing ปลด Row Locks, ระบาย Write-Ahead Log (WAL), และการันตีความปลอดภัยของข้อมูลในระดับ Enterprise

📍 ตำแหน่งปัจจุบันของคุณบน Roadmap (18-Day Masterclass) Phase 2: Pillar 2 of 4
Phase 1: Architecture Synthesis (Days 1–4) ✓
➔
Day 5: Pillar 1 - UDFs & Inlining ✓
➔
Day 6: Pillar 2 - Stored Procedures
➔
Day 7: Pillar 3 - Event Triggers
➔
Day 8: Pillar 4 - Pre-computation
เปิดบท

ถอดรหัสคำสำคัญของวันนี้

UDF คำนวณสูตรได้ แต่ห้าม COMMIT กลางทาง วันนี้เป็นเสาหลักต้นที่สอง — งานชุดใหญ่ที่ต้องจัดการข้อมูลปริมาณมหาศาล และตัดยอดเป็นระยะๆ ได้เอง

Stored Procedures

โปรแกรมยูนิตในฐานข้อมูลที่ทำงานเป็นขั้นตอน รับพารามิเตอร์ แล้วสั่งงานกับตารางต่างๆ ได้ ไม่ได้แค่คืนค่าคำนวณ

Explicit Transaction Control

ความสามารถในการสั่งเซฟเป็นระยะๆ (COMMIT) หรือยกเลิกทั้งหมด (ROLLBACK) ได้เองกลางโปรแกรม

โดยรวมวันนี้คือการใช้ Stored Procedure ประมวลผลข้อมูลก้อนใหญ่ทีละส่วน (chunking) แล้วสั่ง COMMIT ตัดยอดเป็นระยะ เพื่อให้ล็อกหลุด และไม่ให้ไฟล์บันทึกการทำงาน (WAL) บวมจนดิสก์เต็ม

เปรียบกับชีวิตจริง

เหมือนย้ายบ้านของ 1,000 ชิ้น แทนที่จะขนทั้งหมดใส่รถเที่ยวเดียวแล้วภาวนาไม่ให้ยางแตก (ถ้าแตกต้องเริ่มใหม่หมด) เราแบ่งขนทีละ 50 ชิ้น แล้วเซฟยืนยันไว้เรื่อยๆ เที่ยวไหนมีปัญหา ก็แก้เฉพาะเที่ยวนั้น

ต่างจาก UDF ตรงที่ Procedure สั่ง COMMIT กลางทางได้ เหมาะกับงานตัดเงิน คัดแยกข้อมูล หรือย้ายข้อมูลระดับล้านรายการ โดยไม่ทำให้ระบบล่มทั้งก้อน

1

🏛️ มโนทัศน์ สถาปัตยกรรมองค์กร และสถานการณ์จริง (Concept & Architecture)

ในบทก่อนหน้า (Day 5) เราได้เรียนรู้การใช้ User-Defined Functions (UDFs) เป็นเครื่องมือคำนวณระดับอะตอมที่ฝังใน Query แต่ฟังก์ชันมี "กฎเหล็กอันเด็ดขาด" คือ ต้องรันอยู่ภายใต้ Transaction Context ของผู้เรียกภายนอกเสมอ และห้ามมีคำสั่ง COMMIT หรือ ROLLBACK อยู่ภายในฟังก์ชันเด็ดขาด หากระบบต้องประมวลผลงานย้ายข้อมูล หรือ Batch Job ปริมาณมหาศาลระดับ 5–10 ล้านแถว การมัดรวมทุกอย่างใน Transaction เดียวจะกลายเป็นระเบิดเวลาที่ทำลายเสถียรภาพของทั้งระบบ!

📦 วิกฤตปรับปรุงยอดสินค้าสิ้นปี 5,000,000 แถว: เสียเวลาฟรี 2 ชั่วโมงเพราะ Rollback ทั้งหมด!

ระบบคลังสินค้าต้องทำการปรับปรุงยอดคงเหลือและตั้งค่าดัชนีสิ้นปีสำหรับสินค้า 5,000,000 รายการ ทีมพัฒนาเขียนสคริปต์ห่อไว้ใน CREATE FUNCTION ให้กวาดอัปเดตข้อมูลทั้งหมดในคำสั่งเดียว เมื่อสคริปต์ประมวลผลไปถึงนาทีที่ 120 (ทำไปแล้ว 4,900,000 รายการ) เกิด Data Constraint Error ขึ้นเพียง 1 แถว:

Rollback 100%
ข้อมูล 4.9M แถวที่ทำไปแล้วถูกยกเลิกทิ้งทั้งหมด (เสียเวลาฟรี 2 ชม.)
200 GB WAL
Write-Ahead Log บวมพองจนดิสก์เซิร์ฟเวอร์เต็ม
Exclusive Lock
ตารางสินค้าถูกล็อกค้างจนระบบสั่งซื้อหน้าบ้านล่มสนิท

ต้นตอของวิกฤตนี้คือการใช้ Function ที่บังคับรันแบบ Single Giant Transaction สถาปนิกจึงต้องใช้ Stored Procedures (PostgreSQL 11+) เพื่อทำ Chunked Transaction Control ปล่อย Lock และ Flush WAL เป็นระยะสั้นๆ ครับ!

🚛 อุปมาอุปไมย: การขนสัมภาระ 5,000,000 ชิ้นขึ้นรถบรรทุกคันเดียว vs การขนส่งเป็นล็อต

❌ ขนขึ้นรถคันเดียวรวดเดียว (Single Giant Transaction ใน Function)

คุณพยายามยกของ 5 ล้านชิ้นใส่รถบรรทุกคันเดียวโดยไม่ยอมเซ็นส่งมอบระหว่างทาง (ห้ามสั่ง COMMIT) หวังจะเซ็นมอบทีเดียวเมื่อของชิ้นสุดท้ายถึงปลายทาง

  • รถคันใหญ่กีดขวางถนน เลนปิดทั้งสาย (Row Locks แช่นาน 2 ชม. ระบบหน้าบ้านค้าง)
  • น้ำหนักกดทับจนแชสซีรถแอ่น (RAM บวม และ WAL Bloat ล้นดิสก์ 200GB)
  • หายนะตอนจบ: ที่ชิ้น 4,900,001 มีของตกแตก 1 ชิ้น ตำรวจสั่ง "ยึดของทั้งหมดกลับจุดเริ่มต้นทันที (Rollback All)" เสียเวลาทำงานฟรี 2 ชั่วโมง!
✅ ขนส่งด้วยขบวนรถเป็นล็อต (Chunked Batch ใน Stored Procedure)

คุณเปลี่ยนมาใช้ Stored Procedure ขนส่งสินค้าเป็นล็อตย่อยๆ ล็อตละ 10,000 ชิ้น และให้เซ็นรับมอบสำเร็จในแต่ละล็อตทันที (สั่ง COMMIT; ทุกรอบ)

  • ของถึงแล้วเซ็นรับทันที (ปลด Row Lock, ระบาย WAL และคืน RAM ทุก 10,000 แถว)
  • รถขนาดเล็กไม่ขวางถนน ลูกค้าหน้าบ้านใช้งานระบบสั่งซื้อได้ราบรื่นตลอดเวลา
  • Resilience สูงสุด: หากล็อตที่ 491 เกิดปัญหา จะเสียหายเฉพาะล็อตนั้น ส่วนของ 4.9 ล้านชิ้นที่ COMMIT ไปแล้ว ปลอดภัย 100%!

⚖️ การเปรียบเทียบเชิงสถาปัตยกรรม: Stored Function vs. Stored Procedure ใน PostgreSQL

มิติเปรียบเทียบสถาปัตยกรรม Stored Function (CREATE FUNCTION) Stored Procedure (CREATE PROCEDURE)
วิธีการเรียกใช้งาน (Invocation) เรียกเป็นส่วนหนึ่งของ Query เช่น SELECT fn_calc(); เรียกใช้ด้วยคำสั่ง CALL pr_batch(); โดยตรง
ข้อกำหนดการคืนค่า (Return Contract) บังคับ (Mandatory RETURNS) ต้องคืนค่าเสมอ ไม่มี RETURNS (ส่งค่าออกผ่านพารามิเตอร์ OUT / INOUT)
Explicit Transaction Control ❌ ห้ามทำเด็ดขาด (เกิด Error: invalid transaction termination) ✅ สั่ง COMMIT; และ ROLLBACK; ได้กลางลูป
กลไกการปล่อย Lock & WAL แช่ Row Locks และสะสม WAL ไว้จนจบ Query หลัก ปลด Row Locks และ Flush WAL ลงดิสก์ทันทีเมื่อสั่ง COMMIT;
งานที่เหมาะสม (Sweet Spot) คำนวณและแปลงค่าระดับแถว (Atomic Math / Inlining) งานหลายขั้นตอน (Workflows) และ Chunked Batch Processing ขนาดใหญ่
เวอร์ชันที่เริ่มรองรับ รองรับมาตั้งแต่เวอร์ชันแรกๆ ของ PostgreSQL เพิ่มเข้ามาตั้งแต่ PostgreSQL 11+ เพื่อรองรับงานระดับ Enterprise
สถาปัตยกรรมระดับลึก: การจัดการ Transaction Scope & Lock / WAL Flushing Pillar 2 Architecture Pipeline
❌ แย่: Single Giant Transaction ใน Function (พังแล้ว Rollback หมด) ✅ ปัง: Stored Procedure + Chunked COMMIT (ปลด Lock & ปลอดภัย 100%) 1. เปิด Single Transaction เดียวคลุม 5,000,000 แถว • สั่งรัน SELECT fn_archive_old_logs_flawed(); • เริ่มสะสม Exclusive Row Locks บน Data Pages ทีละแถวต่อเนื่อง 2. วิกฤตสะสม: แช่ Row Locks นาน 2 ชั่วโมงเต็ม! • แถวข้อมูล 4.9 ล้านแถวติด Lock ห้ามระบบอื่นแตะต้อง • WAL Buffers พุ่งทะลุ 200GB ดิสก์เกือบเต็ม ไม่สามารถ Flush/Purge ได้ • ลูกค้าหน้าบ้านติด Lock Wait Timeout ระบบสั่งซื้อล่มทั้งระบบ 💥 เกิด Constraint Error ณ แถวที่ 4,900,001! 3. All-or-Nothing Catastrophic Rollback • PostgreSQL สั่งยกเลิกข้อมูลทั้ง 4.9 ล้านแถวทิ้งทันที! • เสียเวลาทำงานฟรี 2 ชั่วโมง และต้องเสียเวลา Rollback อีก 30 นาที ผลลัพธ์: ได้งาน 0 แถว, WAL บวม 200GB, ระบบหน้าบ้านพัง 1. Stored Procedure: CALL pr_archive_old_logs_production(10000); • ดึงข้อมูลเฉพาะกลุ่มย่อย 10,000 แถวด้วย LIMIT ใน CTE ⚡ ใช้ FOR UPDATE SKIP LOCKED ไม่ชนกับ Worker หรือระบบหน้าบ้าน 2. Chunked COMMIT Loop (Explicit Transaction Control) Chunk #1 (1 - 10,000) ➔ COMMIT; ปลด Lock + Flush WAL + คืน RAM ทันที Chunk #2 (10,001 - 20,000) ➔ COMMIT; ปลด Lock + Flush WAL + คืน RAM ทันที ⋮ วนลูปต่อเนื่องอย่างราบรื่น Lock Duration < 0.2 วินาทีต่อ Chunk หากพังที่ Chunk 491 ➔ 4.9 ล้านแถวแรกที่ COMMIT ไปแล้วยังปลอดภัย 100%! เวลารวม: จาก 2 ชั่วโมง เหลือเพียงไม่ถึง 3 นาที! WAL Segment ถูก Recycle สม่ำเสมอ ไม่บวมพอง และระบบหน้าบ้านไม่สะดุด
🎙️ สคริปต์คำพูดสำหรับพรีเซนท์บนเวที (Presentation Pitch Script)

"ทุกท่านครับ งาน Batch ขนาดใหญ่ที่รันแล้วค้างหรือพังจน Rollback ทิ้งทั้งหมด 2 ชั่วโมง ไม่ได้เกิดจากฐานข้อมูลประมวลผลช้า แต่เกิดจากการฝืนใช้ Function ที่บังคับรันใน Single Transaction เดียวจน WAL บวมและถือ Lock ค้าง

ทางออกเชิงสถาปัตยกรรมตาม Pillar 2 คือการเปลี่ยนมาใช้ CREATE PROCEDURE ร่วมกับ Chunked Transaction Control สั่ง COMMIT; ทุกๆ 10,000 รายการ ซึ่งปลด Row Lock ทันที, ระบาย WAL ลงดิสก์ และการันตีว่างานที่ทำสำเร็จไปแล้วจะปลอดภัย 100% แม้ระบบดับกะทันหันครับ"

🧮 วิดเจ็ตทดลอง: ตัวจำลองความปลอดภัยของ Chunked Batch (Disaster Resilience Simulator)

ลองปรับจำนวนแถวข้อมูล ขนาด Chunk Size และจุดที่สมมติว่าเกิด Failure เพื่อดูว่าระบบ Stored Procedure ช่วยปกป้องข้อมูลที่ถูก COMMIT ไปแล้วได้มากเพียงใด เทียบกับ Single Giant Transaction ใน Function

ผลลัพธ์การรอดชีวิตของข้อมูลเมื่อเกิด Crash ที่จุดดังกล่าว:
• แบบเดิม (Function): สูญเสียทั้งหมด 0 แถวรอด (Rollback 100%)
• แบบใหม่ (Procedure Chunking): บันทึกสำเร็จถาวรแล้ว 4,000,000 แถว (พังเฉพาะ Chunk ล่าสุด 10,000 แถว)
รอดชีวิต 80.0%
2

💻 ปูพื้นฐานไวยากรณ์ & แบบฝึกหัดไต่ระดับ (Stored Procedures Syntax & Hands-on)

การสร้าง Stored Procedure ใน PostgreSQL เริ่มต้นจากคำสั่ง CREATE PROCEDURE, เรียกใช้ด้วยคำสั่ง CALL, ไม่มีประโยค RETURNS และสามารถสั่ง COMMIT; หรือ ROLLBACK; ได้อย่างอิสระ:

🧱 แกะโครงสร้างไวยากรณ์ Stored Procedure: แม่แบบการคุม Transaction

ไวยากรณ์หลัก: High-Performance Stored Procedure Blueprint Pillar 2 Architecture
CREATE OR REPLACE PROCEDURE pr_process_batch_orders (
    p_chunk_size INT DEFAULT 10000 -- [1] พารามิเตอร์ขาเข้า (Input Parameter)
)
LANGUAGE plpgsql                   -- [2] ใช้ภาษา PL/pgSQL
-- 🎯 [3] สังเกตว่า "ไม่มีประโยค RETURNS" เหมือน Function
AS $$
DECLARE
    v_rows_updated INT := 0;
    v_total_updated INT := 0;
BEGIN
    LOOP
        -- [Statement 1] ประมวลผลอัปเดตเฉพาะกลุ่มย่อย (Chunk) ด้วย CTE + LIMIT
        WITH updated_chunk AS (
            UPDATE orders
            SET status = 'ARCHIVED',
                archived_at = CURRENT_TIMESTAMP
            WHERE id IN (
                SELECT id FROM orders
                WHERE status = 'COMPLETED' AND archived_at IS NULL
                ORDER BY id ASC -- 🎯 เรียงลำดับ ID แน่นอน ป้องกัน Deadlock
                LIMIT p_chunk_size
                FOR UPDATE SKIP LOCKED -- 🎯 ข้ามแถวติดล็อก ไม่ชนกับ Worker อื่น
            )
            RETURNING id
        )
        SELECT COUNT(*) INTO v_rows_updated FROM updated_chunk;

        -- [Statement 2] เงื่อนไขออกจากลูปเมื่อไม่มีข้อมูลเหลือให้ทำแล้ว
        IF v_rows_updated = 0 THEN
            EXIT;
        END IF;

        v_total_updated := v_total_updated + v_rows_updated;
        RAISE NOTICE 'Processed and Committed Chunk: % rows (Total: %)', v_rows_updated, v_total_updated;

        -- 🎯 [Statement 3: Explicit Transaction Control]
        -- ปลด Row Lock, Flush WAL และเริ่ม Transaction ใหม่ทันทีในลูป!
        COMMIT;

    END LOOP;
END $$;
💡 กฎเหล็กสำคัญ (Subtransaction Exception Trap): ห้ามสั่ง COMMIT; หรือ ROLLBACK; ภายในบล็อกที่มีส่วน EXCEPTION WHEN OTHERS THEN เด็ดขาด เนื่องจากบล็อกที่มี EXCEPTION จะสร้าง Implicit Subtransaction (Savepoint) ซึ่ง PostgreSQL ห้ามสั่ง COMMIT ภายในนั้น!

🏋️️‍♂️ แบบฝึกหัดไต่ระดับการเขียนจริง (จากระดับเริ่มต้น ➔ Refactoring ระดับ Production)

🟢 Level 1: การสร้าง Stored Procedure และเรียกใช้งานด้วย CALL ระดับเริ่มต้น
-- [1] สร้าง Procedure สำหรับปรับอัปเดตสถานะบัญชีที่ไม่มีการเคลื่อนไหว
CREATE OR REPLACE PROCEDURE pr_deactivate_inactive_users()
LANGUAGE plpgsql
AS $$
BEGIN
    -- อัปเดตผู้ใช้ที่ไม่ได้ล็อกอินเกิน 1 ปี
    UPDATE users
    SET is_active = FALSE
    WHERE last_login < CURRENT_DATE - INTERVAL '1 year';

    -- 🎯 สั่ง COMMIT ปิด Transaction ภายใน Procedure ทันที
    COMMIT;

    RAISE NOTICE 'ทำความสะอาดข้อมูลผู้ใช้ที่ไม่ใช้งานเรียบร้อยแล้ว';
END $$;

-- 📤 การเรียกใช้งาน Stored Procedure ต้องใช้คำสั่ง CALL (ห้ามใช้ SELECT):
CALL pr_deactivate_inactive_users();
จุดที่ต้องสังเกต: การเรียกใช้ Procedure ต้องใช้คำสั่ง CALL pr_name(); และตัว Procedure สามารถสั่ง COMMIT; ได้อย่างสมบูรณ์โดยไม่เกิด Error
🟡 Level 2: การใช้ COMMIT และ ROLLBACK ร่วมกับการตรวจสอบเงื่อนไขความปลอดภัย ระดับปานกลาง
-- [1] สร้าง Procedure โอนเงินที่สามารถตัดสินใจสั่ง ROLLBACK หรือ COMMIT ได้เอง
CREATE OR REPLACE PROCEDURE pr_transfer_funds_safe (
    p_sender_id   INT,
    p_receiver_id INT,
    p_amount      NUMERIC
)
LANGUAGE plpgsql
AS $$
DECLARE
    v_sender_bal NUMERIC;
BEGIN
    -- สั่งตัดเงินจากบัญชีผู้ส่ง
    UPDATE accounts SET balance = balance - p_amount WHERE account_id = p_sender_id;

    -- ตรวจสอบว่ายอดเงินติดลบหรือไม่
    SELECT balance INTO v_sender_bal FROM accounts WHERE account_id = p_sender_id;

    IF v_sender_bal < 0 THEN
        RAISE NOTICE 'ยอดเงินไม่พอ! (ยอดติดลบ %) ทำการ ROLLBACK ยกเลิกการโอน', v_sender_bal;
        -- 🎯 สั่ง ROLLBACK ย้อนกลับคำสั่งหักเงินทันที
        ROLLBACK;
        RETURN;
    END IF;

    -- เพิ่มเงินให้บัญชีผู้รับ
    UPDATE accounts SET balance = balance + p_amount WHERE account_id = p_receiver_id;

    -- 🎯 ยืนยัน Transaction สมบูรณ์
    COMMIT;
    RAISE NOTICE 'โอนเงินสำเร็จ % บาท', p_amount;
END $$;

-- 📤 ทดสอบเรียกใช้งาน:
CALL pr_transfer_funds_safe(101, 102, 500.00);
จุดที่ต้องสังเกต: เราสามารถใช้ ROLLBACK; RETURN; เพื่อสั่งยกเลิกคำสั่งใน Transaction ปัจจุบันและออกจาก Procedure ได้ทันทีเมื่อพบเงื่อนไขที่ผิดปกติ

🔴 Level 3: Refactoring โค้ด Batch Processing ระดับ Production (Chunked Transaction Control)

เปรียบเทียบโค้ดเดิมที่เป็น CREATE FUNCTION ใน Single Giant Transaction กับโค้ดใหม่ที่เป็น CREATE PROCEDURE พร้อมการทำ Chunked COMMIT เป็นระยะ:

❌ โค้ดเดิมแบบ Flawed: FUNCTION ใน Single Giant Transaction พังแถวสุดท้าย Rollback 100%
-- ❌ ฟังก์ชันไม่สามารถใส่ COMMIT ได้ เมื่อรัน 5 ล้านแถว
-- เกิด Memory Leak, WAL Bloat 200GB และ Rollback ทั้งหมดถ้าพัง
CREATE OR REPLACE FUNCTION fn_archive_old_logs_flawed()
RETURNS INT 
LANGUAGE plpgsql 
AS $$
DECLARE
    v_count INT;
BEGIN
    -- 🚨 อัปเดตทีเดียว 5,000,000 แถว ถือ Lock ค้าง 2 ชม. และบวม WAL
    UPDATE logs 
    SET status = 'ARCHIVED' 
    WHERE log_date < '2025-01-01';

    GET DIAGNOSTICS v_count = ROW_COUNT;

    -- 🚨 หากเขียน COMMIT; ตรงนี้ จะเกิด Error:
    -- ERROR: invalid transaction termination!
    RETURN v_count;
END $$;

-- ❌ เรียกใช้ใน Query:
-- SELECT fn_archive_old_logs_flawed();
วิเคราะห์ข้อเสีย: ถือ Row Locks ค้างยาวนาน, เสี่ยงต่อ All-or-Nothing Rollback และอาจทำให้ Live Banking เกิด Lock Wait Timeout จนระบบล่ม
✅ โค้ดใหม่ระดับ Production: PROCEDURE + Chunked COMMIT รัน 3 นาที ปลอดภัย 100%
-- [Statement 1] สร้าง Stored Procedure สำหรับ Batch Processing ปริมาณมหาศาล
CREATE OR REPLACE PROCEDURE pr_archive_old_logs_production(
    p_batch_size INT DEFAULT 10000 -- กำหนดขนาด Chunk ละ 10,000 แถว
)
LANGUAGE plpgsql
AS $$
DECLARE
    -- [บรรทัดที่ 1-3] ประกาศตัวแปรติดตามจำนวนแถวที่ประมวลผล
    v_chunk_updated INT := 0;
    v_total_updated INT := 0;
BEGIN
    LOOP
        -- [บรรทัดที่ 4-13] ประมวลผลเฉพาะกลุ่มย่อย 10,000 แถวโดยใช้ CTE + LIMIT
        WITH target_chunk AS (
            SELECT id FROM logs
            WHERE status = 'PENDING' AND log_date < '2025-01-01'
            ORDER BY id ASC -- 🎯 จัดลำดับ ID แน่นอน ป้องกัน Deadlock
            LIMIT p_batch_size
            FOR UPDATE SKIP LOCKED -- 🎯 ข้ามแถวติดล็อก ไม่ขวางคนอื่น
        ),
        updated_rows AS (
            UPDATE logs
            SET status = 'ARCHIVED',
                archived_at = CURRENT_TIMESTAMP
            FROM target_chunk
            WHERE logs.id = target_chunk.id
            RETURNING logs.id
        )
        SELECT COUNT(*) INTO v_chunk_updated FROM updated_rows;

        -- [บรรทัดที่ 14-16] เช็คถ้าจบงานแล้ว ให้หลุดออกจากลูป
        IF v_chunk_updated = 0 THEN
            EXIT;
        END IF;

        v_total_updated := v_total_updated + v_chunk_updated;

        -- [บรรทัดที่ 17] แจ้ง Log ความคืบหน้า
        RAISE NOTICE 'Batch Success: % rows archived. Total: %', v_chunk_updated, v_total_updated;

        -- 🎯 [บรรทัดที่ 18] สั่ง COMMIT ปิด Transaction ของ Chunk นี้!
        -- ยืนยันข้อมูลลงดิสก์, ปลด Row Locks ทั้งหมด และระบาย WAL ทันที!
        COMMIT;

    END LOOP;

    RAISE NOTICE '== เสร็จสิ้นกระบวนการ ARCHIVE ข้อมูลทั้งหมด % รายการ ==', v_total_updated;
END $$;

-- [Statement 2] เรียกใช้งานผ่านคำสั่ง CALL
CALL pr_archive_old_logs_production(10000);
วิเคราะห์ข้อได้เปรียบ: ปล่อย Row Locks ทุกๆ 10,000 แถว, คืน Memory Buffer, ระบาย WAL สม่ำเสมอ และหากระบบไฟดับกลางทาง ข้อมูลที่ COMMIT ไปแล้วจะปลอดภัย 100%
3

📝 คลังข้อสอบและแบบประเมินความรู้ (18 ข้อ Multiple Choices พร้อมเฉลยยิบ)

แบบทดสอบวัดระดับความเข้าใจ Pillar 2: Stored Procedures & Transaction Control (COMMIT / ROLLBACK), Chunked Batch Processing, WAL Flushing, และข้อสอบสะสมทบทวนครบทั้ง Day 1–5 คลิกเลือกคำตอบ ก, ข, ค, หรือ ง เพื่อดูผลลัพธ์และคำอธิบายอย่างละเอียดทันที

📊 สรุปผลการประเมิน Pillar 2: Procedures (Day 6) 0 / 18
ความคืบหน้า ตอบแล้ว 0 จาก 18 ข้อ

🎯 สรุปหมุดเชื่อมโยง & ก้าวต่อไปสู่ Day 7

บทเรียน Day 6: Pillar 2 - Stored Procedures & Transaction Control แสดงให้เห็นถึงความทรงพลังของการสลับมาใช้ CREATE PROCEDURE ร่วมกับกลไก Chunked Transaction Control (COMMIT / ROLLBACK) ซึ่งช่วยขจัดปัญหา Batch Job พังแล้ว Rollback งานมหาศาลทิ้ง, ลดการถือ Row Lock ค้างยาวนาน และป้องกันปัญหา WAL Accumulation Bloat ได้อย่างเด็ดขาด

🚩 ก้าวต่อไปสู่ Day 7:

Pillar 3 - Event Triggers & Audit Logging Mechanics: ในวันถัดไป เราจะไปเจาะลึกเสาหลักต้นที่ 3 นั่นคือ Triggers & Audit Logging (BEFORE / AFTER Triggers) พร้อมการจัดการโครงสร้างข้อมูล OLD และ NEW เพื่อสร้างระบบบันทึกประวัติการเปลี่ยนแปลงแบบ Real-time Automatic Audit Engine ครับ!