Back

Relational Database: ก้าวต่อไป Advance ขึ้นBlur image

บทความนี้เป็น Next Step ต่อจาก พื้นฐาน Relational Database และ SQL จะพาสำรวจหัวข้อที่นักพัฒนาและผู้ดูแลระบบฐานข้อมูล (Database Administrator, DBA) ควรเข้าใจ ได้แก่ การออกแบบฐานข้อมูลด้วย ER Diagram, Primary/Foreign Key และ Referential Integrity, Normalization (1NF–3NF), Views, Index, การควบคุมการเข้าถึง (Access Control), การสำรองและกู้คืนข้อมูล (Backup & Recovery) และแนวปฏิบัติในการดูแลระบบ

ทุกหัวข้อมี SQL ให้ copy แล้วรันได้ทันทีในแท็บ SQL ของ phpMyAdmin หรือ Adminer บน CAMPP เนื้อหาใช้ฐานข้อมูล testdb และตาราง patient/province จากภาคแรกเป็นฐาน และขยายด้วยตัวอย่างเพิ่มเติมในแต่ละหัวข้อ หากยังไม่ได้ติดตั้ง CAMPP ให้เริ่มจากหัวข้อ “ก่อนเริ่มต้น” ด้านล่าง


ก่อนเริ่มต้น: ติดตั้ง CAMPP และเปิด phpMyAdmin#

CAMPP เป็นสภาพแวดล้อมสำหรับพัฒนาและทดสอบเว็บแอปพลิเคชันและฐานข้อมูลบนเครื่อง (local) ฟรีและติดตั้งง่าย รวมเว็บเซิร์ฟเวอร์ ระบบฐานข้อมูล (MySQL/MariaDB) และเครื่องมือจัดการฐานข้อมูล ไว้ในแพ็กเกจเดียว

ดาวน์โหลดและติดตั้ง CAMPP ได้จาก https://campp.melivecode.com/

CAMPP Install Config

เมื่อติดตั้งแล้ว เปิด CAMPP แล้วกด Start เพื่อเริ่มบริการต่าง ๆ

CAMPP Dashboard

จากนั้นเปิด phpMyAdmin ผ่านหน้าเว็บของ CAMPP เพื่อจัดการฐานข้อมูลผ่าน GUI หรือคลิกแท็บ SQL เพื่อพิมพ์และรันคำสั่ง SQL ที่ใช้ในบทความนี้ได้ทันที

phpMyAdmin

เคล็ดลับ: หากคุณทำภาคแรกจบ ฐานข้อมูล testdb พร้อมตาราง province และ patient จะยังอยู่ สามารถทำตามบทความนี้ต่อได้เลย


1. การออกแบบฐานข้อมูลและ ER Diagram#

การออกแบบฐานข้อมูลที่ดีคือรากฐานของระบบที่เร็ว ยืดหยุ่น และไม่มีข้อมูลซ้ำซ้อน เครื่องมือที่นิยมใช้ในขั้นตอนนี้คือ ER Diagram (Entity-Relationship Diagram) ซึ่งเป็นภาพแสดงโครงสร้างของข้อมูลก่อนลงมือสร้างตาราง

Entity, Attribute และ Relationship#

  • Entity (เอนทิตี) — สิ่งที่เราต้องการเก็บข้อมูล เช่น Student, Course, Patient
  • Attribute (แอตทริบิวต์) — คุณสมบัติของ entity เช่น Student มี student_id, fname, lname
  • Relationship (ความสัมพันธ์) — การเชื่อมระหว่าง entity เช่น Student “ลงทะเบียน” Course

ความสัมพันธ์แบบ One-to-One, One-to-Many และ Many-to-Many#

ประเภทความหมายตัวอย่าง
One-to-One (1:1)1 record ของ A เชื่อมกับ 1 record ของ B เท่านั้นuseruser_profile
One-to-Many (1:N)1 record ของ A เชื่อมกับหลาย record ของ Bprovince → หลาย patient
Many-to-Many (M:N)หลาย record ของ A เชื่อมกับหลาย record ของ Bstudentcourse

ข้อสำคัญ: ความสัมพันธ์แบบ Many-to-Many ไม่สามารถเก็บตรง ๆ ใน relational database ได้ ต้องสร้าง junction table (ตารางกลาง) คั่น เช่นตาราง enrollment ระหว่าง student และ course

การสร้าง ER Diagram#

ER Diagram ใช้สัญลักษณ์มาตรฐาน เช่น สี่เหลี่ยมเป็น entity, วงรีเป็น attribute, ข้าวหลามตัดเป็น relationship ปัจจุบันมีเครื่องมือออกแบบ เช่น draw.io, dbdiagram.io และ MySQL Workbench

ER Diagram สัญลักษณ์

ภาพข้างต้นแสดงสัญลักษณ์มาตรฐานของ ER Diagram พร้อม legend อธิบายเครื่องหมายแต่ละแบบ ส่วนภาพด้านล่างคือตัวอย่าง ER Diagram ที่สร้างจริงด้วย dbdiagram.io:

ER Diagram from dbdiagram.io

ภาพ ER Diagram ข้างต้นสร้างด้วย dbdiagram.io ซึ่งใช้ภาษา DBML เขียนนิยามตารางและความสัมพันธ์ copy โค้ดด้านล่างไปวางในเว็บ dbdiagram.io ได้เลย:

Table student {
  student_id int [pk, increment]
  fname varchar(100)
  lname varchar(100)
}

Table course {
  course_id int [pk, increment]
  title varchar(100)
  credits int
}

Table enrollment {
  enrollment_id int [pk, increment]
  student_id int
  course_id int
  enroll_date date
}

Ref: enrollment.student_id > student.student_id
Ref: enrollment.course_id > course.course_id
sql
  • [pk, increment] — กำหนดเป็น Primary Key และเพิ่มค่าอัตโนมัติ (เทียบเท่า AUTO_INCREMENT ใน SQL)
  • บรรทัด Ref: ... > ... — กำหนด Foreign Key เชื่อมจาก enrollment ไปยัง student และ course (เครื่องหมาย > หมายถึงความสัมพันธ์แบบ many-to-one)

แปลง ER Diagram เป็นตารางในฐานข้อมูล#

เมื่อ ER Diagram พร้อม ก็แปลงเป็นตารางได้ทันที:

  • แต่ละ entity → หนึ่ง ตาราง
  • แต่ละ attribute → หนึ่ง คอลัมน์
  • ความสัมพันธ์ 1:N → เก็บ Foreign Key ของฝั่ง “1” ไว้ในตารางฝั่ง “N”
  • ความสัมพันธ์ M:N → สร้าง junction table ที่มี FK ของทั้งสองตาราง

ตัวอย่าง: ระบบลงทะเบียนวิชา เราสร้างฐานข้อมูลใหม่ชื่อ schooldb แล้วสร้างตารางทั้งสามตาราง แปลงเป็น SQL ได้ดังนี้:

-- 1. สร้างฐานข้อมูล schooldb แล้วเลือกใช้งาน
CREATE DATABASE IF NOT EXISTS schooldb;

-- เลือกฐานข้อมูล schooldb
-- (หากใช้ phpMyAdmin ให้คลิกเลือก schooldb จากแผงด้านซ้ายแทนการรัน USE)
USE schooldb;

-- 2. Entity: Student
CREATE TABLE student (
  student_id INT AUTO_INCREMENT PRIMARY KEY,
  fname VARCHAR(100),
  lname VARCHAR(100)
);

-- 3. Entity: Course
CREATE TABLE course (
  course_id INT AUTO_INCREMENT PRIMARY KEY,
  title VARCHAR(100),
  credits INT
);

