Friday, April 30, 2010

ความลึกลับของ Trigger ตอนที่ 1

ข้อเขียนนี้ช่วยฉัน: 

"กลางดึกคืนหนึ่ง... สมชาติยังคงนั่งเขียนโปรแกรมของเขาอยู่ นี่ก็เลยเวลาเลิกงานไปหกชั่วโมงแล้วสินะ สายตาเขายังคงจ้องเขม็งไปที่จอคอมพิวเตอร์ พยายามที่จะหาเหตุผลว่าทำไมข้อมูลในตารางที่โปรแกรมของ "คนเก่า" เขาเขียนไว้ จึงมีข้อมูลแปลก ๆ เก็บไว้ในคอลัมน์ที่ในโปรแกรมก็ไม่ได้แสดงว่าไปอัพเดท เขาเคยได้ยินว่าที่ตึกแห่งนี้เคยมีโปรแกรมเมอร์คนหนึ่งเสียชีวิตขณะนั่ง เขียนโปรแกรมในคืนวันหนึ่ง พอรุ่งเช้าก็มีคนพบเขานอนฟุบอยู่กับคีย์บอร์ดโดยปราศจากลมหายใจแล้ว หรือว่า....โปรแกรมนี้....มัน....."

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

ทริกเกอร์ (Trigger) มีลักษณะคล้าย Stored Procedure ประเภทหนึ่งที่จะถูกกระตุ้นให้ทำงานเมื่อมีการกระทำกับตารางในฐานข้อมูล การกระทำเช่น การเพิ่ม (Insert), การแก้ไข (Update), หรือการลบ (Delete) จะมีผลให้ทริกเกอร์ทำงาน ด้วยคุณสมบัติอันน่าทึ่งอันนี้ทำให้ผู้ที่ได้รู้จักทริกเกอร์ใหม่ ๆ จะรู้สึกตื่นเต้น และอยากหาทางใดทางหนึ่งในการใช้มันให้ได้ เช่นการแปลงค่าก่อนนำไปใส่ไว้ในคอลัมน์, ใช้เช็คค่าที่ถูกต้องก่อนนำไปใส่ในคอลัมน์, หรือการสร้างข้อมูล Audit เป็นต้น ในขณะที่ทริกเกอร์มีคุณสมบัติดี ๆ หลายประการ แต่ขณะเดียวกันการใช้ทริกเกอร์ก็มีข้อพึงระวังเพื่อไม่ให้เกิดเรื่องลึกลับ อย่างที่สมชาติเจอ

สาเหตุหลักที่ทำให้ควรหลีกเลี่ยงการใช้ทริกเกอร์
1. ทริกเกอร์ทำให้ยุ่งยากในการดูแลในระยะยาว เนื่องจากตัวมันซ่อนอยู่ และรันโดยตัวมันเอง ซึ่งอาจจะเกิดขึ้นเมื่อใดก็ได้ หากถูกกระตุ้นโดยเหตุการณ์ต่าง ๆ ซึ่งคนส่วนใหญ่จะไม่ได้นึกถึง หรือลืมไปว่ามีทริกเกอร์อยู่ตรงนั้น
2. ทริกเกอร์มักจะถูกนำมาใช้อย่างไม่เหมาะสม โดยเฉพาะสถานการณ์ที่ไม่สามารถ Rollback ได้

ความยุ่งยากในการดูแลในระยะยาว
สมมติว่าคุณ กำลังทำงานในโครงงานของคนอื่น หรือเพราะว่าคุณต้องทำงานที่คนอื่นทำค้างไว้ ถ้าคุณต้องทำการสำรวจตัวโปรแกรม หรืออาจจะเป็น Stored Procedure ก็อาจจะเป็นวิสัยที่ทำได้ แต่ถ้าคุณพบกับโปรแกรมที่อาศัยทริกเกอร์ที่กระจายอยู่บนตารางต่าง ๆ มากมาย คุณคงต้องเกาหัวแกร็ก ๆ เพราะไม่รู้จะเริ่มตรงไหนดี?

ทริกเกอร์ทำ งานแบบเป็นผลข้างเคียงและเป็นเอกเทศ ไม่ได้อยู่กับโฟลวงานหลักที่คุณสามารถตรวจดูได้ทีละบรรทัด เมื่อคุณทำการอัพเดทเรคคอร์ดระบบอาจจะตอบกลับว่า 1 Row Proccessed แต่อาจจะมีสิ่งต่าง ๆ อีก 500 สิ่งเกิดขึ้นจากทริกเกอร์ก็ได้ นอกจากนี้ทริกเกอร์มักไม่ได้รับการจัดทำเป็นเอกสารอย่างเหมาะสม หรืออาจจะถูกมองข้ามไปเมื่อต้องทำการตรวจสอบตัวโปรแกรม

ลองดูจากตัว อย่างในเว็บไซท์ของ Tom Kyte ดังต่อไปนี้
"เรามีปัญหาเกี่ยวกับการอัพ เดทคอลัมน์ (varchar2) ผลการอัพเดทแสดงว่ามี 1,972 แถวที่ถูกอัพเดท (และเราได้ commit แล้ว) หลังจากนั้นเราดึงข้อมูลมาดูพบว่าคอลัมน์ไม่ได้ถูกอัพเดท แต่เมื่อเราอัพเดทคอลัมน์อื่นกลับทำให้ค่าในคอลัมน์นี้มีค่าเปลี่ยนแปลงไป"

เหตุการณ์ นี้จบเมื่อผู้ถามพบว่ามีประโยคนี้ :new.name := :new.fname || :new.lname อยู่ในทริกเกอร์ ซึ่งหมายถึงการที่ระบุให้กำหนดค่าให้กับคอลัมน์ name ด้วยค่าของ fname || lname (คือคำสั่งให้อัพเดทคอลัมน์ name โดยเอา fname (ชื่อ) ไปรวมกับ lname (นามสกุล) แล้วนำไปใส่ในคอลัมน์ name)

