บทความนี้เป็น 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 แล้วกด Start เพื่อเริ่มบริการต่าง ๆ

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

เคล็ดลับ: หากคุณทำภาคแรกจบ ฐานข้อมูล
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 เท่านั้น | user ↔ user_profile |
| One-to-Many (1:N) | 1 record ของ A เชื่อมกับหลาย record ของ B | province → หลาย patient |
| Many-to-Many (M:N) | หลาย record ของ A เชื่อมกับหลาย record ของ B | student ↔ course |
ข้อสำคัญ: ความสัมพันธ์แบบ 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 พร้อม legend อธิบายเครื่องหมายแต่ละแบบ ส่วนภาพด้านล่างคือตัวอย่าง ER Diagram ที่สร้างจริงด้วย 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_idsql[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 ที่เรากำหนดไว้ ขั้นตอนคือ:
- เลือกฐานข้อมูล
schooldbที่แผงด้านซ้าย - คลิกแท็บ Designer ด้านบน
- phpMyAdmin จะแสดงตารางทั้งหมดพร้อมเส้นความสัมพันธ์ (Foreign Key) เชื่อมระหว่างตารางให้ทันที

ปัจจุบันยังต้องวาด 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#
- คลิกเลือกฐานข้อมูล schooldb จากแถบด้านซ้าย
- เลือกแท็บ Operations
- คลิก Drop the database (DROP)
- กด OK เพื่อยืนยันการลบ

⚠️ คำเตือน: การลบฐานข้อมูลจะลบตารางและข้อมูลทั้งหมดภายในฐานข้อมูลนั้นอย่างถาวร
สร้างฐานข้อมูลและตารางใหม่#
ให้รัน 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 หรือตารางกลาง เพื่อเก็บความสัมพันธ์ระหว่างนักเรียนและรายวิชา
โครงสร้างความสัมพันธ์สามารถอธิบายได้ดังนี้

กล่าวคือ
student.student_idเชื่อมกับenrollment.student_idcourse.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_id | fname | lname | title | credits | enroll_date |
|---|---|---|---|---|---|
| 1 | Somchai | Jaidee | Database Systems | 3 | 2026-06-01 |
| 1 | Somchai | Jaidee | Web Programming | 3 | 2026-06-01 |
| 2 | Suda | Sukjai | Database Systems | 3 | 2026-06-02 |
| 2 | Suda | Sukjai | Artificial Intelligence | 3 | 2026-06-02 |
| 3 | Anan | Meesuk | Web Programming | 3 | 2026-06-03 |
| 4 | Malee | Thongdee | Artificial Intelligence | 3 | 2026-06-03 |
| 5 | Krit | Wongsa | Cybersecurity Basics | 2 | 2026-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;sqlON DELETE และ ON UPDATE#
เราสามารถควบคุมพฤติกรรมเมื่อมีการลบหรือแก้ไขข้อมูลที่ถูกอ้างอิงได้ด้วย ON DELETE และ ON UPDATE
ตัวอย่างเช่น
FOREIGN KEY (student_id) REFERENCES student(student_id)
ON DELETE RESTRICT
ON UPDATE CASCADEsql| 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_id | fname | lname | courses |
|---|---|---|---|
| 1 | Somchai | Jaidee | Database Systems, Web Programming |
| 2 | Suda | Sukjai | Database Systems, Artificial Intelligence |
| 3 | Anan | Meesuk | Web Programming |
การเก็บหลายค่าในช่องเดียวทำให้ค้นหา อัปเดต หรือ join ได้ยาก — นี่คือสิ่งที่ Normalization จะมาแก้
ข้อมูลซ้ำซ้อนและ Anomalies#
ตารางที่ออกแบบไม่ดีจะเกิด ข้อมูลซ้ำซ้อน (redundancy) และ 3 ปัญหา (anomalies):
- Update Anomaly — เปลี่ยนชื่อวิชาต้องแก้หลายแถว ถ้าแก้ไม่ครบข้อมูลจะขัดแย้งกัน
- Insert Anomaly — เพิ่มวิชาใหม่ที่ยังไม่มีใครลงทะเบียนไม่ได้ (ยังไม่มีนักเรียน → ไม่มีแถวให้ใส่)
- Delete Anomaly — ลบการลงทะเบียนคนสุดท้ายของวิชา ทำให้ข้อมูลวิชานั้นหายไปด้วย
First Normal Form (1NF) — รูปแบบมาตรฐานที่ 1#
1NF กำหนดให้ทุกคอลัมน์เก็บค่า เดี่ยว (atomic) — ไม่เก็บหลายค่าในช่องเดียว วิธีแก้คือแยกแต่ละวิชาออกเป็นแถวของตัวเอง และดึงรายละเอียดของวิชามาเป็นคอลัมน์:
| student_id | fname | lname | course_id | title | credits | enroll_date |
|---|---|---|---|---|---|---|
| 1 | Somchai | Jaidee | 1 | Database Systems | 3 | 2026-06-01 |
| 1 | Somchai | Jaidee | 2 | Web Programming | 3 | 2026-06-01 |
| 2 | Suda | Sukjai | 1 | Database Systems | 3 | 2026-06-02 |
| 2 | Suda | Sukjai | 3 | Artificial Intelligence | 3 | 2026-06-02 |
| 3 | Anan | Meesuk | 2 | Web Programming | 3 | 2026-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 ตาราง:
student—student_id→fname,lnamecourse—course_id→title,creditsenrollment—student_id,course_id→enroll_date
Third Normal Form (3NF) — รูปแบบมาตรฐานที่ 3#
3NF ต้องผ่าน 2NF และ ไม่มี transitive dependency — คอลัมน์ที่ไม่ใช่ key ต้องไม่ขึ้นกับคอลัมน์ที่ไม่ใช่ key อีกตัว
ตัวอย่าง transitive dependency: เพื่อให้เห็นภาพ ลองสมมติว่าตาราง student ของเรายังเก็บข้อมูลอาจารย์ที่ปรึกษา (advisor) ไว้ในตารางเดียวกันด้วย
| student_id | fname | lname | advisor_id | advisor_name | advisor_dept |
|---|---|---|---|---|---|
| 1 | Somchai | Jaidee | 101 | Dr. Smith | Computer Science |
| 2 | Suda | Sukjai | 101 | Dr. Smith | Computer Science |
| 3 | Anan | Meesuk | 102 | Dr. Lee | Mathematics |
PK คือ student_id แต่ advisor_name และ advisor_dept ไม่ได้ขึ้นกับ student_id โดยตรง — มันขึ้นกับ advisor_id อีกที:
student_id → advisor_id → advisor_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 อยู่ในแผงซ้าย (มีไอคอนตารางเสมือน) และสามารถคลิกเข้าไปดูข้อมูลได้เหมือนตารางทั่วไป

ช่วยลดความซับซ้อนของ 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 ProcedureINSERT INTO student ...— คำสั่ง SQL ที่เพิ่มข้อมูลนักเรียนใหม่END $$— จบบล็อก Stored Procedure (ใช้$$แทน;)DELIMITER ;— เปลี่ยน delimiter กลับเป็น;เหมือนเดิม
หลังจากสร้าง Stored Procedure แล้ว สามารถดูรายการ Stored Procedure ทั้งหมดใน phpMyAdmin ได้โดย:
- เลือกฐานข้อมูล
schooldbจากแผงด้านซ้าย - คลิกเมนู Procedures ด้านบน
- phpMyAdmin จะแสดง Stored Procedure ทั้งหมดที่สร้างไว้ในฐานข้อมูล
schooldb

การเรียกใช้ Stored Procedure#
หลังจากสร้างแล้ว สามารถเรียกใช้ Stored Procedure ได้ด้วยคำสั่ง CALL:
-- เรียกใช้ Stored Procedure เพื่อเพิ่มนักเรียนใหม่
CALL AddStudent('Nopphadol', 'Sriwilai');
-- ตรวจสอบผลลัพธ์
SELECT * FROM student;sqlผลลัพธ์จะแสดงนักเรียนคนใหม่ที่เพิ่มเข้าไป:
| student_id | fname | lname |
|---|---|---|
| 1 | Somchai | Jaidee |
| 2 | Suda | Sukjai |
| 3 | Anan | Meesuk |
| 4 | Malee | Thongdee |
| 5 | Krit | Wongsa |
| 6 | Nopphadol | Sriwilai |
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;sqlStored 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;sqlStored 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#
- Full Table Scan — ไล่อ่านทุกแถว ช้าเมื่อข้อมูลเยอะ
- Indexed Search — ใช้ index กระโดดไปยังแถวที่ต้องการโดยตรง เร็วกว่ามาก
ใช้คำสั่ง EXPLAIN เพื่อดูว่า query ใช้ index หรือทำ full table scan:
EXPLAIN SELECT * FROM student WHERE lname = 'Jaidee';sqlผลลัพธ์ (ก่อนสร้าง index):
| id | select_type | table | type | possible_keys | key | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | student | ALL | NULL | NULL | 5 | 20.00 | Using 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 อีกครั้ง จะได้ผลลัพธ์ใหม่:
| id | select_type | table | type | possible_keys | key | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | student | ref | idx_student_lname | idx_student_lname | 1 | 100.00 | NULL |
เปรียบเทียบก่อน/หลังสร้าง index:
| คอลัมน์ | ก่อน (ไม่มี index) | หลัง (มี index) |
|---|---|---|
type | ALL (อ่านทุกแถว) | ref (ค้นผ่าน index) ✅ |
key | NULL (ไม่ใช้ index) | idx_student_lname ✅ |
rows | 5 (ไล่อ่านทุกแถว) | 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';sqlAuthentication และ 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 กลับเข้ามา (กู้คืน)

หรือใช้คำสั่ง mysqldump ใน command line โดย mysql/mysqldump อยู่ในโฟลเดอร์ runtime ของ CAMPP ขั้นตอนคือ:
- เปิด Command Prompt (Windows) หรือ Terminal (macOS/Linux)
cdเข้าไปยังโฟลเดอร์binที่เก็บmysql/mysqldump(อยู่ใต้runtimeตามเวอร์ชันที่ติดตั้ง) เช่น บน Windows:
cd \
cd CAMPP\runtime\mysql-8.4.0-winx64\binbashชื่อโฟลเดอร์เวอร์ชัน (เช่น
mysql-8.4.0-winx64) อาจต่างจากนี้ตามเวอร์ชันที่ติดตั้ง — ให้เปิดโฟลเดอร์C:\CAMPP\runtimeแล้วเข้าไปในโฟลเดอร์binของ MySQL บน macOS/Linux ก็คล้ายกัน เช่นcd /Applications/CAMPP/runtime/mysql-<version>/bin
- รันคำสั่งสำรอง/กู้คืนจากโฟลเดอร์นี้ (สำรองฐานข้อมูล
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.sqlbashความหมายของ flag แต่ละตัว:
-u root— เข้าสู่ระบบด้วย userroot-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 ได้อย่างมั่นใจ