Posted by: siamnobita | 06/23/2010

Design Database Performance

หัวข้อในวันนี้จุดประกายความคิดมาจาก up1’s Blog แห่งเว็บ narisa

ผมไม่เคยใช้ MySQL จึงไม่ขอแสดงความเห็นอะไรเกี่ยวกับหลักการ design บน database ตัวนี้ แต่ก็ลองมาคิดดูว่าถ้าเป็น Oracle แล้วผมจะมี tips อะไรในการออกแบบเพื่อประสิทธิภาพบ้าง

ข้อแรก การออกแบบตารางและข้อมูลในตาราง ผมเห็นด้วยกับคุณ up1 ว่าควรทำ Normalization ก่อน แล้วค่อยทำ Denormalization เมื่อจำเป็น อย่างไรก็ดีคำว่า จำเป็น นั้นเราจะรู้ได้อย่างไร ผมเป็น SA มาหลายปี แต่ไม่เคยจำได้สักทีว่าทำ Normalization แต่ละ form เค้าต้องทำอะไรกันบ้าง ถ้าเอาตารางที่ผมออกแบบไปให้ครูบาอาจารย์ทั้งหลายตรวจ มีหวังได้ศูนย์คะแนนสมชื่อโนบิตะแน่ ๆ

อย่างไรก็ดีผมมีหลักการในการออกแบบเพื่อประสิทธิภาพโดยตั้งต้นจาก output ที่ต้องการ เช่น รูปแบบของรายงาน เงื่อนไขในการค้นหา ความถี่ในการใช้ และที่สำคัญความคาดหวังของผู้ใช้ เวลาที่ผมรวบรวม requirement ผมจะถามความคาดหวังของลูกค้ามาด้วย (แต่ไม่เคยรับปากว่าจะทำให้ แหะ ๆ tip อันนี้ สำคัญนะครับ) จากนั้นผมก็จะเริ่มออกแบบว่าจะมีข้อมูลอะไรบ้าง แต่ละข้อมูลควรวางไว้ที่ตารางไหน และแต่ละตารางจะมีความสัมพันธ์กันอย่างไร นี่คือการ normalize ของผม ซึ่งจะเน้นที่ข้อมูลไม่ซ้ำซ้อน และไม่เก็บค่าที่คำนวณได้จาก column อื่น ซึ่งจะทำให้การออกแบบหน้าจอ input ง่ายขึ้น

จากนั้นก็ย้อนมาดู query หลัก ๆ ที่เป็น concern ของผู้ใช้ ย้อนกลับมาว่าเรายอมวางข้อมูลเดียวกันซ้ำสองที่ในเรื่องใดบ้าง หรือเก็บค่าจากการคำนวณเอาไว้ล่วงหน้าหรือไม่ นี่คือการ denormalize ตรงนี้ผมอาจต้องขอค้านคุณ up1 เล็กน้อยที่ว่า การ join เร็วกว่าการ denormalize ไม่น่าจะจริงครับ ขึ้นกับว่าการ denormalize นั้นทำแล้วตอบตรงคำถามหรือไม่ต่างหาก (แต่ตรงนี้ต้องขอเสริมว่า ไม่ต้องกลัวการ join นั้น เป็นหลักการที่ถูกต้องครับ การ join เป็นงานหลักของ RDBMS อยู่แล้ว)

ข้อที่สอง การเลือก primary key ผมจะมองหา natural key ก่อนตัดสินใจใช้ surrogate key ผมมักจะดึง primary key ของ parent มาเป็นส่วนหนึ่งของ primary key ของตารางที่เป็น child เสมอ มองในแง่การ normalize นี่เป็นจุดที่ขัดแย้ง แต่ผมพบว่ามันมักเกิดประโยชน์ในภายหลัง เมื่อเราต้องการดูข้อมูลในตารางที่เป็น หลาน เหลน โหลน ของตารางแรก เราจะลดการ join 3-4 ตารางได้ ถึงจะไม่กลัวการ join แต่เลี่ยงได้ก็ไม่เสียหายนี่ครับ

ปัญหาเดียวของการกำหนด primary key แบบนี้ คือเราไปเลือก primary key ตัวที่มีสิทธิ์เปลี่ยนแปลงได้ กติกาของ primary key มีอยู่ว่าต้อง unique และมีค่าเสมอไม่เป็น null แค่สองข้อเท่านั้น แต่ primary key ที่ดีควรมีกติกาเพิ่มอีกหนึ่งข้อคือ ห้าม update ด้วย ผมมี case ที่จำฝังใจมาตลอด คือ member no ของบริษัทประกันภัยแห่งหนึ่ง ซึ่งเขาใช้ตัวเลขหลักสุดท้ายในการบอกว่า 0 คือผู้เอาประกันหลัก 1 คือคู่สมรส และ 2,3,.. คือบุตร member no นี้ไม่มีวันซ้ำกัน (ณ ขณะใด ๆ) และต้องมีค่าเสมอ แล้วผมก็ใช้มันเป็น primary key สิ่งทีผมมองข้ามคือ วันดีคืนดีผู้เอาประกันหลักหย่าแล้วแต่งงานใหม่ คนเก่าก็ลบจากระบบไม่ได้เพราะมีตารางอื่น link มาเต็มไปหมด คนใหม่ก็จำเป็นต้องใช้รหัสของคนเก่า สิ่งที่ต้องทำคือต้องเขียนโปรแกรมตาม update member no ของคนเก่า ในตารางลูกหลานเหลนโหลนทั้งหมด แล้วค่อย insert คนใหม่เข้ามา นิทานเรื่องนี้สอนให้รู้ว่า primary key ที่มีความหมายในตัวเองแบบนี้ เสี่ยงกับการเปลี่ยนแปลงภายหลัง ถึงกระนั้นก็ตามผมก็ยังเลือกใช้ primary key ที่มีความหมายอยู่ดี แต่ใช้ความระมัดระวังมากขึ้น

