เข้าใจ ON DELETE ใน PostgreSQL: CASCADE, SET NULL และ RESTRICT เลือกใช้อย่างไรให้ข้อมูลไม่พัง

10 นาที 4 views บันทึกเป็น PDF
เข้าใจ ON DELETE ใน PostgreSQL: CASCADE, SET NULL และ RESTRICT เลือกใช้อย่างไรให้ข้อมูลไม่พัง

มือใหม่หัดออกแบบฐานข้อมูลต้องรู้! มาดูวิธีจัดการข้อมูลเมื่อมีการลบข้อมูลต้นทางด้วย ON DELETE ทั้งแบบ CASCADE, SET NULL และ RESTRICT ให้ข้อมูลในระบบไม่กลายเป็นขยะ

ทำความเข้าใจความสัมพันธ์ในฐานข้อมูลก่อนเริ่มต้น

เวลาเราออกแบบฐานข้อมูล (แหล่งเก็บข้อมูลที่จัดระเบียบไว้) เรามักจะมีตารางสองตารางที่เกี่ยวข้องกันเสมอ เช่น ตารางผู้ใช้งาน และตารางออเดอร์สินค้า ความสัมพันธ์นี้เรียกว่า Foreign Key (กุญแจเชื่อมโยงข้อมูลระหว่างสองตาราง) ซึ่งเป็นหัวใจสำคัญที่ทำให้ข้อมูลในระบบของเราไม่มั่วซั่วและมีความถูกต้องแม่นยำอยู่เสมอ

ปัญหาจะเกิดขึ้นเมื่อเราต้องการลบข้อมูลฝั่งหนึ่งออกไป เช่น ถ้าเราลบชื่อผู้ใช้งานทิ้ง แล้วออเดอร์ที่ผู้คนคนนั้นเคยสั่งไว้จะทำอย่างไรต่อ นี่คือจุดที่ ON DELETE (คำสั่งจัดการข้อมูลเมื่อข้อมูลต้นทางถูกลบ) เข้ามามีบทบาทสำคัญมากในการตัดสินใจว่าเราจะจัดการกับข้อมูลที่เหลืออยู่ (Child row) อย่างไรให้เหมาะสมกับธุรกิจ

ถ้าเปรียบเทียบให้เห็นภาพ ลองนึกถึงสมุดรายชื่อเพื่อน ถ้าเพื่อนคนนี้ลาออกไปจากบริษัท เราควรจะฉีกหน้ากระดาษที่จดเบอร์โทรเขาทิ้งไปเลย หรือจะแค่ลบชื่อออกแล้วเหลือเบอร์ทิ้งไว้ หรือจะเก็บชื่อไว้แต่เปลี่ยนสถานะว่าเขาไม่อยู่แล้ว การเลือกวิธีที่ถูกต้องจะช่วยป้องกันไม่ให้ข้อมูลในระบบของเรากลายเป็น ข้อมูลขยะ (ข้อมูลที่ไม่มีประโยชน์และหาความเชื่อมโยงไม่ได้) ในอนาคต

CASCADE: ลบให้เหี้ยนเมื่อต้นทางหายไป

CASCADE (การลบแบบลูกโซ่) เป็นวิธีที่ตรงไปตรงมาที่สุด หากคุณเลือกว่าเมื่อข้อมูลหลักถูกลบไปแล้ว ข้อมูลลูกที่เกี่ยวข้องก็ไม่มีความหมายอีกต่อไป วิธีนี้จะจัดการลบข้อมูลที่เหลือทิ้งให้โดยอัตโนมัติทันทีที่คำสั่งลบทำงาน ทำให้เราไม่ต้องมาคอยนั่งไล่ลบข้อมูลย่อยด้วยตัวเองให้เสียเวลา

วิธีนี้เหมาะมากกับสถานการณ์ที่ข้อมูลลูกไม่สามารถอยู่ได้หากไม่มีข้อมูลหลัก เช่น รายการสินค้าในออเดอร์หนึ่งๆ ถ้าเราลบออเดอร์นั้นทิ้งไป รายการสินค้าเหล่านั้นก็ไม่มีความจำเป็นต้องเก็บไว้ในฐานข้อมูลอีกต่อไป เพราะมันไม่ได้ระบุตัวตนได้ด้วยตัวเอง การใช้ CASCADE จึงช่วยให้ฐานข้อมูลสะอาดอยู่เสมอ

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

-- สร้างตารางออเดอร์และรายการสินค้าแบบ CASCADE
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_name TEXT NOT NULL
);

CREATE TABLE order_items (
    id SERIAL PRIMARY KEY,
    order_id INTEGER REFERENCES orders(id) ON DELETE CASCADE,
    product_name TEXT NOT NULL
);

ในตัวอย่างนี้ บรรทัด REFERENCES orders(id) ON DELETE CASCADE คือการบอก PostgreSQL (ระบบจัดการฐานข้อมูลยอดนิยม) ว่าถ้าลบแถวในตาราง orders ให้ลบข้อมูลใน order_items ที่มี order_id ตรงกันทิ้งทั้งหมดทันที

ผลลัพธ์ที่ควรเห็นคือ เมื่อคุณรันคำสั่ง DELETE FROM orders WHERE id = 1; ข้อมูลสินค้าทั้งหมดที่เคยผูกกับออเดอร์หมายเลข 1 จะถูกลบออกจากฐานข้อมูลไปโดยอัตโนมัติโดยที่คุณไม่ต้องเขียนคำสั่งลบแยกต่างหาก

SET NULL: เก็บข้อมูลลูกไว้แต่ตัดการเชื่อมต่อ

ในบางกรณี ข้อมูลลูกอาจจะยังมีความสำคัญแม้ว่าข้อมูลหลักจะถูกลบไปแล้ว SET NULL (การเปลี่ยนค่าเป็นความว่างเปล่า) จะเข้ามาช่วยในจังหวะนี้ โดยมันจะเข้าไปเปลี่ยนค่าในช่องที่เชื่อมโยงอยู่ให้กลายเป็นค่าว่าง (NULL) แทนที่จะลบแถวนั้นทิ้งไปทั้งแถว

ลองนึกถึงระบบจัดการตั๋วงานบริการ ถ้าพนักงานที่ดูแลตั๋วลาออกไป แต่เรายังต้องเก็บข้อมูลตั๋วใบนั้นไว้เพื่อตรวจสอบย้อนหลัง การใช้ SET NULL จะทำให้ช่อง "ผู้ดูแล" กลายเป็นค่าว่าง ซึ่งสื่อความหมายได้ว่า "ตั๋วใบนี้ยังไม่มีคนรับผิดชอบ" แทนที่จะทำลายข้อมูลตั๋วใบนั้นทิ้งไปทั้งหมด

การใช้คำสั่งนี้ต้องระวังเรื่องการตั้งค่าคอลัมน์ในฐานข้อมูลด้วย เพราะถ้าคุณตั้งค่าคอลัมน์นั้นให้เป็น NOT NULL (ห้ามเป็นค่าว่าง) ตั้งแต่แรก ระบบจะเกิดข้อผิดพลาดทันทีเมื่อมีการลบข้อมูลหลัก เพราะมันพยายามจะใส่ค่าว่างลงไปในช่องที่ห้ามว่างนั่นเอง

-- สร้างตารางตั๋วและพนักงานแบบ SET NULL
CREATE TABLE agents (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL
);

CREATE TABLE tickets (
    id SERIAL PRIMARY KEY,
    assignee_id INTEGER REFERENCES agents(id) ON DELETE SET NULL,
    title TEXT NOT NULL
);

โค้ดด้านบนกำหนดให้ assignee_id เป็น ON DELETE SET NULL ซึ่งหมายความว่าเมื่อ agents ถูกลบ ค่าใน assignee_id ของตาราง tickets จะเปลี่ยนเป็นค่าว่างแทนที่จะหายไป

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

RESTRICT และ NO ACTION: ป้องกันการลบเพื่อความปลอดภัย

ถ้าข้อมูลลูกของคุณมีความสำคัญระดับที่ว่า "ห้ามหายไปเด็ดขาด" หรือ "ต้องให้คนมาจัดการก่อนลบ" คุณควรใช้ RESTRICT (การจำกัดการกระทำ) หรือ NO ACTION (ไม่ทำอะไรเลย) เพื่อหยุดยั้งการลบข้อมูลหลักไว้ก่อนที่ความเสียหายจะเกิดขึ้น

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

ความแตกต่างเล็กน้อยระหว่างสองคำสั่งนี้คือ RESTRICT จะทำงานทันทีและไม่ยอมให้เลื่อนการตรวจสอบไปทีหลัง ส่วน NO ACTION ในบางกรณีอาจจะยอมให้มีการตรวจสอบล่าช้าได้ถ้ามีการตั้งค่าการทำธุรกรรมแบบพิเศษ แต่โดยทั่วไปสำหรับมือใหม่ถือว่าทั้งคู่ทำงานคล้ายกันคือ "หยุดการลบ" เหมือนกันครับ

-- ใช้ RESTRICT เพื่อป้องกันการลบข้อมูลหลัก
CREATE TABLE customers (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL
);

CREATE TABLE invoices (
    id SERIAL PRIMARY KEY,
    customer_id INTEGER REFERENCES customers(id) ON DELETE RESTRICT,
    amount DECIMAL NOT NULL
);

ในตัวอย่างนี้ การใส่ ON DELETE RESTRICT จะทำให้คำสั่ง DELETE FROM customers WHERE id = 1; ล้มเหลวทันทีหากยังมีข้อมูลในตาราง invoices ที่อ้างอิงถึงลูกค้าหมายเลข 1 อยู่

ผลลัพธ์ที่ควรเห็นคือระบบจะส่ง Error (ข้อความแจ้งเตือนเมื่อเกิดความผิดพลาด) ขึ้นมาว่าการลบไม่สามารถทำได้เนื่องจากติดข้อจำกัดของ Foreign Key ทำให้คุณต้องกลับไปลบหรือย้ายข้อมูลใบเสร็จก่อนถึงจะลบชื่อลูกค้าได้สำเร็จ