-- 4. Junction table สำหรับความสัมพันธ์ M:N (Student ↔ Course)
CREATE TABLE enrollment (
  enrollment_id INT AUTO_INCREMENT PRIMARY KEY,
  student_id INT,
  course_id INT,
  enroll_date DATE,
  FOREIGN KEY (student_id) REFERENCES student(student_id),
  FOREIGN KEY (course_id) REFERENCES course(course_id)
);
sql

หลังสร้างตารางครบแล้ว phpMyAdmin สามารถ วาด ER Diagram ให้อัตโนมัติ จากตารางและ Foreign Key ที่เรากำหนดไว้ ขั้นตอนคือ:

  1. เลือกฐานข้อมูล schooldb ที่แผงด้านซ้าย
  2. คลิกแท็บ Designer ด้านบน
  3. phpMyAdmin จะแสดงตารางทั้งหมดพร้อมเส้นความสัมพันธ์ (Foreign Key) เชื่อมระหว่างตารางให้ทันที

phpMyAdmin Designer

ปัจจุบันยังต้องวาด ER Diagram ก่อนไหม?#

ในยุคที่เราสามารถใช้ AI (เช่น ChatGPT, Claude) ช่วยออกแบบโครงสร้างฐานข้อมูล หรือแม้กระทั่งให้สร้าง SQL CREATE TABLE ให้ได้ในไม่กี่วินาที คำถามที่ตามมาคือ “เรายังจำเป็นต้องวาด ER Diagram ก่อนไหม?”

คำตอบคือ “ไม่จำเป็นเสมอไป” แต่ ER Diagram ยังมีประโยชน์มาก ขึ้นอยู่กับขนาดและความซับซ้อนของระบบ:

  • ระบบเล็ก หรือ prototype — มักไม่จำเป็นต้องวาด ER Diagram อย่างเป็นทางการ สามารถอธิบายความต้องการให้ AI แล้วปรับโครงสร้างไปเรื่อย ๆ ได้ เพราะตารางน้อยและความสัมพันธ์ไม่ซับซ้อน
  • ระบบกลางถึงใหญ่ หรือทำงานเป็นทีม — ER Diagram ยังคงมีคุณค่ามาก เป็น “ภาษากลาง” ที่ทุกคนในทีมและ stakeholder มองเห็นโครงสร้างและความสัมพันธ์ได้ตรงกัน และยังเป็นแผนที่ดีให้ AI ทำงานตรงตามทิศทางที่เราต้องการ

กล่าวอีกแบบคือ AI ช่วยให้เราสร้างโครงสร้างได้เร็วขึ้น แต่ไม่ได้แทนความเข้าใจในการออกแบบที่ดี ER Diagram เปรียบเสมือนแผนผังบ้าน — ยิ่งบ้านใหญ่และซับซ้อน ยิ่งจำเป็นต้องมีแผนก่อนลงมือก่อสร้าง

สรุป: ER Diagram ช่วยให้เห็นภาพรวมของข้อมูลก่อนสร้างตาราง ทำให้ออกแบบได้ถูกต้องตั้งแต่ต้น entity กลายเป็นตาราง, attribute กลายเป็นคอลัมน์ และความสัมพันธ์กลายเป็น Foreign Key หรือ junction table


2. Primary Key และ Foreign Key#

Primary Key และ Foreign Key คือกลไกสำคัญที่ทำให้ตารางต่าง ๆ เชื่อมโยงกันได้อย่างถูกต้องและเชื่อถือได้ โดยเฉพาะในฐานข้อมูลแบบ Relational Database ที่ข้อมูลมักถูกแยกเก็บเป็นหลายตาราง

ในหัวข้อนี้เราจะใช้ฐานข้อมูล schooldb ที่สร้างไว้ในหัวข้อ 1 ซึ่งประกอบด้วย 3 ตารางหลัก ได้แก่

  • student เก็บข้อมูลนักเรียน
  • course เก็บข้อมูลรายวิชา
  • enrollment เก็บข้อมูลการลงทะเบียนเรียนของนักเรียนในแต่ละรายวิชา

ความสัมพันธ์ระหว่าง student และ course เป็นแบบ Many-to-Many เพราะนักเรียนหนึ่งคนสามารถลงทะเบียนได้หลายวิชา และหนึ่งวิชาก็มีนักเรียนลงทะเบียนได้หลายคน ดังนั้นจึงต้องมีตารางกลางชื่อ enrollment

แนวคิด Primary Key#

Primary Key (PK) คือคอลัมน์ หรือชุดคอลัมน์ ที่ใช้ระบุแต่ละ record ให้ไม่ซ้ำกัน และไม่เป็น NULL ตารางหนึ่งมี Primary Key ได้เพียงหนึ่งชุด

ตัวอย่างเช่น ตาราง student ใช้ student_id เป็น Primary Key เพื่อระบุนักเรียนแต่ละคน

CREATE TABLE student (
  student_id INT AUTO_INCREMENT PRIMARY KEY,
  fname VARCHAR(100),
  lname VARCHAR(100)
);
sql

ตาราง course ใช้ course_id เป็น Primary Key เพื่อระบุรายวิชาแต่ละวิชา

CREATE TABLE course (
  course_id INT AUTO_INCREMENT PRIMARY KEY,
  title VARCHAR(100),
  credits INT
);
sql

ส่วนตาราง enrollment ใช้ enrollment_id เป็น Primary Key (โครงสร้างเต็มพร้อม Foreign Key จะแสดงในหัวข้อถัดไป)

แนวคิด Foreign Key#

Foreign Key (FK) คือคอลัมน์ที่ใช้อ้างอิงไปยัง Primary Key ของอีกตารางหนึ่ง เพื่อสร้างความสัมพันธ์ระหว่างตาราง

ในตัวอย่างนี้ ตาราง enrollment มี Foreign Key 2 ตัว คือ

  • student_id อ้างอิงไปยัง student(student_id)
  • course_id อ้างอิงไปยัง course(course_id)
CREATE TABLE enrollment (
  enrollment_id INT AUTO_INCREMENT PRIMARY KEY,
  student_id INT,
  course_id INT,
  enroll_date DATE,
  FOREIGN KEY (student_id) REFERENCES student(student_id),
  FOREIGN KEY (course_id) REFERENCES course(course_id)
);
sql

ความหมายคือ การลงทะเบียนแต่ละรายการต้องอ้างอิงถึงนักเรียนที่มีอยู่จริงในตาราง student และรายวิชาที่มีอยู่จริงในตาราง course

Referential Integrity (ความสมบูรณ์ของการอ้างอิง)#

Referential Integrity คือกฎที่รับประกันว่าค่า Foreign Key ทุกค่าจะต้องอ้างอิงไปยังข้อมูลที่มีอยู่จริงในตารางหลัก

ตัวอย่างเช่น หากต้องการเพิ่มข้อมูลในตาราง enrollment โดยระบุ student_id = 999 แต่ไม่มีนักเรียนที่มี student_id = 999 อยู่ในตาราง student ระบบจะปฏิเสธคำสั่งนั้น

เช่นเดียวกัน หากระบุ course_id = 999 แต่ไม่มีรายวิชานั้นอยู่ในตาราง course ก็จะไม่สามารถเพิ่มข้อมูลได้

เตรียมฐานข้อมูลสำหรับการทดลอง#

ก่อนเริ่มตัวอย่างในบทนี้ ควรลบฐานข้อมูล schooldb เดิมออกก่อน เพื่อให้ทุกคนเริ่มต้นจากฐานข้อมูลเดียวกัน

ลบฐานข้อมูลเดิมใน phpMyAdmin#

  1. คลิกเลือกฐานข้อมูล schooldb จากแถบด้านซ้าย
  2. เลือกแท็บ Operations
  3. คลิก Drop the database (DROP)
  4. กด OK เพื่อยืนยันการลบ

phpMyAdmin Drop Database

⚠️ คำเตือน: การลบฐานข้อมูลจะลบตารางและข้อมูลทั้งหมดภายในฐานข้อมูลนั้นอย่างถาวร

สร้างฐานข้อมูลและตารางใหม่#

ให้รัน SQL ต่อไปนี้ในแท็บ SQL ของ phpMyAdmin หรือเครื่องมือจัดการฐานข้อมูลที่ใช้งานอยู่

-- 1. สร้างฐานข้อมูล schooldb แล้วเลือกใช้งาน
CREATE DATABASE IF NOT EXISTS schooldb;

-- เลือกฐานข้อมูล schooldb
USE schooldb;

-- 2. Entity: Student
CREATE TABLE student (
  student_id INT AUTO_INCREMENT PRIMARY KEY,
  fname VARCHAR(100),
  lname VARCHAR(100)
);

-- 3. Entity: Course
CREATE TABLE course (
  course_id INT AUTO_INCREMENT PRIMARY KEY,
  title VARCHAR(100),
  credits INT
);

-- 4. Junction table สำหรับความสัมพันธ์ M:N ระหว่าง Student และ Course
CREATE TABLE enrollment (
  enrollment_id INT AUTO_INCREMENT PRIMARY KEY,
  student_id INT,
  course_id INT,
  enroll_date DATE,
  FOREIGN KEY (student_id) REFERENCES student(student_id),
  FOREIGN KEY (course_id) REFERENCES course(course_id)
);
sql

เพิ่มข้อมูลตัวอย่าง#

หลังจากสร้างตารางแล้ว ให้เพิ่มข้อมูลตัวอย่างลงในตาราง student และ course ก่อน เพราะตาราง enrollment ต้องอ้างอิงข้อมูลจากสองตารางนี้

-- เพิ่มข้อมูลนักเรียน
INSERT INTO student (fname, lname) VALUES
('Somchai', 'Jaidee'),
('Suda', 'Sukjai'),
('Anan', 'Meesuk'),
('Malee', 'Thongdee'),
('Krit', 'Wongsa');

-- เพิ่มข้อมูลรายวิชา
INSERT INTO course (title, credits) VALUES
('Database Systems', 3),
('Web Programming', 3),
('Artificial Intelligence', 3),
('Cybersecurity Basics', 2);

-- เพิ่มข้อมูลการลงทะเบียนเรียน
INSERT INTO enrollment (student_id, course_id, enroll_date) VALUES
(1, 1, '2026-06-01'),
(1, 2, '2026-06-01'),
(2, 1, '2026-06-02'),
(2, 3, '2026-06-02'),
(3, 2, '2026-06-03'),
(4, 3, '2026-06-03'),
(5, 4, '2026-06-04');
sql

กำหนดความสัมพันธ์ระหว่างตาราง#

ตาราง student และ course มีความสัมพันธ์แบบ Many-to-Many

  • นักเรียนหนึ่งคนสามารถลงทะเบียนเรียนได้หลายวิชา
  • รายวิชาหนึ่งวิชาสามารถมีนักเรียนลงทะเบียนได้หลายคน

ตาราง enrollment จึงทำหน้าที่เป็น Junction Table หรือตารางกลาง เพื่อเก็บความสัมพันธ์ระหว่างนักเรียนและรายวิชา

โครงสร้างความสัมพันธ์สามารถอธิบายได้ดังนี้

ER Diagram ระบบโรงเรียน

กล่าวคือ

  • student.student_id เชื่อมกับ enrollment.student_id
  • course.course_id เชื่อมกับ enrollment.course_id

ตัวอย่าง SQL สำหรับดูข้อมูลที่เชื่อมกัน#

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

SELECT
  student.student_id,
  student.fname,
  student.lname,
  course.title,
  course.credits,
  enrollment.enroll_date
FROM enrollment
JOIN student ON enrollment.student_id = student.student_id
JOIN course ON enrollment.course_id = course.course_id;
sql

ผลลัพธ์ที่ได้จะเป็นข้อมูลรวมจากทั้ง 3 ตาราง ดังนี้

student_idfnamelnametitlecreditsenroll_date
1SomchaiJaideeDatabase Systems32026-06-01
1SomchaiJaideeWeb Programming32026-06-01
2SudaSukjaiDatabase Systems32026-06-02
2SudaSukjaiArtificial Intelligence32026-06-02
3AnanMeesukWeb Programming32026-06-03
4MaleeThongdeeArtificial Intelligence32026-06-03
5KritWongsaCybersecurity Basics22026-06-04

ตัวอย่าง Key Constraint#

ลองรันคำสั่งต่อไปนี้ในแท็บ SQL ของ phpMyAdmin เพื่อดูว่า Primary Key และ Foreign Key ทำงานอย่างไร

-- ถูก: เพิ่มการลงทะเบียนให้ student_id และ course_id ที่มีอยู่จริง
INSERT INTO enrollment (student_id, course_id, enroll_date)
VALUES (1, 3, '2026-06-05');

-- ผิด: เพิ่มการลงทะเบียนให้ student_id ที่ไม่มีอยู่จริง
-- จะถูกปฏิเสธ เพราะไม่มี student_id = 999 ในตาราง student
INSERT INTO enrollment (student_id, course_id, enroll_date)
VALUES (999, 1, '2026-06-05');

-- ผิด: เพิ่มการลงทะเบียนให้ course_id ที่ไม่มีอยู่จริง
-- จะถูกปฏิเสธ เพราะไม่มี course_id = 999 ในตาราง course
INSERT INTO enrollment (student_id, course_id, enroll_date)
VALUES (1, 999, '2026-06-05');
sql

ตัวอย่างการลบข้อมูลที่ถูกอ้างอิง#

ถ้ามีนักเรียนคนหนึ่งถูกใช้อ้างอิงอยู่ในตาราง enrollment แล้วพยายามลบข้อมูลนักเรียนคนนั้น ระบบจะปฏิเสธ เพราะอาจทำให้ข้อมูลการลงทะเบียนเสียความถูกต้อง

-- ผิด: ลบนักเรียนที่มีข้อมูลอยู่ใน enrollment
-- จะถูกปฏิเสธ เพราะ student_id = 1 ถูกใช้อ้างอิงอยู่
DELETE FROM student
WHERE student_id = 1;
sql

หากต้องการลบจริง ต้องลบข้อมูลในตาราง enrollment ที่เกี่ยวข้องก่อน แล้วจึงลบข้อมูลในตาราง student

-- ลบข้อมูลการลงทะเบียนของ student_id = 1 ก่อน
DELETE FROM enrollment
WHERE student_id = 1;

-- จากนั้นจึงลบข้อมูลนักเรียนได้
DELETE FROM student
WHERE student_id = 1;
sql

ON DELETE และ ON UPDATE#

เราสามารถควบคุมพฤติกรรมเมื่อมีการลบหรือแก้ไขข้อมูลที่ถูกอ้างอิงได้ด้วย ON DELETE และ ON UPDATE

ตัวอย่างเช่น

FOREIGN KEY (student_id) REFERENCES student(student_id)
  ON DELETE RESTRICT
  ON UPDATE CASCADE