ข้อที่สาม การเลือกชนิดของข้อมูล คุณ up1 พูดถึงกรณีของ MySQL ที่เกี่ยวกับเรื่องนี้ ของ Oracle ก็มีเหมือนกัน หลักการง่าย ๆ ของผมก็คือ เก็บวันที่ไว้ใน Date เก็บตัวเลขไว้ใน Number เรื่องนี้มีผลกับ performance โดยตรงเลย เพราะการเลือกใช้ชนิดของข้อมูลให้ตรงกับข้อมูลที่จะเก็บนั้น เป็นทางหนึ่งที่จะบอกให้ optimizer รู้ว่าการกระจายตัวของข้อมูลที่แท้จริงเป็นอย่างไร จึงจะเลือก execution plan ที่เหมาะสมให้ สมัยเป็น SA ใหม่ ๆ มีคนบอกผมให้เก็บตัวเลขที่ไม่ต้องมีการคำนวณเช่น id ไว้ใน varchar2 ซึ่งก็โอเคตราบเท่าที่เราค้นหาข้อมูลด้วยเงื่อนไข id = ??? เท่านั้น หากเราค้นหาด้วย id >= ??? หรือ between … and … ชนิดของข้อมูลที่เราใช้มีผลทำให้ plan ผิดได้ทันที

เคยมีคนบอกผมว่าไม่อยากใช้ date ใน oracle เพราะเคยเจอปัญหากับ database ยี่ห้ออื่น หรือประมาณว่าถ้าย้ายไป database อื่นจะเกิดปัญหา อะไรทำนองนี้ นี่เป็นอีกประเด็นที่สำคัญ หลักการออกแบบที่ดีต้องขึ้นกับ database ที่เราใช้งานจริงเท่านั้น การมองเผื่อไปว่าอนาคตต้องไปใช้กับ database อื่น ๆ อีก เป็นตัวถ่วงประสิทธิภาพอย่างแรงครับ เพราะเราไม่สามารถใช้งาน database ของเราอย่างเต็มที่ จะโดนห้ามไปหมดทุกอย่างเลย date ก็ห้ามใช้ ค่า null ก็ห้ามใช้ ฟังก์ชั่นหลาย ๆ ตัวก็ห้ามใช้ด้วย ผมว่าคนที่พูดแบบนี้ไม่เคยศึกษาความสามารถของ DBMS อย่างจริงจังครับว่ามันทำอะไรได้บ้าง เลยคิดว่ามันก็แค่ตาราง กับ select command เท่านั้น

อีกประเด็นที่คู่กับการกำหนดชนิดของข้อมูล คือการกำหนดขนาดของข้อมูลครับ การกำหนดขนาดสูงสุดของข้อมูลที่เราประมาณไว้เป็นการบอกให้ฐานข้อมูลช่วยกรองข้อมูลที่ผิดเพี้ยนออกให้ตั้งแต่ก่อนนำเข้า เราอาจเผื่อเพิ่มไว้สักหลักสองหลักอันนี้ไม่มีปัญหา หรือเผื่อน้อยเกินไป เพิ่มทีหลังก็ยังไหว แต่หลาย ๆ ครั้ง ผมเคยเห็นประกาศเป็น number หรือเป็น varchar2(4000) โดยอ้างว่า oracle ใช้พื้นที่เก็บข้อมูลตามจริงนั้น ซึ่งเป็นข้ออ้างที่ไม่เข้าท่าเลยครับ คนที่ออกแบบตารางแบบนี้เป็น SA ที่ไม่รับผิดชอบงานที่ตัวเองต้องทำ ถึงจะไม่ได้ใช้พื้นที่เก็บข้อมูลมากขึ้น แต่ Programmer ที่จะเขียนหน้าจอบันทึกข้อมูลต้องเผื่อไว้แค่ไหนครับ หน้ารายงานด้วย ให้เขาเดาเอาเองหรือครับ นี่เป็นหน้าที่ของ SA ต่างหากที่จะต้องเดา ไม่ใช่ Programmer เดาเสร็จแล้วก็บอกเขาด้วย definition ของตารางนี่แหละครับ ไม่ต้องอาศัย program spec. อะไรให้มันยุ่งยากอีก

แล้วถ้าประกาศ varchar2(4000) รู้ไหมครับว่า เกิดผลอะไรกับการสร้าง index ตอนสร้าง index oracle จะเช็คขนาดของ index entry ที่เป็นไปได้จากค่าสูงสุดที่เราประกาศไว้นี่แหละ แล้วถ้าเกินขนาดของ block (ลบ overhead อีกเล็กน้อย) ก็จะไม่อนุญาตให้สร้าง index นั้น ผลก็คือ composite index ที่ประกอบด้วย varchar2(4000) 2 column สร้างไม่ได้บน default block size (8k)

เขียนเป็นคุ้งเป็นแคว เพิ่งออกแบบแค่ตารางเอง ยังไม่ได้ออกแบบ index เลย เอาไว้ต่อคราวหน้านะครับ (ถ้าไม่ลืมเสียก่อน)


Responses

  1. ขอบคุณสำหรับความรู้ดีๆ ครับ


ใส่ความเห็น

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / เปลี่ยนแปลง )

Twitter picture

You are commenting using your Twitter account. Log Out / เปลี่ยนแปลง )

Facebook photo

You are commenting using your Facebook account. Log Out / เปลี่ยนแปลง )

Google+ photo

You are commenting using your Google+ account. Log Out / เปลี่ยนแปลง )

Connecting to %s

หมวดหมู่

%d bloggers like this: