T
Trinh Digital
Xây dựng Hệ thống

Database chậm? 5 cách tối ưu không cần viết lại code

Trinh Digital · · 11 phút đọc

Tối ưu database là kỹ năng quan trọng mà mọi developer và chủ doanh nghiệp cần quan tâm khi hệ thống bắt đầu chậm. Tin tốt là: phần lớn vấn đề database chậm có thể giải quyết mà không cần viết lại code — chỉ cần tối ưu cấu hình và thêm index đúng chỗ. Theo nghiên cứu từ Percona (2025), 80% vấn đề performance database đến từ thiếu index hoặc query không tối ưu, chỉ 20% cần thay đổi kiến trúc.

Bài viết này chia sẻ 5 kỹ thuật tối ưu database thực tế — mỗi kỹ thuật kèm ví dụ cụ thể và con số cải thiện.

Dấu hiệu database đang chậm

Trước khi tối ưu, cần xác định chính xác vấn đề:

Triệu chứngCó thể do database?Cách xác nhận
Website load 5+ giâyKiểm tra TTFB > 2s
Admin panel chậm hơn frontendRất có thểAdmin query phức tạp hơn
Chậm hơn vào giờ cao điểmCheck CPU/RAM lúc peak
Trang listing/search chậmGần chắc chắnQuery không có index
Export báo cáo timeoutQuery quét toàn bảng
Server CPU 100%Có thểCheck MySQL processlist

Cách xác nhận database là bottleneck

-- MySQL: Xem query đang chạy
SHOW PROCESSLIST;

-- Bật slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- Log query chạy > 1 giây

-- Xem slow query log
-- File thường ở /var/log/mysql/mysql-slow.log

Cách 1: Thêm Index đúng chỗ

Vấn đề phổ biến nhất

80% database chậm là do thiếu index. Khi query không có index, database phải quét toàn bộ bảng (full table scan) — giống như đọc hết 1.000 trang sách để tìm 1 từ.

Ví dụ thực tế

Bảng orders: 500.000 records

-- Query: Tìm đơn hàng của khách hàng
SELECT * FROM orders WHERE customer_id = 1234;

Không có index: Quét 500.000 rows → 2.3 giây

-- Thêm index
CREATE INDEX idx_orders_customer_id ON orders(customer_id);

Có index: Tìm trực tiếp → 0.003 giây (nhanh hơn 766 lần)

Xác định query nào cần index

-- EXPLAIN cho biết query scan bao nhiêu rows
EXPLAIN SELECT * FROM orders WHERE customer_id = 1234;

Kết quả EXPLAIN:

TrườngTốtXấu
typeref, const, eq_refALL (full scan)
keyTên indexNULL (không dùng index)
rowsNhỏ (< 100)Lớn (= tổng records)
ExtraUsing indexUsing filesort, Using temporary

Composite Index

Khi query filter nhiều cột cùng lúc:

-- Query phổ biến
SELECT * FROM products
WHERE category_id = 5
AND is_active = 1
ORDER BY price ASC;

-- Composite index (thứ tự cột quan trọng!)
CREATE INDEX idx_products_cat_active_price
ON products(category_id, is_active, price);

Quy tắc thứ tự cột trong composite index:

  1. Cột dùng trong WHERE (equality) trước
  2. Cột dùng trong WHERE (range) sau
  3. Cột dùng trong ORDER BY cuối cùng

Cảnh báo: Đừng index quá nhiều

Số indexRead speedWrite speedDung lượng
0 indexChậmNhanh nhấtNhỏ nhất
3–5 indexNhanhTốt+20–30%
10+ indexNhanhChậm+50–100%
20+ indexNhanhRất chậm+100–200%

Khuyến nghị: 3–7 index/bảng là hợp lý cho hầu hết ứng dụng.

Cách 2: Tối ưu Query (không sửa code)

Slow query thường gặp và cách fix

Query 1: SELECT *

-- Sai: Lấy tất cả cột (kể cả không dùng)
SELECT * FROM products WHERE category_id = 5;

-- Đúng: Chỉ lấy cột cần thiết
SELECT id, name, price, thumbnail FROM products WHERE category_id = 5;

Cải thiện: Giảm data transfer 60–80%, đặc biệt khi bảng có cột TEXT/BLOB lớn.

Query 2: N+1 Problem

-- Sai: 1 query lấy orders + N query lấy customer cho mỗi order
SELECT * FROM orders WHERE status = 'pending';
-- Rồi loop:
SELECT * FROM customers WHERE id = ?; -- Chạy N lần

-- Đúng: 1 query với JOIN
SELECT o.*, c.name, c.email
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.status = 'pending';

Cải thiện: Từ 101 queries → 1 query. Thời gian từ 3 giây → 0.05 giây.