sql
Actionพฤติกรรม
RESTRICT / NO ACTIONปฏิเสธการลบหรือแก้ไข หากยังมี record อื่นอ้างอิงอยู่
CASCADEลบหรือแก้ไขตามไปยัง record ที่อ้างอิงด้วย
SET NULLตั้งค่า Foreign Key เป็น NULL ถ้าคอลัมน์นั้นอนุญาตให้เป็น NULL

โดยทั่วไป ในกรณีข้อมูลการลงทะเบียนเรียน อาจเลือกใช้ RESTRICT เพื่อป้องกันการลบนักเรียนหรือรายวิชาโดยไม่ตั้งใจ หากยังมีข้อมูลการลงทะเบียนเชื่อมโยงอยู่

สรุป: Primary Key ระบุ record ให้ไม่ซ้ำ (เช่น student_id) ส่วน Foreign Key เชื่อมตารางเข้าด้วยกัน ตาราง enrollment ทำให้ความสัมพันธ์แบบ Many-to-Many เป็นไปได้ และ Referential Integrity ป้องกันการอ้างอิงข้อมูลที่ไม่มีอยู่จริง


3. การทำ Normalization (1NF–3NF)#

Normalization (การทำให้เป็นมาตรฐาน) คือกระบวนการจัดระเบียบตารางเพื่อ ลดข้อมูลซ้ำซ้อน และ กำจัดความผิดปกติของข้อมูล (anomalies) ในหัวข้อนี้เราจะใช้ตัวอย่างระบบโรงเรียน (schooldb) จากหัวข้อ 2 — เริ่มจากตารางเดียวที่ “ยังไม่ผ่านการทำ Normalization” แล้วค่อย ๆ แยกจนกลายเป็นตาราง student, course และ enrollment ที่คุ้นเคย

จุดเริ่มต้น: ตารางเดียวที่เก็บทุกอย่าง#

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

student_idfnamelnamecourses
1SomchaiJaideeDatabase Systems, Web Programming
2SudaSukjaiDatabase Systems, Artificial Intelligence
3AnanMeesukWeb Programming

การเก็บหลายค่าในช่องเดียวทำให้ค้นหา อัปเดต หรือ join ได้ยาก — นี่คือสิ่งที่ Normalization จะมาแก้

ข้อมูลซ้ำซ้อนและ Anomalies#

ตารางที่ออกแบบไม่ดีจะเกิด ข้อมูลซ้ำซ้อน (redundancy) และ 3 ปัญหา (anomalies):

  • Update Anomaly — เปลี่ยนชื่อวิชาต้องแก้หลายแถว ถ้าแก้ไม่ครบข้อมูลจะขัดแย้งกัน
  • Insert Anomaly — เพิ่มวิชาใหม่ที่ยังไม่มีใครลงทะเบียนไม่ได้ (ยังไม่มีนักเรียน → ไม่มีแถวให้ใส่)
  • Delete Anomaly — ลบการลงทะเบียนคนสุดท้ายของวิชา ทำให้ข้อมูลวิชานั้นหายไปด้วย

First Normal Form (1NF) — รูปแบบมาตรฐานที่ 1#

1NF กำหนดให้ทุกคอลัมน์เก็บค่า เดี่ยว (atomic) — ไม่เก็บหลายค่าในช่องเดียว วิธีแก้คือแยกแต่ละวิชาออกเป็นแถวของตัวเอง และดึงรายละเอียดของวิชามาเป็นคอลัมน์:

student_idfnamelnamecourse_idtitlecreditsenroll_date
1SomchaiJaidee1Database Systems32026-06-01
1SomchaiJaidee2Web Programming32026-06-01
2SudaSukjai1Database Systems32026-06-02
2SudaSukjai3Artificial Intelligence32026-06-02
3AnanMeesuk2Web Programming32026-06-03

ตอนนี้ผ่าน 1NF แล้ว แต่ยังมีข้อมูลซ้ำซ้อนชัดเจน — ชื่อ “Somchai/Jaidee” ซ้ำ 2 แถว และชื่อวิชา “Database Systems/3cr” ก็ซ้ำ 2 แถว

Second Normal Form (2NF) — รูปแบบมาตรฐานที่ 2#

2NF ต้องผ่าน 1NF และ ไม่มี partial dependency — ทุกคอลัมน์ต้องขึ้นกับ PK ทั้งหมด ไม่ใช่แค่บางส่วน

ในตารางนี้ PK คือ (student_id, course_id) แต่:

  • fname, lname ขึ้นกับ แค่ student_id (บางส่วนของ PK)
  • title, credits ขึ้นกับ แค่ course_id (บางส่วนของ PK)
  • enroll_date ขึ้นกับ ทั้งคู่ (เป็นข้อมูลของการลงทะเบียนนั้น ๆ)

วิธีแก้คือแยกออกเป็น 3 ตาราง:

  • studentstudent_idfname, lname
  • coursecourse_idtitle, credits
  • enrollmentstudent_id, course_idenroll_date

Third Normal Form (3NF) — รูปแบบมาตรฐานที่ 3#

3NF ต้องผ่าน 2NF และ ไม่มี transitive dependency — คอลัมน์ที่ไม่ใช่ key ต้องไม่ขึ้นกับคอลัมน์ที่ไม่ใช่ key อีกตัว

ตัวอย่าง transitive dependency: เพื่อให้เห็นภาพ ลองสมมติว่าตาราง student ของเรายังเก็บข้อมูลอาจารย์ที่ปรึกษา (advisor) ไว้ในตารางเดียวกันด้วย

student_idfnamelnameadvisor_idadvisor_nameadvisor_dept
1SomchaiJaidee101Dr. SmithComputer Science
2SudaSukjai101Dr. SmithComputer Science
3AnanMeesuk102Dr. LeeMathematics

PK คือ student_id แต่ advisor_name และ advisor_dept ไม่ได้ขึ้นกับ student_id โดยตรง — มันขึ้นกับ advisor_id อีกที:

student_idadvisor_idadvisor_name, advisor_dept

นี่คือ transitive dependency (ความสัมพันธ์แบบทอดผ่าน) ซึ่งก่อปัญหาเดียวกัน: ถ้า “Dr. Smith” ย้ายภาควิชา ต้องแก้ข้อมูลหลายแถว หรือถ้าอาจารย์เปลี่ยนชื่อก็ต้องแก้ซ้ำในทุกแถวของนักเรียนที่เขาดูแล

วิธีแก้ (3NF): แยกข้อมูลอาจารย์ออกเป็นตาราง advisor ของมันเอง แล้วให้ student อ้างอิงด้วย advisor_id

CREATE TABLE advisor (
  advisor_id INT PRIMARY KEY,
  advisor_name VARCHAR(100),
  advisor_dept VARCHAR(100)
);

CREATE TABLE student (
  student_id INT AUTO_INCREMENT PRIMARY KEY,
  fname VARCHAR(100),
  lname VARCHAR(100),
  advisor_id INT,
  FOREIGN KEY (advisor_id) REFERENCES advisor(advisor_id)
);
sql

ตอนนี้ทุกคอลัมน์ในแต่ละตารางขึ้นกับ PK โดยตรง ไม่มีทอดผ่าน — ผ่าน 3NF

กลับมาที่ตัวอย่างของเรา: หลังแยกในขั้น 2NF ตาราง student, course, enrollment ทั้งสามก็ผ่าน 3NF แล้ว (ไม่มี dependency แบบทอดผ่าน) และผลลัพธ์ที่ได้คือโครงสร้างเดียวกับที่เราสร้างในหัวข้อ 2:

-- student: ข้อมูลนักเรียน ขึ้นกับ student_id ทั้งหมด
CREATE TABLE student (
  student_id INT AUTO_INCREMENT PRIMARY KEY,
  fname VARCHAR(100),
  lname VARCHAR(100)
);

-- course: ข้อมูลวิชา ขึ้นกับ course_id ทั้งหมด
CREATE TABLE course (
  course_id INT AUTO_INCREMENT PRIMARY KEY,
  title VARCHAR(100),
  credits INT
);

-- enrollment: เก็บความสัมพันธ์ M:N + รายละเอียดการลงทะเบียน
CREATE TABLE enrollment (
  enrollment_id INT AUTO_INCREMENT PRIMARY KEY,
  student_id INT,
  course_id INT,
  enroll_date DATE,
  FOREIGN KEY (student_id) REFERENCES student(student_id),
  FOREIGN KEY (course_id) REFERENCES course(course_id)
);
sql

หมายเหตุ: ในทางทฤษฎี PK ของ enrollment คือ (student_id, course_id) แต่ในทางปฏิบัติมักใช้ surrogate key (enrollment_id แบบ AUTO_INCREMENT) เพื่อให้อ้างอิงแถวง่ายขึ้น เหมือนที่ทำในหัวข้อ 2

ข้อดีของฐานข้อมูลที่ผ่านการทำ Normalization#

  • ลดข้อมูลซ้ำซ้อน → ประหยัดพื้นที่
  • กำจัด anomalies → ข้อมูลถูกต้อง สอดคล้องกัน
  • แก้ไขที่เดียว → สะท้อนทุกที่ (เช่น เปลี่ยนชื่อวิชาใน course ที่เดียว)
  • ดูแลรักษาง่าย และขยายระบบได้สะดวก

สรุป: Normalization พาตารางเดียวที่รวมทุกอย่าง ไปสู่หลายตารางที่ “แต่ละตารางเก็บแค่สิ่งเดียว” ผ่าน 1NF → 2NF → 3NF — ผลลัพธ์คือโครงสร้าง student/course/enrollment ที่เราใช้ในหัวข้อ 2

Denormalize คือ? บางครั้งในงานจริงเราอาจกลับมาเก็บข้อมูลบางส่วนซ้ำกัน (denormalize) เช่น เก็บชื่อวิชาไว้ใน enrollment ด้วย เพื่อลดการ JOIN หลายตารางตอนอ่าน ทำให้ query เร็วขึ้น แต่ต้องแลกกับการดูแลให้ข้อมูลที่ซ้ำกันยังตรงกันเสมอ


4. Views (มุมมอง)#

แนะนำ View#

View คือตารางเสมือน (virtual table) ที่สร้างจากผลลัพธ์ของ query ไม่ได้เก็บข้อมูลจริง (ยกเว้น materialized view) แต่เก็บเพียงนิยามของ query ในหัวข้อนี้เราจะใช้ตาราง student/course/enrollment จากหัวข้อ 2

การสร้างและจัดการ Views#

จำ query JOIN ยาว ๆ ที่เชื่อม 3 ตารางในหัวข้อ 2 ได้ไหม? เราสามารถห่อมันไว้ใน view เพื่อเรียกใช้ซ้ำได้:

-- สร้าง view ที่รวมข้อมูลนักเรียนกับรายวิชาที่ลงทะเบียน
CREATE VIEW student_courses AS
SELECT
  s.student_id,
  s.fname,
  s.lname,
  c.title AS course_title,
  c.credits,
  e.enroll_date
FROM enrollment e
JOIN student s ON e.student_id = s.student_id
JOIN course c ON e.course_id = c.course_id;

-- ใช้งานเหมือนตารางปกติ — สั้นกว่าเขียน JOIN ใหม่ทุกครั้ง
SELECT * FROM student_courses;

-- กรองเฉพาะนักเรียนที่ลงวิชา Database Systems
SELECT * FROM student_courses WHERE course_title = 'Database Systems';

-- ลบ view
DROP VIEW student_courses;
sql

ผลลัพธ์ของ SELECT * FROM student_courses; จะเหมือนกับ query JOIN ในหัวข้อ 2 เพราะ view เป็นเพียงการ “บันทึก query ไว้” แล้วเรียกใช้ผ่านชื่อสั้น ๆ ใน phpMyAdmin จะเห็น view student_courses อยู่ในแผงซ้าย (มีไอคอนตารางเสมือน) และสามารถคลิกเข้าไปดูข้อมูลได้เหมือนตารางทั่วไป

phpMyAdmin view

ช่วยลดความซับซ้อนของ Query#

แทนที่จะเขียน JOIN ยาว ๆ ซ้ำทุกครั้ง เราห่อไว้ใน view แล้วเรียก SELECT * FROM student_courses สั้น ๆ ได้ — ทั้งในแอปพลิเคชัน, report หรือเครื่องมือวิเคราะห์ข้อมูล

การทำ Data Abstraction#

View ซ่อนความซับซ้อนของโครงสร้างตารางจริง ผู้ใช้ไม่ต้องรู้ว่าข้อมูลกระจายอยู่ในกี่ตาราง เหมาะกับการทำ “interface” ให้กับผู้ใช้หรือแอปพลิเคชัน

ใช้ Views เพื่อความปลอดภัย#

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

สรุป: View ช่วยลดความซับซ้อนของ query ทำ abstraction และเพิ่มความปลอดภัยโดยควบคุมว่าผู้ใช้เห็นข้อมูลใดได้บ้าง


6. Stored Procedures (โพรซีเจอร์จัดเก็บ)#

แนะนำ Stored Procedure#

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

ประโยชน์ของ Stored Procedures#

  • ลดการส่งข้อมูลระหว่างแอปและฐานข้อมูล — แทนที่จะส่งคำสั่ง SQL หลายบรรทัด แอปพลิเคชันเพียงแค่เรียกชื่อ Stored Procedure
  • ความปลอดภัยที่ดีกว่า — ผู้ใช้สามารถเรียก Stored Procedure ได้โดยไม่ต้องมีสิทธิ์เข้าถึงตารางโดยตรง และสามารถซ่อนตรรกะธุรกิจที่ซับซ้อนไว้ได้
  • บำรุงรักษาง่าย — แก้ไขตรรกะธุรกิจได้ที่ฐานข้อมูลโดยตรง ไม่ต้องแก้โค้ดแอปพลิเคชันและ redeploy
  • ประสิทธิภาพที่ดีกว่า — ฐานข้อมูลคอมไพล์และ cache execution plan ไว้ ทำให้รันเร็วกว่าการส่ง SQL ทีละคำสั่ง
  • ลดความซับซ้อนในแอปพลิเคชัน — แอปพลิเคชันเพียงเรียก CALL procedure_name(...) ไม่ต้องเขียน query ยาว ๆ

การสร้าง Stored Procedure#

ใน MySQL/MariaDB การสร้าง Stored Procedure ต้องเปลี่ยน delimiter ชั่วคราวเพื่อให้ฐานข้อมูลเข้าใจว่าจบคำสั่ง CREATE PROCEDURE ตรงไหน เพราะภายใน Stored Procedure มีเครื่องหมาย semicolon (;) อยู่

ตัวอย่างการสร้าง Stored Procedure ชื่อ AddStudent ที่รับพารามิเตอร์ fname และ lname แล้วเพิ่มนักเรียนใหม่:

DELIMITER $$

CREATE PROCEDURE AddStudent(
    IN p_fname VARCHAR(100),
    IN p_lname VARCHAR(100)
)
BEGIN
    INSERT INTO student (fname, lname)
    VALUES (p_fname, p_lname);
END $$

DELIMITER ;
sql

อธิบายแต่ละส่วน:

  • DELIMITER $$ — เปลี่ยนตัวคั่นคำสั่งชั่วคราวจาก ; เป็น $$ เพื่อให้เขียน SQL หลายบรรทัดภายใน Stored Procedure ได้
  • CREATE PROCEDURE AddStudent(...) — สร้าง Stored Procedure ชื่อ AddStudent พร้อมรับพารามิเตอร์ 2 ตัว
  • IN p_fname VARCHAR(100) — พารามิเตอร์ input ชื่อ p_fname ชนิด VARCHAR(100)
  • BEGIN ... END — บล็อกที่รวมคำสั่ง SQL ทั้งหมดของ Stored Procedure
  • INSERT INTO student ... — คำสั่ง SQL ที่เพิ่มข้อมูลนักเรียนใหม่
  • END $$ — จบบล็อก Stored Procedure (ใช้ $$ แทน ;)
  • DELIMITER ; — เปลี่ยน delimiter กลับเป็น ; เหมือนเดิม

หลังจากสร้าง Stored Procedure แล้ว สามารถดูรายการ Stored Procedure ทั้งหมดใน phpMyAdmin ได้โดย:

  1. เลือกฐานข้อมูล schooldb จากแผงด้านซ้าย
  2. คลิกเมนู Procedures ด้านบน
  3. phpMyAdmin จะแสดง Stored Procedure ทั้งหมดที่สร้างไว้ในฐานข้อมูล schooldb

phpMyAdmin Procedures

การเรียกใช้ Stored Procedure#

หลังจากสร้างแล้ว สามารถเรียกใช้ Stored Procedure ได้ด้วยคำสั่ง CALL:

-- เรียกใช้ Stored Procedure เพื่อเพิ่มนักเรียนใหม่
CALL AddStudent('Nopphadol', 'Sriwilai');

-- ตรวจสอบผลลัพธ์
SELECT * FROM student;
sql

ผลลัพธ์จะแสดงนักเรียนคนใหม่ที่เพิ่มเข้าไป:

student_idfnamelname
1SomchaiJaidee
2SudaSukjai
3AnanMeesuk
4MaleeThongdee
5KritWongsa
6NopphadolSriwilai

Stored Procedure ที่ซับซ้อนขึ้น#

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

DELIMITER $$

CREATE PROCEDURE EnrollStudent(
    IN p_student_id INT,
    IN p_course_id INT
)
BEGIN
    -- ตัวแปรสำหรับเก็บจำนวนนักเรียน
    DECLARE student_count INT;
    DECLARE course_count INT;

    -- ตรวจสอบว่านักเรียนมีอยู่จริงหรือไม่
    SELECT COUNT(*) INTO student_count
    FROM student
    WHERE student_id = p_student_id;

    -- ตรวจสอบว่าวิชามีอยู่จริงหรือไม่
    SELECT COUNT(*) INTO course_count
    FROM course
    WHERE course_id = p_course_id;

    -- ถ้านักเรียนและวิชามีอยู่จริง ให้เพิ่มการลงทะเบียน
    IF student_count > 0 AND course_count > 0 THEN
        INSERT INTO enrollment (student_id, course_id, enroll_date)
        VALUES (p_student_id, p_course_id, CURDATE());
        SELECT 'Enrollment successful' AS result;
    ELSE
        SELECT 'Student or course not found' AS result;
    END IF;

END $$

DELIMITER ;
sql

เรียกใช้ Stored Procedure นี้:

-- ลงทะเบียนนักเรียน ID 1 เรียนวิชา ID 3 (Artificial Intelligence)
CALL EnrollStudent(1, 3);

-- ตรวจสอบผลลัพธ์
SELECT * FROM enrollment;
sql

การดูและลบ Stored Procedure#

-- ดู Stored Procedure ทั้งหมดในฐานข้อมูล
SHOW PROCEDURE STATUS WHERE Db = 'schooldb';

-- ดูโค้ดของ Stored Procedure ชื่อ AddStudent
SHOW CREATE PROCEDURE AddStudent;

-- ลบ Stored Procedure
DROP PROCEDURE IF EXISTS AddStudent;
sql

Stored Procedure พร้อมพารามิเตอร์หลายแบบ#

Stored Procedure สามารถมีทั้งพารามิเตอร์ input (IN), output (OUT), และ input/output (INOUT) ตัวอย่าง Stored Procedure ที่คำนวณค่าเฉลี่ยและคืนค่าผ่านพารามิเตอร์ output:

DELIMITER $$

CREATE PROCEDURE GetStudentCount(
    OUT p_total INT
)
BEGIN
    SELECT COUNT(*) INTO p_total
    FROM student;
END $$

DELIMITER ;

-- เรียกใช้พร้อมรับค่า output
CALL GetStudentCount(@total);
SELECT @total AS total_students;
sql

Stored Procedure สำหรับ UPDATE#

ตัวอย่าง Stored Procedure ที่แก้ไขข้อมูล:

DELIMITER $$

CREATE PROCEDURE UpdateStudentName(
    IN p_student_id INT,
    IN p_new_fname VARCHAR(100),
    IN p_new_lname VARCHAR(100)
)
BEGIN
    UPDATE student
    SET fname = p_new_fname,
        lname = p_new_lname
    WHERE student_id = p_student_id;

    SELECT ROW_COUNT() AS affected_rows;
END $$

DELIMITER ;

-- เรียกใช้
CALL UpdateStudentName(6, 'Nopphadol', 'Update');
sql

สรุป: Stored Procedure คือชุดคำสั่ง SQL ที่จัดเก็บไว้ในฐานข้อมูล ช่วยลดความซับซ้อนในแอปพลิเคชัน เพิ่มประสิทธิภาพ และเพิ่มความปลอดภัยโดยซ่อนตรรกะธุรกิจและควบคุมสิทธิ์การเข้าถึงข้อมูล สามารถเรียกใช้ซ้ำได้และบำรุงรักษาได้ง่ายกว่าการเขียน SQL ฝังในโค้ดแอปพลิเคชัน


7. Index (ดัชนี)#

ทำไม Index จึงสำคัญ#

Index เปรียบเสมือนดัชนีท้ายหนังสือ ช่วยให้ฐานข้อมูลค้นหา record เร็วขึ้นอย่างมาก โดยไม่ต้องอ่านตารางทั้งตาราง ในหัวข้อนี้เราจะใช้ตาราง student/course/enrollment จากหัวข้อ 2

  • Full Table Scan — ไล่อ่านทุกแถว ช้าเมื่อข้อมูลเยอะ
  • Indexed Search — ใช้ index กระโดดไปยังแถวที่ต้องการโดยตรง เร็วกว่ามาก

ใช้คำสั่ง EXPLAIN เพื่อดูว่า query ใช้ index หรือทำ full table scan:

EXPLAIN SELECT * FROM student WHERE lname = 'Jaidee';
sql

ผลลัพธ์ (ก่อนสร้าง index):

idselect_typetabletypepossible_keyskeyrowsfilteredExtra
1SIMPLEstudentALLNULLNULL520.00Using where

จุดสำคัญคือคอลัมน์ type = ALL และ key = NULL — แปลว่า MySQL ยังไม่มี index ให้ใช้ จึงต้อง อ่านทุกแถว (Full Table Scan) แล้วกรองด้วย WHERE (เห็นได้จาก Extra: Using where) ในที่นี้มีแค่ 5 แถวเลยไม่รู้สึกช้า แต่ถ้าตารางมี 100,000 แถว การไล่อ่านทุกแถวทุกครั้งจะช้ามาก

