Nỗi ám ảnh mang tên “Search tiếng Việt”
Làm ứng dụng cho người Việt, bài toán “gõ không dấu ra có dấu” là yêu cầu bắt buộc, không phải tính năng thêm thắt. Khách hàng luôn muốn gõ ‘dien thoai’ vẫn phải ra ‘điện thoại’. Nếu bạn chỉ dùng LIKE %keyword%, hệ thống sẽ sớm “hụt hơi” khi dữ liệu phình to.
Tôi từng quản lý một database production chạy MySQL 8.0 với hơn 10 triệu bản ghi (khoảng 50GB). Ban đầu, các query tìm kiếm mất tận 2-3 giây để phản hồi, đẩy CPU server lên mức báo động 80-90%. Sau khi áp dụng các kỹ thuật tối ưu dưới đây, thời gian query giảm xuống còn dưới 150ms, CPU duy trì ổn định ở mức 10%.
Cân nhắc giữa các phương pháp phổ biến
1. Dùng LIKE và Collation (Cách thủ công)
Đây là giải pháp “mì ăn liền”. Bạn thường chọn Collation utf8mb4_unicode_ci hoặc utf8mb4_vietnamese_ci. Tuy nhiên, MySQL không thể tự động hiểu ‘d’ và ‘đ’ là một nếu không cấu hình cực kỳ phức tạp.
- Điểm yếu: Gây ra tình trạng Full Table Scan. Database phải quét từng dòng một, cực kỳ tốn tài nguyên.
- Thực tế: Chỉ nên dùng cho các bảng danh mục dưới 1.000 dòng.
2. MySQL Full-Text Search (FTS) mặc định
FTS nhanh hơn LIKE gấp nhiều lần nhờ cơ chế đánh Index chuyên dụng. Dù vậy, nó vẫn gặp khó khăn với các từ có dấu đặc thù của tiếng Việt nếu bạn để cấu hình mặc định.
3. Kỹ thuật Shadow Column (Cột bóng ma) – “Vua” hiệu năng
Đây là kỹ thuật tôi luôn ưu tiên cho các dự án lớn. Ý tưởng rất đơn giản: Ta tạo thêm một cột phụ (ví dụ search_vector) lưu nội dung đã được loại bỏ dấu. Khi người dùng tìm kiếm, ta chỉ việc quét trên cột “sạch” này.
Triển khai Shadow Column kết hợp Full-Text Search
Bước 1: Cấu trúc lại bảng dữ liệu
Thêm một cột chuyên biệt để lưu text không dấu. Đừng quên đánh Index Full-Text ngay cho cột này.
ALTER TABLE products ADD COLUMN content_search_no_sign TEXT;
CREATE FULLTEXT INDEX idx_fts_vietnamese ON products(content_search_no_sign);
Bước 2: Chuẩn hóa dữ liệu từ tầng Application
Nhiều bạn viết Function trực tiếp trong MySQL để xóa dấu. Tuy nhiên, việc này vô tình bắt Database Server phải xử lý thêm logic tính toán. Hãy chuyển việc này về phía Backend (NodeJS, Python, PHP).
Ví dụ với NodeJS, bạn có thể dùng thư viện unidecode để chuyển “Tiếng Việt có dấu” thành “Tieng Viet co dau” trước khi lưu vào DB. Cách này giúp giảm tải cho Database tối đa.
Bước 3: Tự động hóa với Trigger
Để dữ liệu ở cột chính và cột phụ luôn khớp nhau, Trigger là lựa chọn sáng giá. Mỗi khi title được cập nhật, cột content_search_no_sign cũng sẽ tự động thay đổi theo.
CREATE TRIGGER before_product_update
BEFORE UPDATE ON products
FOR EACH ROW
BEGIN
-- Gọi hàm xóa dấu đã định nghĩa sẵn
SET NEW.content_search_no_sign = remove_accents(NEW.title);
END;
Cấu hình để MySQL hiểu tiếng Việt “chuẩn” hơn
Tiếng Việt có đặc điểm là nhiều từ rất ngắn như “áo”, “xe”, “tủ”. Mặc định, MySQL bỏ qua các từ dưới 3 ký tự, khiến kết quả tìm kiếm bị thiếu sót trầm trọng.
Chỉnh lại chiều dài từ tối thiểu
Bạn cần can thiệp vào file cấu hình my.cnf (Linux) hoặc my.ini (Windows):
[mysqld]
innodb_ft_min_token_size = 2
ft_min_word_len = 2
Sau khi sửa, hãy restart service và chạy lệnh OPTIMIZE TABLE products;. Bước này cực kỳ quan trọng để MySQL xây dựng lại bộ Index mới theo quy tắc vừa thiết lập.
Khai thác sức mạnh Boolean Mode
Đừng chỉ dùng tìm kiếm thông thường. Hãy tận dụng IN BOOLEAN MODE để lọc kết quả chính xác hơn.
-- Tìm sản phẩm bắt buộc có từ "dien" và "thoai"
SELECT * FROM products
WHERE MATCH(content_search_no_sign) AGAINST('+dien +thoai' IN BOOLEAN MODE);
Mẹo nhỏ từ kinh nghiệm thực chiến
Khi hệ thống đạt ngưỡng hàng triệu user, việc chỉ lưu bản không dấu đôi khi làm giảm độ chính xác. Một mẹo tôi thường dùng là lưu cả hai bản vào cùng một cột, phân tách bằng dấu cách. Ví dụ: "điện thoại dien thoai".
Lúc này, dù người dùng gõ kiểu gì, MySQL vẫn tìm ra. Đặc biệt, những kết quả khớp cả dấu sẽ có điểm số (relevance score) cao hơn và hiện lên đầu. Đây chính là cách các hệ thống lớn tối ưu trải nghiệm người dùng.
Lời kết
Nếu dự án của bạn chỉ ở quy mô nhỏ, LIKE vẫn ổn. Nhưng để đi đường dài và chuyên nghiệp, hãy nhớ 3 trụ cột: Shadow Column, Xử lý dấu ở Backend và Cấu hình lại min_token_size. Chúc các bạn có những câu query nhanh như chớp!