Query 3: LIKE ‘%keyword%’

-- Sai: Leading wildcard → không dùng được index
SELECT * FROM products WHERE name LIKE '%áo thun%';

-- Tốt hơn: Full-text search
ALTER TABLE products ADD FULLTEXT(name, description);
SELECT * FROM products WHERE MATCH(name, description) AGAINST('áo thun');

Cải thiện: Từ full table scan → full-text index. 10x–100x nhanh hơn.

Query 4: Subquery có thể thay bằng JOIN

-- Chậm: Subquery chạy cho mỗi row
SELECT * FROM products
WHERE id IN (SELECT product_id FROM order_items WHERE quantity > 10);

-- Nhanh: JOIN
SELECT DISTINCT p.*
FROM products p
JOIN order_items oi ON p.id = oi.product_id
WHERE oi.quantity > 10;

Công cụ phân tích query

ToolSử dụngMiễn phí?
EXPLAIN / EXPLAIN ANALYZEXem execution plan
MySQL Slow Query LogLog query chậm
pt-query-digestPhân tích slow log chi tiếtCó (Percona)
phpMyAdminGiao diện đồ họa analyze
MySQL WorkbenchVisual EXPLAIN
pgAdminPostgreSQL query analyzer

Cách 3: Cấu hình MySQL/PostgreSQL cho performance

MySQL/MariaDB tuning

# /etc/mysql/mysql.conf.d/mysqld.cnf

[mysqld]
# InnoDB Buffer Pool - CÁI QUAN TRỌNG NHẤT
# Đặt = 70-80% RAM nếu server chỉ chạy MySQL
innodb_buffer_pool_size = 1G  # Cho VPS 2GB RAM

# InnoDB Log
innodb_log_file_size = 256M
innodb_log_buffer_size = 64M

# Connection
max_connections = 150
wait_timeout = 300
interactive_timeout = 300

# Query Cache (MySQL 5.7, đã bỏ ở 8.0)
# query_cache_type = 1
# query_cache_size = 64M

# Temp tables
tmp_table_size = 64M
max_heap_table_size = 64M

# Sort & Join buffer
sort_buffer_size = 4M
join_buffer_size = 4M
read_rnd_buffer_size = 4M

# Thread
thread_cache_size = 16

Mức cải thiện từ tuning cấu hình

Thông sốMặc địnhTối ưuCải thiện
innodb_buffer_pool_size128MB1GB (VPS 2GB)Lên đến 10x cho read
innodb_log_file_size48MB256MB2–5x cho write
max_connections151150 (+ connection pooling)Tránh OOM
sort_buffer_size256KB4MB2–3x cho ORDER BY

PostgreSQL tuning

# /etc/postgresql/15/main/postgresql.conf

# Memory
shared_buffers = 512MB          # 25% RAM
effective_cache_size = 1536MB   # 75% RAM
work_mem = 16MB                 # Per-operation memory
maintenance_work_mem = 256MB    # VACUUM, CREATE INDEX

# WAL
wal_buffers = 16MB
checkpoint_completion_target = 0.9

# Planner
random_page_cost = 1.1          # SSD (mặc định 4.0 cho HDD)
effective_io_concurrency = 200  # SSD

# Connections
max_connections = 100

Cách 4: Caching Layer

Redis/Memcached: Không cần query lại database

Thay vì query database mỗi lần user truy cập, cache kết quả trong memory:

So sánhDatabase queryRedis cache
Thời gian50–500ms0.1–1ms
Throughput1.000 queries/giây100.000 queries/giây
CPU usageCaoThấp

Chiến lược caching

Chiến lượcMô tảPhù hợp
Cache-asideApp check cache trước, nếu miss thì query DB rồi cachePhổ biến nhất
Write-throughGhi vào cache + DB cùng lúcData consistency cao
Write-behindGhi vào cache trước, async ghi DB sauWrite-heavy
TTL-basedCache tự hết hạn sau N giâyĐơn giản, hiệu quả

Dữ liệu nên cache

Nên cacheKhông nên cache
Trang chủ, category listingGiỏ hàng (thay đổi liên tục)
Thông tin sản phẩmSố lượng tồn kho real-time
Menu, navigationSession data nhạy cảm
Kết quả tìm kiếm phổ biếnDữ liệu cá nhân
Cấu hình websiteDữ liệu tài chính

Kết quả thực tế

Một website e-commerce (200.000 products, 50.000 visitors/ngày):

MetricTrước cacheSau Redis cacheCải thiện
Page load (listing)3.2 giây0.4 giây8x
Database CPU85%25%-71%
Database queries/giây2.500400-84%
Response time P955.1 giây0.8 giây6.4x

Cách 5: Bảo trì database định kỳ

Các tác vụ bảo trì