หลังสร้าง index (ในหัวข้อย่อยถัดไป) แล้วลองรัน EXPLAIN อีกครั้ง จะเห็น type เปลี่ยนจาก ALL เป็น ref และ key เป็นชื่อ index — แปลว่าเปลี่ยนไปใช้ Indexed Search แล้ว

Single-Column Index (ดัชนีคอลัมน์เดียว)#

-- สร้าง index บนคอลัมน์ lname ของตาราง student (ค้นนักเรียนตามนามสกุล)
CREATE INDEX idx_student_lname ON student(lname);
sql

หลังสร้าง index แล้วลองรัน EXPLAIN อีกครั้ง จะได้ผลลัพธ์ใหม่:

idselect_typetabletypepossible_keyskeyrowsfilteredExtra
1SIMPLEstudentrefidx_student_lnameidx_student_lname1100.00NULL

เปรียบเทียบก่อน/หลังสร้าง index:

คอลัมน์ก่อน (ไม่มี index)หลัง (มี index)
typeALL (อ่านทุกแถว)ref (ค้นผ่าน index) ✅
keyNULL (ไม่ใช้ index)idx_student_lname
rows5 (ไล่อ่านทุกแถว)1 (กระโดดไปแถวที่ตรง) ✅

คราวนี้ MySQL ใช้ index ค้นหา (key: idx_student_lname) และกระโดดไปยังแถวที่ตรงโดยตรง (rows: 1) แทนที่จะไล่อ่านทุกแถว — นี่คือ Indexed Search

Composite Index (ดัชนีหลายคอลัมน์)#

Index บนหลายคอลัมน์พร้อมกัน เหมาะกับการ query ที่ใช้คอลัมน์เหล่านั้นร่วมกัน

-- index บน student_id และ enroll_date ของตาราง enrollment
CREATE INDEX idx_enrollment_student_date ON enrollment(student_id, enroll_date);
sql

ลำดับสำคัญ: composite index (student_id, enroll_date) ใช้ได้ดีกับ WHERE student_id = ... และ WHERE student_id = ... AND enroll_date > ... แต่ไม่ช่วยกับ WHERE enroll_date = ... เพียงอย่างเดียว

ข้อดีและข้อแลกเปลี่ยนของ Index#

  • ข้อดี — query WHERE, JOIN, ORDER BY เร็วขึ้นมาก
  • ข้อเสีย — ทำให้ INSERT/UPDATE/DELETE ช้าลงเล็กน้อย (ต้องอัปเดต index ด้วย) และกินพื้นที่เก็บข้อมูลเพิ่ม

สรุป: สร้าง index กับคอลัมน์ที่ใช้ค้นหา/เชื่อม/เรียงบ่อย แต่อย่าสร้างมากเกินไปเพราะกระทบการเขียนข้อมูล


8. Access Control และความปลอดภัย#

ผู้ใช้และ Role ในฐานข้อมูล#

DBMS แยกผู้ใช้ (user) แต่ละคนด้วยบัญชีของตัวเอง และมักจัดกลุ่มเป็น role เพื่อมอบสิทธิ์เป็นกลุ่น ๆ แทนที่ทีละคน ในระบบ schooldb เราอาจมี user เช่น teacher และ staff ที่ทำงานต่างบทบาทกัน

-- สร้าง user
CREATE USER 'teacher'@'localhost' IDENTIFIED BY 'secure_password';
sql

Authentication และ Authorization#

  • Authentication (การตรวจสอบตัวตน) — ยืนยันว่าผู้ใช้คือใคร (เช่น ชื่อผู้ใช้ + รหัสผ่าน)
  • Authorization (การอนุญาต) — กำหนดว่าผู้ใช้คนนั้นทำอะไรได้บ้าง

สิทธิ์และ Permission (Privileges)#

มอบสิทธิ์ด้วย GRANT และเพิกถอนด้วย REVOKE:

-- ให้ staff อ่านตาราง student และ course ได้อย่างเดียว
GRANT SELECT ON schooldb.student TO 'staff'@'localhost';
GRANT SELECT ON schooldb.course TO 'staff'@'localhost';

-- ให้ teacher อ่านและแก้ไขการลงทะเบียน (enrollment) ได้
GRANT SELECT, UPDATE ON schooldb.enrollment TO 'teacher'@'localhost';

-- เพิกถอนสิทธิ์
REVOKE UPDATE ON schooldb.enrollment FROM 'teacher'@'localhost';

-- ดูสิทธิ์ของ user
SHOW GRANTS FOR 'teacher'@'localhost';
sql

สิทธิ์ที่กำหนดได้ เช่น SELECT, INSERT, UPDATE, DELETE, CREATE, DROP และระดับการมอบ (*.* ทุกฐานข้อมูล, schooldb.enrollment เฉพาะตาราง)

หลักสิทธิ์น้อยที่สุด (Principle of Least Privilege)#

หลักสิทธิ์น้อยที่สุด — ให้ผู้ใช้มีสิทธิ์เท่าที่จำเป็นต่อการทำงาน ไม่ใช่มากกว่า เช่น staff ที่ดูข้อมูลนักเรียนได้อย่างเดียว ไม่ควรมีสิทธิ์ DELETE หรือ UPDATE

การจัดการข้อมูลอ่อนไหว#

จัดการข้อมูลอ่อนไหว (เช่น ข้อมูลส่วนตัวนักเรียน) ด้วยหลายชั้น: สิทธิ์ระดับตาราง/คอลัมน์, view ที่กรองข้อมูล (ดูหัวข้อ 4), การเข้ารหัส (encryption) และการบันทึกการเข้าถึง (auditing)

Access Control#

DBMS จัดการสิทธิ์ (permission) และระดับการเข้าถึงของผู้ใช้แต่ละคน ทำให้ผู้ใช้คนละคนเข้าถึงฐานข้อมูล schooldb เดียวกันในระดับที่ต่างกันได้ ตัวอย่างเช่น teacher สามารถแก้ไขข้อมูลการลงทะเบียนได้ ในขณะที่ staff สามารถ “ดู” ข้อมูลนักเรียนและรายวิชาได้อย่างเดียว ซึ่งช่วยปกป้องข้อมูลจากการเปลี่ยนแปลงโดยไม่ได้รับอนุญาต

สรุป: Access Control คุมว่าใครเข้าถึงอะไรได้บ้าง ใช้ user/role + GRANT/REVOKE + หลักสิทธิ์น้อยที่สุด เพื่อให้ข้อมูลปลอดภัยและผู้ใช้ทำงานได้ตามบทบาทของตน


9. Backup & Recovery (สำรองและกู้คืนข้อมูล)#

ความสำคัญของการสำรองข้อมูล#

ข้อมูลมีค่าและเปราะบาง — ฮาร์ดแวร์เสีย โปรแกรมบั๊ก การลบโดยไม่ตั้งใจ หรือการโจมตี ล้วนทำให้ข้อมูลสูญหายได้ การสำรองข้อมูล (backup) คือเครือข่ายนิรภัยสุดท้าย

กลยุทธ์การสำรองข้อมูล#

วางกลยุทธ์สำรองข้อมูลให้เหมาะกับความสำคัญของข้อมูล: สำรองบ่อยแค่ไหน เก็บกี่ชุด เก็บที่ไหน (on-site + off-site/cloud) และทดสอบกู้คืนเป็นระยะ

Full Backup และ Incremental Backup#

  • Full Backup — สำรองทุกอย่างในครั้งเดียว ง่ายแต่ช้าและกินพื้นที่
  • Incremental Backup — สำรองเฉพาะส่วนที่ “เปลี่ยนแปลง” ตั้งแต่ครั้งล่าสุด เร็วและประหยัดพื้นที่ แต่กู้คืนซับซ้อนกว่า