ผู้ออกแบบโปรแกรมไม่ควรจะใช้คอลัมน์ name ซึ่งรวมเอาชื่อกับนามสกุลเข้าไว้ด้วยกัน เพราะเรามีคอลัมน์ชื่อกับคอลัมน์นามสกุลอยู่แล้ว เราสามารถจะใส่ฟังก์ชั่น Concatenate (||) ไว้ในวิว โดยไม่ต้องนำค่าชื่อ + นามสกุลไปเก็บไว้ในคอลัมน์จริง ๆ นอกไปจากนี้ยังมีการเขียนทริกเกอร์อัพเดทคอลัมน์ name ที่ทำให้เกิดความสับสนขึ้นไปอีก เพราะเมื่อผู้ใช้ระบบต้องการอัพเดทคอลัมน์ name จะทำไม่ได้ถ้าไม่ได้หยุด (Disable) การทำงานของทริกเกอร์ไว้ แต่เมื่ออัพเดทคอลัมน์ fname, lname ข้อมูลในคอลัมน์ name กลับเปลียนไป

SQL> create table test_name (fname varchar2(10), lname varchar2(20), name varchar2(30));

Table created

SQL> insert into test_name values ('Somchai','Klaharn','Somchai Klaharn');

1 row inserted

SQL> commit;

Commit complete

SQL> select * from test_name;

FNAME LNAME NAME
---------- -------------------- ------------------------------
Somchai Klaharn Somchai Klaharn


SQL> create or replace trigger upd_testname_trg
2 before update on test_name
3 for each row
4 declare
5 -- local variables here
6 begin
7 :new.name := :new.fname || ' ' || :new.lname;
8 end upd_testname_trg;
9 /

Trigger created

SQL> update test_name set fname = 'Tanakorn',lname = 'Tavornsasnavong';

1 row updated

SQL> select * from test_name;

FNAME LNAME NAME
---------- -------------------- ------------------------------
Tanakorn Tavornsasnavong Tanakorn Tavornsasnavong

SQL> commit;

Commit complete

SQL> update test_name set name = 'Somchai Klaharn';

1 row updated

SQL> commit;

Commit complete

SQL> select * from test_name;

FNAME LNAME NAME
---------- -------------------- ------------------------------
Tanakorn Tavornsasnavong Tanakorn Tavornsasnavong

SQL> alter trigger upd_testname_trg disable;

Trigger altered

SQL> update test_name set name = 'Somchai Klaharn';

1 row updated

SQL> select * from test_name;

FNAME LNAME NAME
---------- -------------------- ------------------------------
Tanakorn Tavornsasnavong Somchai Klaharn


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

การนำไปใช้อย่างไม่เหมาะสม

บ่อยครั้งที่ ผู้สร้างทริกเกอร์อาจจะไม่ได้คิดถึงพฤติกรรมของมันอย่างทั่วถึง ดังเช่น Code ต่อไปนี้

SQL> create trigger send_mail
2 after insert on purchase_order
3 for each row
4 begin
5 utl_mail.send
6 (sender=>'database@x.com',
7 recipients=>'orders@x.com',
8 subject=>'New Order '||:new.po_number,
9 message=>'... ');
10 end;
11 /

Trigger created.


Code ข้างต้นถูกต้องในแง่ของไวยกรณ์ และหลังจากที่ Insert ข้อมูลลงในตารางทริกเกอร์ก็จะทำงานได้อย่างถูกต้อง แต่มีความผิดพลาดในการนำไปใช้ กล่าวคือถ้าเกิดการ Rollback เมล์ที่ถูกส่งไปให้กับผู้รับจะไม่สามารถยกเลิกได้ ในการนี้ SMTP (Simple Mail Transfer Protocol) จะไม่รับรู้ถึงการยกเลิกหรือ Rollback ในฐานข้อมูล การ Rollback อาจจะเกิดจากสาเหตุอื่น ๆ ซึ่งอาจจะไม่ได้เกิดจากความจงใจเช่นการ Disconnect แบบไม่ปกติจากการปิดโปรแกรม SQL*Plus, การเกิดปัญหาเน็ทเวอร์ค เป็นต้น การใช้ทริกเกอร์กับยูทิลิตี้ต่าง ๆ เช่น UTL_FILE (จัดการไฟล์), UTL_MAIL (จัดการเมล์), UTL_HTTP (การใช้บริการของ HTTP) ฯลฯ ล้วนประสบปัญหาเดียวกัน ดังนั้นข้อควรจำประการหนึ่งในการใช้ทริกเกอร์คือ "อย่าใช้ทริกเกอร์กับระบบงานที่ไม่สามารถ Rollback ได้"

(ยังไม่จบ นะครับ ยังมีตอนต่อไป...ขอขอบคุณที่ให้ความสนใจและโปรดติดตามตอนต่อไปนะครับ)

เรียบเรียงจาก "The Trouble with Triggers"
โดย Tom Kyte, Oracle Magazine Volume XXII / Number 5 September/October2008, ASK TOM

4 comments:

Biggy2day said...

น่าสนใจคับ ขอบคุณคับ

Anonymous said...

เยี่ยมมมม

Somkid Tantrakul said...

ให้ความรู้ดีมากครับ..ขอบคุณครับ

ปรีชา เหล่าชำนาญกิจ said...

ขอบคุณครับ สำหรับความรู้ดีๆ เข้าใจง่าย น่าติดตามมากๆเลย สนใจ ๆ

Post a Comment