Tác vụTần suấtLệnh MySQLHiệu quả
ANALYZE TABLEHàng tuầnANALYZE TABLE products;Cập nhật thống kê cho optimizer
OPTIMIZE TABLEHàng thángOPTIMIZE TABLE orders;Giảm fragmentation, thu gọn file
Xóa data cũHàng thángArchive + DELETEGiảm kích thước bảng
Check slow queriesHàng tuầnReview slow query logPhát hiện query mới chậm
Backup + test restoreHàng ngày/tuầnmysqldump, pg_dumpĐảm bảo data an toàn

Automatic maintenance script

#!/bin/bash
# /opt/db-maintenance.sh
# Chạy hàng tuần (crontab: 0 4 * * 0)

MYSQL_USER="admin"
MYSQL_PASS="password"
DB_NAME="production_db"

echo "=== Database Maintenance: $(date) ==="

# Analyze all tables
mysql -u$MYSQL_USER -p$MYSQL_PASS $DB_NAME -e "
SELECT CONCAT('ANALYZE TABLE ', table_name, ';')
FROM information_schema.tables
WHERE table_schema = '$DB_NAME'" | mysql -u$MYSQL_USER -p$MYSQL_PASS $DB_NAME

# Check for tables needing optimization
mysql -u$MYSQL_USER -p$MYSQL_PASS $DB_NAME -e "
SELECT table_name, data_free/1024/1024 as fragmented_mb
FROM information_schema.tables
WHERE table_schema = '$DB_NAME'
AND data_free > 10*1024*1024
ORDER BY data_free DESC;"

echo "=== Maintenance Complete ==="

Archiving old data

-- Chuyển đơn hàng > 2 năm sang bảng archive
INSERT INTO orders_archive SELECT * FROM orders WHERE created_at < DATE_SUB(NOW(), INTERVAL 2 YEAR);
DELETE FROM orders WHERE created_at < DATE_SUB(NOW(), INTERVAL 2 YEAR);

-- Kết quả: Bảng orders từ 5M rows → 1.5M rows
-- Query nhanh hơn 2–3x

Tổng kết: Lộ trình tối ưu database

BướcThao tácThời gianCải thiện kỳ vọng
1Bật slow query log, xác định query chậm30 phútBaseline
2Thêm index cho query chậm nhất1–2 giờ5–100x
3Tối ưu cấu hình MySQL/PostgreSQL1 giờ2–10x
4Triển khai Redis caching2–4 giờ5–20x
5Setup bảo trì tự động1 giờDuy trì performance

Tổng thời gian: 5–8 giờ. Tổng chi phí: 0 VND (nếu tự làm) hoặc 5–15 triệu VND (thuê chuyên gia).

FAQ — Câu hỏi thường gặp

Database bao nhiêu records thì bắt đầu chậm?

Không có con số cụ thể — phụ thuộc vào indexing, cấu hình server, và query complexity. MySQL/PostgreSQL xử lý tốt hàng chục triệu records nếu index đúng. Nhiều hệ thống chạy mượt với 100M+ records. Nếu database chậm ở mức vài trăm nghìn records, vấn đề chắc chắn là do thiếu index hoặc query sai.

Có nên dùng ORM hay viết raw SQL?

ORM (Eloquent, Sequelize, TypeORM) tốt cho development speed và bảo trì. Nhưng ORM đôi khi generate query không tối ưu (N+1 problem). Khuyến nghị: dùng ORM cho CRUD đơn giản, viết raw SQL cho query phức tạp và báo cáo. Luôn check query mà ORM generate bằng EXPLAIN.

Redis vs Memcached — dùng cái nào?

Redis: data structures phong phú (string, list, set, hash), persistence, pub/sub. Memcached: đơn giản hơn, nhanh hơn một chút cho simple key-value. 90% trường hợp: chọn Redis — linh hoạt hơn, tính năng nhiều hơn, và có thể dùng cho cả caching, session, queue.

Kết luận

Database chậm không có nghĩa cần viết lại ứng dụng. 5 kỹ thuật trong bài viết này — indexing, query optimization, server tuning, caching, và maintenance — có thể cải thiện performance 10–100x mà không thay đổi 1 dòng code ứng dụng.

Nếu bạn cần hỗ trợ tối ưu database hoặc xây dựng hệ thống hiệu suất cao cho doanh nghiệp, hãy liên hệ Trinh Digital. Chúng tôi đã giúp nhiều SME Việt Nam tăng tốc hệ thống mà không cần thay đổi tech stack.

#performance#database#indexing#tối ưu
Chia sẻ: Z

Sẵn sàng chuyển đổi số cùng Trinh Digital?

Liên hệ ngay để nhận tư vấn miễn phí. Đội ngũ chuyên gia sẽ phân tích nhu cầu và đề xuất giải pháp tối ưu.

Zalo