เทคนิคการกู้คืนฐานข้อมูล#

Export/Import ผ่าน phpMyAdmin เป็นวิธีง่าย ๆ: คลิกแท็บ Export เพื่อบันทึกฐานข้อมูลเป็นไฟล์ .sql (สำรองข้อมูล) และคลิกแท็บ Import เพื่อนำไฟล์ .sql กลับเข้ามา (กู้คืน)

phpMyAdmin Export

หรือใช้คำสั่ง mysqldump ใน command line โดย mysql/mysqldump อยู่ในโฟลเดอร์ runtime ของ CAMPP ขั้นตอนคือ:

  1. เปิด Command Prompt (Windows) หรือ Terminal (macOS/Linux)
  2. cd เข้าไปยังโฟลเดอร์ bin ที่เก็บ mysql/mysqldump (อยู่ใต้ runtime ตามเวอร์ชันที่ติดตั้ง) เช่น บน Windows:
cd \
cd CAMPP\runtime\mysql-8.4.0-winx64\bin
bash

ชื่อโฟลเดอร์เวอร์ชัน (เช่น mysql-8.4.0-winx64) อาจต่างจากนี้ตามเวอร์ชันที่ติดตั้ง — ให้เปิดโฟลเดอร์ C:\CAMPP\runtime แล้วเข้าไปในโฟลเดอร์ bin ของ MySQL บน macOS/Linux ก็คล้ายกัน เช่น cd /Applications/CAMPP/runtime/mysql-<version>/bin

  1. รันคำสั่งสำรอง/กู้คืนจากโฟลเดอร์นี้ (สำรองฐานข้อมูล schooldb ที่เราสร้างในหัวข้อ 2):
# สำรองข้อมูล (full backup) เป็นไฟล์ .sql (เก็บไว้ที่ C:\CAMPP)
mysqldump -u root -p -P 3307 schooldb > C:\CAMPP\schooldb_backup.sql

# ลบฐานข้อมูลเดิมก่อน แล้วสร้างใหม่
mysql -u root -p -P 3307 -e "DROP DATABASE IF EXISTS schooldb;"
mysql -u root -p -P 3307 -e "CREATE DATABASE schooldb;"

# กู้คืน — นำข้อมูลกลับเข้ามา
mysql -u root -p -P 3307 schooldb < C:\CAMPP\schooldb_backup.sql
bash

ความหมายของ flag แต่ละตัว:

  • -u root — เข้าสู่ระบบด้วย user root
  • -p — ระบบจะถามรหัสผ่าน (พิมพ์แล้วไม่แสดงบนหน้าจอ เป็นวิธีที่ปลอดภัยกว่าการใส่รหัสผ่านติดไว้ในคำสั่ง)
  • -P 3307 — port ของ MySQL (P ตัวใหญ่)
  • > / < — เปลี่ยนทิศทางไฟล์: > เขียนออกเป็นไฟล์ (backup), < อ่านไฟล์เข้ามารัน (กู้คืน)
  • -e "..." — รันคำสั่ง SQL ตรง ๆ จาก command line (เช่น ตอนลบ/สร้างฐานข้อมูล)

การวางแผนรับมือภัยพิบัติ (Disaster Recovery)#

แผนรับมือภัยพิบัติ (Disaster Recovery) ครอบคลุมการกู้คืนเร็วแค่ไหน (RTO) และเสียข้อมูลได้มากแค่ไหน (RPO), การเก็บ backup หลายชุดหลายที่, การทำ replication และการซ้อมกู้คืนเป็นประจำ

Backup and Recovery#

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

สรุป: Backup & Recovery ปกป้องข้อมูลจากทุกสถานการณ์ วางกลยุทธ์ (full/incremental) ทดสอบกู้คืน และมีแผน disaster recovery ไว้เสมอ


10. แนวปฏิบัติการดูแลระบบฐานข้อมูล (Best Practices)#

ติดตามสุขภาพของฐานข้อมูล (Monitoring)#

ติดตามสถานะด้วยคำสั่ง เช่น SHOW STATUS, SHOW PROCESSLIST และเครื่องมือ monitoring เพื่อดูการใช้ทรัพยากร, query ช้า และการล็อก

แนวปฏิบัติด้านความปลอดภัย#

  • ใช้หลักสิทธิ์น้อยที่สุด (ดูหัวข้อ 6)
  • เปลี่ยนรหัสผ่านเป็นระยะ และเก็บเป็นความลับ
  • เปิด encryption สำหรับการเชื่อมต่อ (TLS) และข้อมูลอ่อนไหว
  • อัปเดต DBMS เพื่อแก้ช่องโหว่ด้านความปลอดภัย

ตั้งเวลาสำรองข้อมูล (Backup Scheduling)#

ตั้งเวลาสำรองข้อมูลอัตโนมัติ (เช่น cron job รัน mysqldump ทุกคืน) และเก็บหลายชุดหลายที่ตามกลยุทธ์ (ดูหัวข้อ 7)

การปรับประสิทธิภาพ (Performance Optimization)#

  • สร้าง index บนคอลัมน์ที่ค้นหา/เชื่อม/เรียงบ่อย (ดูหัวข้อ 5)
  • วิเคราะห์ query ช้าด้วย EXPLAIN และปรับให้ใช้ index
  • จำกัดจำนวนแถวด้วย LIMIT และเลือกเฉพาะคอลัมน์ที่จำเป็น (ไม่ใช้ SELECT * เมื่อไม่จำเป็น)
  • ใช้ view หรือ denormalize เล็กน้อยเมื่อช่วยความเร็วในการอ่าน

การบำรุงรักษาและแก้ปัญหา (Maintenance & Troubleshooting)#

  • ทำ OPTIMIZE TABLE / จัดเรียงข้อมูลเป็นระยะ
  • อัปเดต statistics ของ index ให้ query optimizer ทำงานได้ดี
  • เก็บ log และทดสอบกู้คืนเพื่อให้พร้อมรับมือปัญหา

สรุป: การดูแลระบบฐานข้อมูลที่ดี คือการ monitor สุขภาพระบบ รักษาความปลอดภัย สำรองข้อมูลเป็นประจำ ปรับประสิทธิภาพ และบำรุงรักษาอย่างสม่ำเสมอ เพื่อให้ระบบเร็ว ปลอดภัย และเชื่อถือได้


สรุป#

บทความนี้คือ Next Step ที่พาสำรวจหัวข้อถัดไปของระบบฐานข้อมูล:

  • การออกแบบฐานข้อมูลและ ER Diagram — ออกแบบโครงสร้างข้อมูลก่อนสร้างตาราง
  • Primary Key & Foreign Key — ตัวตนและความสัมพันธ์ พร้อม referential integrity
  • Normalization (1NF–3NF) — ลดข้อมูลซ้ำและกำจัด anomalies
  • Views — ตารางเสมือนช่วยลดความซับซ้อนและเพิ่มความปลอดภัย
  • Index — เร่งความเร็วการค้นหา พร้อมข้อแลกเปลี่ยน
  • Access Control & Security — ควบคุมสิทธิ์ตามหลักสิทธิ์น้อยที่สุด
  • Backup & Recovery — ปกป้องข้อมูลและกู้คืนเมื่อเกิดเหตุ
  • การดูแลระบบฐานข้อมูล (Database Administration) — แนวปฏิบัติดูแลระบบให้เร็ว ปลอดภัย และเชื่อถือได้

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

Relational Database: ก้าวต่อไป Advance ขึ้น
Author กานต์ ยงศิริวิทย์ / Karn Yongsiriwit
Published at June 19, 2026

Loading comments...

Comments 0