จุดที่มือใหม่มักพลาดและข้อควรระวัง

หนึ่งในจุดที่มือใหม่มักจะพลาดบ่อยที่สุดคือการลืมสร้าง Index (ดัชนีช่วยค้นหาข้อมูล) ให้กับคอลัมน์ที่เป็น Foreign Key หลายคนเข้าใจว่าฐานข้อมูลจะทำให้เองโดยอัตโนมัติ แต่จริงๆ แล้ว PostgreSQL ไม่ได้สร้างให้ ซึ่งจะส่งผลเสียอย่างมากต่อความเร็วในการลบข้อมูลเมื่อตารางมีขนาดใหญ่ขึ้น

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

อีกเรื่องคือการเปลี่ยนค่า ON DELETE หลังจากสร้างตารางไปแล้ว คุณไม่สามารถแก้ค่านี้ได้ด้วยคำสั่ง ALTER TABLE แบบง่ายๆ คุณต้องลบเงื่อนไขเก่าออกแล้วสร้างใหม่ ซึ่งหากตารางมีข้อมูลเยอะ ขั้นตอนนี้อาจทำให้ฐานข้อมูลหยุดชะงักชั่วคราวได้ ดังนั้นวางแผนให้ดีตั้งแต่ตอนออกแบบจะดีที่สุดครับ

สรุป: เลือกใช้อะไรในงานจริง?

การเลือก ON DELETE ที่เหมาะสมไม่ใช่เรื่องของเทคนิคที่ซับซ้อน แต่เป็นเรื่องของความเข้าใจใน "ความหมาย" ของข้อมูล ให้ลองถามตัวเองว่า "ถ้าข้อมูลหลักหายไป ข้อมูลลูกยังมีความหมายอยู่ไหม" ถ้าคำตอบคือไม่ ก็ใช้ CASCADE เพื่อความสะอาดของระบบ แต่ถ้าคำตอบคือยังมีค่าอยู่ ให้พิจารณา SET NULL หรือ RESTRICT แทน

ตัวอย่างการนำไปใช้จริง: หากคุณกำลังสร้างระบบบล็อกส่วนตัว สำหรับตาราง posts (บทความ) และ comments (ความคิดเห็น) การลบบทความทิ้งควรใช้ CASCADE เพราะคอมเมนต์ใต้บทความไม่มีความหมายหากบทความต้นทางหายไป แต่ถ้าเป็นระบบจัดการสมาชิก users และ posts คุณอาจใช้ RESTRICT เพื่อป้องกันไม่ให้ลบสมาชิกที่ยังมีบทความเขียนทิ้งไว้อยู่ เพื่อป้องกันข้อมูลหายโดยไม่ตั้งใจ

สุดท้ายจำไว้ว่า SET NULL นั้นสะดวกแต่ต้องระวังเรื่องข้อจำกัดของคอลัมน์ และการทำ Index เป็นสิ่งจำเป็นที่ห้ามมองข้ามเด็ดขาด หากคุณเริ่มต้นฝึกฝนด้วยการออกแบบฐานข้อมูลให้รัดกุมตั้งแต่แรก คุณจะประหยัดเวลาในการแก้ปัญหาหน้างานไปได้มหาศาลเมื่อต้องทำโปรเจกต์ขนาดใหญ่ขึ้นครับ


ที่มา: ON DELETE SET NULL vs CASCADE vs RESTRICT in PostgreSQL: Which to Use — DEV Community

แชร์บทความ

Facebook X LINE

บทความที่เกี่ยวข้อง

เทคนิคเขียนแอป Flutter สำหรับ Meta Smart Glasses ให้ลื่นไหลและมีประสิทธิภาพ

เทคนิคเขียนแอป Flutter สำหรับ Meta Smart Glasses ให้ลื่นไหลและมีประสิทธิภาพ

เรียนรู้วิธีเขียนแอป Flutter เชื่อมต่อ Meta Smart Glasses ให้ทำงานเร็ว ไม่กระตุก ด้วยการวางสถาปัตยกรรมโค้ดและการจัดการข้อมูลแบบมือโปรที่มือใหม่ทำตามได้จริง

ที่มา: DEV Community

7 hours ago 10 นาที
5 views
เปรียบเทียบ WebSocket, SSE และ Polling เลือกวิธีทำระบบ Real-Time ให้เหมาะกับงาน

เปรียบเทียบ WebSocket, SSE และ Polling เลือกวิธีทำระบบ Real-Time ให้เหมาะกับงาน

อยากทำระบบ Real-Time แต่ไม่รู้จะเลือกใช้ Polling, SSE หรือ WebSocket ดี? มาดูวิธีเลือกใช้ให้เหมาะกับงาน เพื่อให้แอปของคุณทำงานลื่นไหลและประหยัดทรัพยากรเซิร์ฟเวอร์

ที่มา: DEV Community

10 hours ago 10 นาที
5 views