
ในยุคที่ข้อมูลคือหัวใจของทุกแอปพลิเคชัน ไม่ว่าจะเป็นเว็บไซต์ แอปมือถือ หรือระบบ Enterprise การเข้าใจ SQL และ Database อย่างถ่องแท้คือทักษะพื้นฐานที่นักพัฒนาทุกคนต้องมี บทความนี้จะพาคุณเดินทางตั้งแต่พื้นฐาน SQL ไปจนถึงการออกแบบฐานข้อมูล การเปรียบเทียบ MySQL กับ PostgreSQL และโลกของ NoSQL อย่าง MongoDB พร้อมตัวอย่างโค้ดจริงที่ใช้งานได้ทันที
Database (ฐานข้อมูล) คือระบบที่ใช้จัดเก็บ จัดการ และเรียกค้นข้อมูลอย่างมีโครงสร้าง ฐานข้อมูลแบ่งออกเป็นสองประเภทหลักที่มีปรัชญาการออกแบบแตกต่างกันอย่างสิ้นเชิง
ฐานข้อมูลเชิงสัมพันธ์จัดเก็บข้อมูลในรูปแบบตาราง (Table) ที่มีแถว (Row) และคอลัมน์ (Column) โดยใช้ภาษา SQL ในการจัดการ ข้อมูลจะถูกเชื่อมโยงกันผ่าน Primary Key และ Foreign Key ทำให้มีความสัมพันธ์ที่ชัดเจนระหว่างข้อมูลชุดต่างๆ ตัวอย่าง RDBMS ที่นิยม ได้แก่ MySQL, PostgreSQL, Oracle Database, Microsoft SQL Server และ SQLite
จุดเด่นของ RDBMS คือการรับประกัน ACID Properties ซึ่งประกอบด้วย Atomicity (ทำทั้งหมดหรือไม่ทำเลย), Consistency (ข้อมูลต้องถูกต้องเสมอ), Isolation (แต่ละ Transaction ไม่กระทบกัน) และ Durability (ข้อมูลที่ Commit แล้วจะไม่สูญหาย) ทำให้เหมาะกับระบบที่ต้องการความถูกต้องของข้อมูลสูง เช่น ระบบธนาคาร ระบบบัญชี หรือระบบ E-commerce
ฐานข้อมูลแบบ NoSQL ออกแบบมาเพื่อรองรับข้อมูลที่มีโครงสร้างหลากหลายและปริมาณมหาศาล แบ่งออกเป็นหลายประเภท ได้แก่ Document Store (MongoDB), Key-Value Store (Redis), Column-family Store (Cassandra) และ Graph Database (Neo4j) NoSQL เน้น Horizontal Scaling คือการเพิ่มเครื่องเพื่อรองรับภาระงานที่มากขึ้น แทนที่จะอัพเกรดเครื่องเดิม
คำสั่ง SELECT เป็นคำสั่งที่ใช้บ่อยที่สุดในการดึงข้อมูลจากฐานข้อมูล เราสามารถกำหนดเงื่อนไขด้วย WHERE, เรียงลำดับด้วย ORDER BY และจำกัดจำนวนผลลัพธ์ด้วย LIMIT
-- ดึงข้อมูลพนักงานที่เงินเดือนมากกว่า 50000 เรียงจากมากไปน้อย
SELECT id, name, department, salary
FROM employees
WHERE salary > 50000
AND department = 'Engineering'
ORDER BY salary DESC
LIMIT 10;
-- ใช้ LIKE สำหรับการค้นหาแบบ Pattern
SELECT * FROM products
WHERE name LIKE '%laptop%'
AND price BETWEEN 20000 AND 50000;
-- ใช้ IN สำหรับหลายค่า
SELECT * FROM orders
WHERE status IN ('pending', 'processing', 'shipped')
AND created_at >= '2026-01-01';
การจัดกลุ่มข้อมูลด้วย GROUP BY ร่วมกับฟังก์ชัน Aggregate เป็นเครื่องมือสำคัญในการวิเคราะห์ข้อมูล ฟังก์ชันพื้นฐาน ได้แก่ COUNT() นับจำนวน, SUM() รวมค่า, AVG() หาค่าเฉลี่ย, MAX() หาค่าสูงสุด, MIN() หาค่าต่ำสุด
-- สรุปยอดขายรายเดือนของแต่ละแผนก
SELECT department,
DATE_FORMAT(sale_date, '%Y-%m') AS month,
COUNT(*) AS total_orders,
SUM(amount) AS total_revenue,
AVG(amount) AS avg_order_value,
MAX(amount) AS max_order
FROM sales
GROUP BY department, DATE_FORMAT(sale_date, '%Y-%m')
HAVING SUM(amount) > 100000
ORDER BY total_revenue DESC;
นอกจากการดึงข้อมูล เราต้องสามารถเพิ่ม แก้ไข และลบข้อมูลได้อย่างถูกต้อง ทุกคำสั่งเหล่านี้ควรใช้ภายใน Transaction เพื่อความปลอดภัย
-- INSERT: เพิ่มข้อมูล
INSERT INTO employees (name, email, department, salary, hired_date)
VALUES ('สมชาย ใจดี', 'somchai@company.com', 'Engineering', 65000, CURDATE());
-- INSERT หลายแถวพร้อมกัน (Batch Insert — เร็วกว่า INSERT ทีละแถว)
INSERT INTO products (name, price, category) VALUES
('Keyboard Mechanical', 2500, 'peripheral'),
('Mouse Wireless', 890, 'peripheral'),
('Monitor 27 inch', 8900, 'display');
-- UPDATE: แก้ไขข้อมูล (ระวัง! ต้องมี WHERE เสมอ)
UPDATE employees
SET salary = salary * 1.10,
updated_at = NOW()
WHERE department = 'Engineering'
AND performance_rating >= 4;
-- DELETE: ลบข้อมูล (ระวัง! ต้องมี WHERE เสมอ)
DELETE FROM logs
WHERE created_at < DATE_SUB(NOW(), INTERVAL 90 DAY);
JOIN เป็นหัวใจของ Relational Database ที่ช่วยเชื่อมข้อมูลจากหลายตารางเข้าด้วยกัน การเลือก JOIN ที่ถูกต้องส่งผลต่อทั้งผลลัพธ์และประสิทธิภาพ
ดึงเฉพาะแถวที่มีข้อมูลตรงกันในทั้งสองตาราง หากฝั่งใดฝั่งหนึ่งไม่มีข้อมูลที่ตรงกัน แถวนั้นจะไม่ปรากฏในผลลัพธ์
-- INNER JOIN: ดึงออเดอร์พร้อมชื่อลูกค้า
SELECT o.id AS order_id, c.name AS customer_name,
o.total_amount, o.order_date
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
WHERE o.order_date >= '2026-01-01';
ดึงทุกแถวจากตารางซ้าย และแถวที่ตรงกันจากตารางขวา หากตารางขวาไม่มีข้อมูลจะแสดงเป็น NULL เหมาะสำหรับกรณีที่ต้องการดูข้อมูลทั้งหมดจากตารางหลัก
-- LEFT JOIN: ดูลูกค้าทุกคน รวมทั้งคนที่ยังไม่เคยสั่งซื้อ
SELECT c.name, c.email,
COUNT(o.id) AS order_count,
COALESCE(SUM(o.total_amount), 0) AS total_spent
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name, c.email
ORDER BY total_spent DESC;
RIGHT JOIN ทำงานตรงข้ามกับ LEFT JOIN คือดึงทุกแถวจากตารางขวา ส่วน FULL OUTER JOIN ดึงทุกแถวจากทั้งสองตาราง (MySQL ไม่รองรับโดยตรง ต้องใช้ UNION)
-- FULL OUTER JOIN ใน PostgreSQL
SELECT e.name AS employee, d.name AS department
FROM employees e
FULL OUTER JOIN departments d ON e.department_id = d.id;
-- จำลอง FULL OUTER JOIN ใน MySQL ด้วย UNION
SELECT e.name AS employee, d.name AS department
FROM employees e LEFT JOIN departments d ON e.department_id = d.id
UNION
SELECT e.name, d.name
FROM employees e RIGHT JOIN departments d ON e.department_id = d.id;
สร้าง Cartesian Product ระหว่างสองตาราง คือจับคู่ทุกแถวจากตารางแรกกับทุกแถวจากตารางที่สอง มักใช้ในการสร้างข้อมูลชุดค่าผสม
-- CROSS JOIN: สร้าง Matrix ขนาด/สีสินค้า
SELECT p.name, s.size_label, c.color_name
FROM products p
CROSS JOIN sizes s
CROSS JOIN colors c
WHERE p.category = 'clothing';
Subquery และ CTE ช่วยแบ่ง Query ที่ซับซ้อนออกเป็นส่วนย่อยๆ ที่เข้าใจง่ายขึ้น CTE มักอ่านง่ายกว่า Subquery และสามารถอ้างอิงซ้ำได้
-- Subquery: หาพนักงานที่เงินเดือนสูงกว่าค่าเฉลี่ยของแผนก
SELECT name, department, salary
FROM employees e
WHERE salary > (
SELECT AVG(salary)
FROM employees e2
WHERE e2.department = e.department
);
-- CTE: วิเคราะห์ลูกค้า VIP (สั่งซื้อมากกว่า 10 ครั้งและยอดรวมเกิน 100,000)
WITH customer_stats AS (
SELECT customer_id,
COUNT(*) AS order_count,
SUM(total_amount) AS total_spent,
AVG(total_amount) AS avg_order
FROM orders
WHERE order_date >= '2025-01-01'
GROUP BY customer_id
),
vip_customers AS (
SELECT cs.*, c.name, c.email
FROM customer_stats cs
JOIN customers c ON cs.customer_id = c.id
WHERE cs.order_count >= 10 AND cs.total_spent >= 100000
)
SELECT * FROM vip_customers
ORDER BY total_spent DESC;
Index คือโครงสร้างข้อมูลที่ช่วยให้ฐานข้อมูลค้นหาข้อมูลได้เร็วขึ้นอย่างมาก โดยไม่ต้องสแกนทุกแถวในตาราง (Full Table Scan) เปรียบเสมือนสารบัญในหนังสือที่ช่วยให้เราหาหน้าที่ต้องการได้ทันที
| ประเภท | วิธีการทำงาน | เหมาะกับ |
|---|---|---|
| B-Tree | โครงสร้างต้นไม้แบบสมดุล ค้นหา O(log n) | การค้นหาแบบช่วง, เรียงลำดับ, เปรียบเทียบ |
| Hash | ใช้ Hash Function แมปค่า O(1) | การค้นหาแบบ exact match เท่านั้น |
| Composite | Index หลายคอลัมน์ | Query ที่ WHERE หลายเงื่อนไขเสมอ |
| Covering | Index ที่ครอบคลุมทุกคอลัมน์ที่ต้องการ | หลีกเลี่ยงการอ่านตารางหลัก |
-- สร้าง Index พื้นฐาน
CREATE INDEX idx_employees_department ON employees(department);
-- Composite Index (ลำดับคอลัมน์สำคัญมาก!)
CREATE INDEX idx_orders_customer_date
ON orders(customer_id, order_date DESC);
-- Unique Index ป้องกันข้อมูลซ้ำ
CREATE UNIQUE INDEX idx_users_email ON users(email);
-- ตรวจสอบว่า Query ใช้ Index หรือไม่ด้วย EXPLAIN
EXPLAIN SELECT * FROM orders
WHERE customer_id = 123 AND order_date >= '2026-01-01';
การออกแบบฐานข้อมูลที่ดีเป็นพื้นฐานของแอปพลิเคชันที่มีประสิทธิภาพ Normalization คือกระบวนการจัดโครงสร้างตารางเพื่อลดความซ้ำซ้อนของข้อมูลและป้องกันปัญหา Anomaly
1NF (First Normal Form): ทุกคอลัมน์ต้องมีค่า Atomic (ค่าเดียว ไม่ใช่ลิสต์) และแต่ละแถวต้องไม่ซ้ำกัน ตัวอย่างที่ผิด: คอลัมน์ phone เก็บ "081-1234567, 089-9876543" ต้องแยกเป็นตาราง phone_numbers
2NF (Second Normal Form): ผ่าน 1NF แล้วและทุก Non-key Column ต้องขึ้นอยู่กับ Primary Key ทั้งหมด ไม่ใช่แค่บางส่วน (กรณี Composite Key) เช่น ถ้า PK คือ (order_id, product_id) แต่ customer_name ขึ้นอยู่กับ order_id เท่านั้น ต้องแยกออก
3NF (Third Normal Form): ผ่าน 2NF แล้วและไม่มี Transitive Dependency คือ Non-key Column ต้องไม่ขึ้นอยู่กับ Non-key Column อื่น เช่น ถ้ามี department_id → department_name ในตาราง employees ต้องแยก department_name ไปอยู่ตาราง departments
-- ตัวอย่าง Database Design: ระบบ E-commerce
CREATE TABLE customers (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock INT DEFAULT 0,
category_id INT,
FOREIGN KEY (category_id) REFERENCES categories(id)
);
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status ENUM('pending','confirmed','shipped','delivered','cancelled'),
total_amount DECIMAL(12,2),
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
CREATE TABLE order_items (
id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
MySQL และ PostgreSQL เป็นสอง RDBMS โอเพนซอร์สที่นิยมมากที่สุดในปัจจุบัน แต่ละตัวมีจุดเด่นที่แตกต่างกัน
| คุณสมบัติ | MySQL | PostgreSQL |
|---|---|---|
| ปรัชญา | ความเร็วและง่ายต่อการใช้งาน | ความถูกต้องและฟีเจอร์ครบถ้วน |
| ACID Compliance | รองรับ (InnoDB) | รองรับเต็มรูปแบบ |
| JSON Support | JSON type พื้นฐาน | JSONB (Binary, index ได้, เร็วกว่า) |
| Full-text Search | รองรับ (InnoDB) | รองรับ + tsvector + Ranking |
| Replication | Master-Slave, Group Replication | Streaming, Logical Replication |
| Extension | จำกัด | PostGIS, pg_trgm, TimescaleDB ฯลฯ |
| Window Functions | รองรับ (8.0+) | รองรับครบถ้วน (มีมานาน) |
| ผู้ใช้หลัก | Facebook, Twitter, Uber | Apple, Instagram, Spotify |
| เหมาะกับ | เว็บแอปทั่วไป, CMS, E-commerce | ระบบซับซ้อน, GIS, Analytics |
เลือก MySQL ถ้าต้องการความง่าย เริ่มต้นเร็ว และมี Community ใหญ่ มี Hosting รองรับมาก ใช้กับ WordPress, Laravel, CMS ทั่วไป
เลือก PostgreSQL ถ้าต้องการฟีเจอร์ขั้นสูง เช่น JSONB, Full-text Search, GIS (PostGIS) หรือต้องการ Standard SQL compliance สูง เหมาะกับระบบที่ซับซ้อนและต้องการ Scalability
NoSQL ไม่ใช่คู่แข่งของ SQL แต่เป็นเครื่องมือที่เหมาะกับงานที่แตกต่างกัน แต่ละประเภทมีจุดเด่นเฉพาะตัว
เก็บข้อมูลในรูปแบบ Document (BSON/JSON) ยืดหยุ่นสูง Schema ไม่ตายตัว เหมาะกับข้อมูลที่โครงสร้างเปลี่ยนแปลงบ่อยหรือมี Nested Data ซับซ้อน
// MongoDB: เพิ่มและค้นหาข้อมูลสินค้า
db.products.insertOne({
name: "Mechanical Keyboard",
price: 2500,
specs: {
switch_type: "Cherry MX Blue",
layout: "TKL",
rgb: true
},
tags: ["keyboard", "mechanical", "gaming"],
reviews: [
{ user: "john", rating: 5, comment: "Great!" },
{ user: "jane", rating: 4, comment: "Good value" }
]
});
// ค้นหาสินค้าที่มี rating >= 4 และราคาไม่เกิน 3000
db.products.find({
price: { $lte: 3000 },
"reviews.rating": { $gte: 4 }
}).sort({ price: 1 });
ฐานข้อมูลแบบ In-Memory ที่เร็วมาก อ่าน/เขียนได้ภายในหลักไมโครวินาที นิยมใช้เป็น Cache Layer, Session Store, Rate Limiter, Real-time Leaderboard และ Message Queue (Pub/Sub)
ออกแบบมาเพื่อรองรับ Write-heavy Workload ขนาดใหญ่ มี Linear Scalability และ No Single Point of Failure เหมาะกับ IoT Data, Time-series Data และ Logging ระดับ Enterprise
บริการ NoSQL แบบ Serverless ของ AWS ที่ Scale อัตโนมัติ จ่ายตามการใช้งาน เหมาะกับ Serverless Architecture และแอปที่ต้องการ Latency ต่ำมากในระดับ single-digit milliseconds
| เกณฑ์ | เลือก SQL | เลือก NoSQL |
|---|---|---|
| โครงสร้างข้อมูล | ชัดเจน ไม่ค่อยเปลี่ยน | ยืดหยุ่น เปลี่ยนบ่อย |
| ความสัมพันธ์ | ข้อมูลมี Relation ซับซ้อน | ข้อมูลค่อนข้าง Independent |
| Consistency | ต้องการ Strong Consistency | ยอมรับ Eventual Consistency ได้ |
| Scale | Vertical Scaling หลัก | Horizontal Scaling ง่าย |
| Transaction | ต้องการ Multi-row Transaction | ไม่จำเป็นมาก |
| ตัวอย่างงาน | E-commerce, Banking, ERP | Social Media, IoT, Real-time Analytics |
คำสั่ง EXPLAIN แสดงแผนการทำงานของ Query ว่าฐานข้อมูลจะค้นหาข้อมูลอย่างไร ใช้ Index หรือไม่ ช่วยให้เราปรับปรุง Query ที่ช้าได้ตรงจุด
-- MySQL: EXPLAIN ANALYZE (8.0+)
EXPLAIN ANALYZE
SELECT c.name, COUNT(o.id) as order_count
FROM customers c
JOIN orders o ON c.id = o.customer_id
WHERE o.order_date >= '2026-01-01'
GROUP BY c.id
HAVING order_count > 5;
-- PostgreSQL: EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders
WHERE customer_id = 123
ORDER BY order_date DESC
LIMIT 10;
เปิดใช้ Slow Query Log เพื่อจับ Query ที่ใช้เวลานาน เป็นเครื่องมือสำคัญในการตรวจจับปัญหาประสิทธิภาพ
-- MySQL: เปิด Slow Query Log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- จับ Query ที่นานกว่า 1 วินาที
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- PostgreSQL: ตั้งค่าใน postgresql.conf
-- log_min_duration_statement = 1000 -- Query นานกว่า 1000ms
การสร้าง Database Connection มีต้นทุนสูง Connection Pooling ช่วยรียูส Connection ที่มีอยู่แทนการสร้างใหม่ทุกครั้ง ลด Overhead อย่างมาก เครื่องมือยอดนิยม ได้แก่ PgBouncer สำหรับ PostgreSQL, ProxySQL สำหรับ MySQL หรือใช้ Pool ใน Application Level เช่น HikariCP (Java) หรือ SQLAlchemy Pool (Python)
การ Backup เป็นสิ่งสำคัญที่สุดอย่างหนึ่งในการดูแลฐานข้อมูล ไม่มีระบบใดที่ปลอดภัย 100% จาก Hardware Failure, Human Error หรือ Ransomware
# MySQL: mysqldump (Logical Backup)
mysqldump -u root -p --single-transaction \
--routines --triggers --all-databases > backup_full.sql
# MySQL: Restore
mysql -u root -p < backup_full.sql
# PostgreSQL: pg_dump
pg_dump -Fc -Z 9 -U postgres mydb > mydb.dump
# PostgreSQL: Restore
pg_restore -U postgres -d mydb mydb.dump
# MongoDB: mongodump
mongodump --uri="mongodb://localhost:27017/mydb" --out=/backup/
mongorestore --uri="mongodb://localhost:27017/mydb" /backup/mydb/
เก็บ Backup อย่างน้อย 3 ชุด บน 2 สื่อจัดเก็บที่ต่างกัน (เช่น Local Disk + Cloud Storage) โดย 1 ชุดต้องอยู่นอกสถานที่ (Off-site) ทดสอบ Restore เป็นประจำเพื่อให้แน่ใจว่า Backup ใช้งานได้จริง
ORM (Object-Relational Mapping) ช่วยให้เราทำงานกับฐานข้อมูลผ่าน Object ในภาษาโปรแกรม แทนการเขียน SQL โดยตรง ลด Boilerplate Code และช่วยป้องกัน SQL Injection
# Python + SQLAlchemy ORM
from sqlalchemy import create_engine, Column, Integer, String, Float
from sqlalchemy.orm import declarative_base, Session
Base = declarative_base()
class Product(Base):
__tablename__ = 'products'
id = Column(Integer, primary_key=True)
name = Column(String(200), nullable=False)
price = Column(Float, nullable=False)
category = Column(String(50))
engine = create_engine('postgresql://user:pass@localhost/mydb')
Base.metadata.create_all(engine)
with Session(engine) as session:
# เพิ่มสินค้า
product = Product(name="Keyboard", price=2500, category="peripheral")
session.add(product)
session.commit()
# ค้นหาสินค้า
products = session.query(Product).filter(
Product.price < 3000,
Product.category == "peripheral"
).order_by(Product.price.desc()).all()
// TypeScript + Prisma ORM
// schema.prisma
model Product {
id Int @id @default(autoincrement())
name String
price Float
category String?
}
// ใช้งาน Prisma Client
const products = await prisma.product.findMany({
where: {
price: { lte: 3000 },
category: "peripheral"
},
orderBy: { price: "desc" }
});
Q: เริ่มต้นเรียน SQL ควรใช้ฐานข้อมูลอะไร?
A: แนะนำ SQLite สำหรับเริ่มต้น เพราะติดตั้งง่ายมาก ไม่ต้องตั้ง Server แค่สร้างไฟล์ .db ก็ใช้ได้ จากนั้นค่อยขยับไป MySQL หรือ PostgreSQL เมื่อต้องการฟีเจอร์เพิ่มเติม
Q: ฐานข้อมูลรองรับข้อมูลได้กี่แถว?
A: ไม่มีขีดจำกัดตายตัว ขึ้นอยู่กับ Hardware และการออกแบบ MySQL/PostgreSQL จัดการตารางที่มีหลายพันล้านแถวได้ ถ้าออกแบบ Index และ Partition ดี
Q: ควรใช้ UUID หรือ Auto-increment เป็น Primary Key?
A: Auto-increment เร็วกว่าและ Index เล็กกว่า แต่ UUID เหมาะกับระบบ Distributed ที่ต้องสร้าง ID โดยไม่ต้องถามฐานข้อมูลส่วนกลาง หรือระบบที่ไม่ต้องการเปิดเผยลำดับข้อมูล
Q: ถ้ามีข้อมูลหลักสิบล้านแถว ควรทำอย่างไร?
A: ใช้ Partitioning แบ่งตารางตามเกณฑ์ (เช่น ตามวันที่ หรือ Range), สร้าง Index ที่เหมาะสม, ใช้ Read Replica สำหรับ Query ที่อ่านอย่างเดียว และพิจารณา Archiving ข้อมูลเก่า
Q: Docker เหมาะกับ Database จริงหรือ?
A: สำหรับ Development และ Testing เหมาะมาก เพราะตั้ง Environment ได้เร็ว แต่สำหรับ Production ต้องจัดการเรื่อง Persistent Volume, Backup Strategy และ Performance Tuning อย่างรอบคอบ อ่านเพิ่มเติมใน คู่มือ Docker ฉบับสมบูรณ์