- Tác giả

- Name
- Nguyễn Đức Xinh
- Ngày xuất bản
- Ngày xuất bản
InnoDB vs MyISAM: So Sánh Và Chọn Storage Engine Phù Hợp
Giới Thiệu
Một câu hỏi tưởng đơn giản nhưng vẫn khiến nhiều người mới bối rối: "Tạo bảng MySQL xong, sao lại phải chọn storage engine?" MySQL là một trong số ít RDBMS có kiến trúc pluggable storage engine — mỗi bảng (không phải cả database) có thể dùng một engine lưu trữ khác nhau, và engine đó quyết định gần như mọi thứ về hành vi của bảng: có hỗ trợ transaction không, khoá ở mức nào, có sống sót qua một lần crash server không.
Bài này tập trung vào hai engine "kinh điển" nhất: InnoDB (mặc định của MySQL từ bản 5.5) và MyISAM (engine mặc định trước đó, vẫn còn tồn tại trong không ít hệ thống legacy). Nếu bạn cần đào sâu kiến trúc InnoDB (buffer pool, MVCC, adaptive hash index, tuning nâng cao), bài InnoDB Storage Engine: Hướng dẫn toàn diện đã viết rất chi tiết — bài này sẽ không lặp lại phần đó, mà tập trung vào MyISAM (engine chưa có bài riêng) và quan trọng nhất: khi nào chọn cái nào, và vì sao.
Storage Engine Là Gì? (Ôn Lại Nhanh)
Storage engine là lớp chịu trách nhiệm thực sự đọc/ghi dữ liệu xuống đĩa — mọi thứ phía trên nó (SQL parser, optimizer, cache câu lệnh) đều dùng chung, bất kể bảng dùng engine nào. Bạn có thể kiểm tra và đổi engine ở mức từng bảng:
-- Xem bảng đang dùng engine gì
SELECT table_name, engine
FROM information_schema.tables
WHERE table_schema = 'shop_db';
-- Chỉ định engine khi tạo bảng
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
...
) ENGINE=InnoDB;
Vì mỗi bảng chọn engine độc lập, một database hoàn toàn có thể có bảng orders dùng InnoDB (cần transaction) và bảng access_log dùng MyISAM (chỉ ghi tuần tự, không cần transaction) — đây là tình huống rất thường gặp trong các hệ thống legacy.
MyISAM Là Gì? Kiến Trúc Và Đặc Điểm
MyISAM là engine mặc định của MySQL trước phiên bản 5.5 (2010). Kiến trúc của nó đơn giản hơn InnoDB rất nhiều — mỗi bảng MyISAM là 3 file vật lý riêng biệt trên đĩa:
.frm— định nghĩa cấu trúc bảng (metadata).MYD(MY Data) — dữ liệu thực tế, lưu tuần tự.MYI(MY Index) — toàn bộ index của bảng
So với InnoDB (dữ liệu và index nằm trong tablespace, có buffer pool quản lý cache thông minh theo thuật toán LRU), cấu trúc 3-file rời rạc của MyISAM là lý do trực tiếp dẫn đến phần lớn hạn chế của nó.
1. Table-Level Locking — Hạn Chế Lớn Nhất
Đây là khác biệt quan trọng nhất cần nhớ: MyISAM khoá toàn bộ bảng mỗi khi có một câu lệnh ghi (INSERT/UPDATE/DELETE), trong khi InnoDB khoá ở mức dòng (row-level locking).
-- Với bảng MyISAM: UPDATE này khoá TOÀN BỘ bảng orders_myisam
-- cho tới khi hoàn tất — mọi INSERT/UPDATE/DELETE khác trên
-- CÙNG bảng (kể cả không liên quan gì tới dòng đang sửa) đều phải xếp hàng chờ.
UPDATE orders_myisam SET status = 'shipped' WHERE id = 12345;
Với một bảng ít ghi (vài lần/phút), điều này không đáng lo. Nhưng với một bảng có nhiều client ghi đồng thời (ví dụ log truy cập, hàng đợi xử lý), table-level lock biến ghi dữ liệu thành một hàng đợi tuần tự — đây chính là nguyên nhân phổ biến nhất khiến một hệ thống cũ dùng MyISAM "đột nhiên chậm hẳn" khi traffic tăng, dù CPU/Memory hoàn toàn bình thường (nếu bạn đã đọc bài Troubleshooting MySQL, đây chính xác là trường hợp State: Locked trong SHOW PROCESSLIST, khác với Waiting for row lock của InnoDB).
2. Không Hỗ Trợ Transaction (Không Có ACID)
MyISAM không có COMMIT/ROLLBACK. Mỗi câu lệnh được ghi thẳng xuống đĩa ngay lập tức, không thể gộp nhiều thay đổi thành một đơn vị "tất cả hoặc không gì cả":
START TRANSACTION;
UPDATE accounts_myisam SET balance = balance - 100 WHERE id = 1;
UPDATE accounts_myisam SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- Với MyISAM: START TRANSACTION/COMMIT bị BỎ QUA HOÀN TOÀN (không báo lỗi!)
-- Nếu server crash giữa 2 câu UPDATE, dòng 1 bị trừ tiền nhưng dòng 2
-- KHÔNG được cộng — dữ liệu sai lệch vĩnh viễn, không có cách nào rollback.
Đây là lý do MyISAM tuyệt đối không phù hợp cho dữ liệu tài chính, đơn hàng, hay bất kỳ nghiệp vụ nào cần tính toàn vẹn giữa nhiều bảng/nhiều dòng.
3. Không Có Foreign Key Constraints
MyISAM chấp nhận cú pháp FOREIGN KEY khi tạo bảng nhưng âm thầm bỏ qua nó — không báo lỗi, nhưng cũng không thực sự ràng buộc:
CREATE TABLE order_items_myisam (
order_id INT,
FOREIGN KEY (order_id) REFERENCES orders_myisam(id)
) ENGINE=MyISAM;
-- Chạy được, không lỗi — nhưng bạn có thể INSERT order_id không tồn tại
-- trong orders_myisam mà MySQL không hề ngăn cản.
4. Không Có Crash Recovery Đáng Tin Cậy
InnoDB có redo log — khi server khởi động lại sau một lần crash bất thường (mất điện, kill -9), nó tự động phát lại (replay) các thay đổi chưa kịp ghi xuống đĩa, đảm bảo dữ liệu nhất quán. MyISAM không có cơ chế này. Một bảng MyISAM bị crash giữa chừng khi đang ghi rất dễ rơi vào trạng thái crashed — MySQL sẽ báo lỗi Table '...' is marked as crashed (đã nhắc ở bài Troubleshooting) và bạn phải chạy thủ công:
CHECK TABLE orders_myisam;
REPAIR TABLE orders_myisam;
REPAIR TABLE với bảng lớn có thể mất hàng giờ và khoá hoàn toàn bảng trong lúc sửa — một trải nghiệm không ai muốn gặp lúc production đang gặp sự cố.
5. Full-Text Search — Lý Do Lịch Sử MyISAM Từng Bắt Buộc
Có một lý do chính đáng khiến rất nhiều hệ thống cũ chọn MyISAM: trước MySQL 5.6 (2013), chỉ MyISAM hỗ trợ FULLTEXT INDEX — muốn tìm kiếm văn bản (MATCH() AGAINST()), bắt buộc phải dùng MyISAM cho bảng đó, bất kể có cần transaction hay không. Từ 5.6 trở đi, InnoDB đã hỗ trợ đầy đủ full-text index — đây gần như là lý do cuối cùng khiến ai đó "buộc phải" chọn MyISAM, và giờ lý do đó không còn nữa. Nếu hệ thống của bạn vẫn dùng MyISAM chỉ vì full-text search, đây là ứng viên hàng đầu để chuyển sang InnoDB.
6. COUNT(*) Không Điều Kiện — Điểm Mạnh Hiếm Hoi
Một điểm MyISAM vẫn thắng InnoDB: SELECT COUNT(*) FROM table (không WHERE) trả về tức thì, vì MyISAM lưu sẵn tổng số dòng ở metadata của bảng. InnoDB (vì hỗ trợ MVCC — mỗi transaction có thể "thấy" một phiên bản dữ liệu khác nhau) phải quét để đếm chính xác số dòng nhìn thấy được tại thời điểm đó, chậm hơn đáng kể với bảng lớn. Đây là đánh đổi hợp lý: MyISAM lợi ở một phép đếm hiếm khi cần chính xác tuyệt đối theo thời gian thực, để đổi lấy transaction và row-level locking — hầu hết hệ thống hiện đại chấp nhận đánh đổi này.
InnoDB — Tóm Tắt Nhanh (Chi Tiết Đầy Đủ Ở Bài Riêng)
Vì đã có bài riêng về InnoDB, phần này chỉ tóm tắt để so sánh: InnoDB hỗ trợ đầy đủ ACID transaction, row-level locking (chỉ khoá đúng dòng đang sửa, không chặn các dòng khác), MVCC (nhiều transaction đọc/ghi đồng thời mà không chặn nhau), foreign key constraints thực sự được kiểm tra, và crash recovery tự động qua redo log. Đây là lý do InnoDB đã là engine mặc định của MySQL từ bản 5.5 tới nay, và là lựa chọn đúng cho tuyệt đại đa số bảng trong một ứng dụng hiện đại.
Bảng So Sánh Toàn Diện
| Tiêu chí | InnoDB | MyISAM |
|---|---|---|
| Transaction (ACID) | Có đầy đủ | Không có |
| Mức độ khoá | Row-level (chỉ khoá dòng đang sửa) | Table-level (khoá cả bảng khi ghi) |
| Foreign key | Được kiểm tra thực sự | Chấp nhận cú pháp nhưng bỏ qua |
| Crash recovery | Tự động (redo log) | Không — dễ "crashed", cần REPAIR TABLE |
| MVCC (đọc không chặn ghi) | Có | Không |
| Full-text search | Có (từ MySQL 5.6+) | Có (từ rất sớm) |
COUNT(*) không điều kiện |
Phải quét, chậm với bảng lớn | Tức thì (lưu sẵn) |
| Backup online không khoá | Có (--single-transaction) |
Không (cần khoá bảng khi backup) |
| Cấu trúc lưu trữ | Tablespace (.ibd) + buffer pool |
3 file rời (.frm/.MYD/.MYI) |
| Phù hợp ghi đồng thời cao | Rất tốt | Kém — nghẽn ở table lock |
| Phù hợp đọc nhiều, ghi hiếm | Tốt | Tốt, đôi khi nhỉnh hơn với bảng rất đơn giản |
| Chống mất dữ liệu khi crash | Cao | Thấp |
Khi Nào MyISAM Vẫn Là Lựa Chọn Hợp Lý?
Không nên kết luận vội "MyISAM luôn tệ, đừng bao giờ dùng". Có một vài tình huống cụ thể nó vẫn hợp lý:
- Bảng chỉ đọc hoặc gần như chỉ đọc (dữ liệu tham chiếu tĩnh, danh mục đã đóng băng, dữ liệu archive cũ không còn ghi) — table-level lock không phải vấn đề khi không có ai ghi để tranh chấp.
- Bảng log ghi tuần tự bởi đúng 1 tiến trình, không có ghi đồng thời từ nhiều nguồn, và mất một phần dữ liệu log không gây hậu quả nghiêm trọng (khác hẳn với dữ liệu giao dịch).
- Bảng tạm trong pipeline ETL/batch xử lý offline, nơi tốc độ ghi tuần tự thô là ưu tiên và có thể build lại toàn bộ nếu có sự cố.
- Hệ thống MySQL rất cũ (< 5.6) cần full-text search — dù trường hợp này ngày nay gần như không còn tồn tại.
Ngoài các trường hợp trên, mặc định nên chọn InnoDB.
Khi Nào Bắt Buộc Phải Dùng InnoDB
- Bất kỳ dữ liệu nào liên quan đến tiền, đơn hàng, giao dịch — bắt buộc cần ACID.
- Bảng có nhiều client ghi đồng thời (web app nhiều người dùng cùng lúc) — row-level locking là yêu cầu sống còn.
- Cần foreign key để đảm bảo toàn vẹn tham chiếu ở tầng database (không chỉ ở tầng application).
- Hệ thống cần backup online không downtime (
mysqldump --single-transaction, Percona XtraBackup) — MyISAM không hỗ trợ backup nhất quán mà không khoá bảng. - Bất kỳ hệ thống production hiện đại nào không có lý do đặc biệt để chọn khác — đây cũng chính là lý do MySQL đổi engine mặc định sang InnoDB từ 2010.
Chuyển Đổi Một Bảng Từ MyISAM Sang InnoDB
ALTER TABLE legacy_log_table ENGINE = InnoDB;
Vài lưu ý quan trọng trước khi chạy trên production:
- Với bảng lớn,
ALTER TABLE ... ENGINE=viết lại toàn bộ bảng — tốn thời gian và (tuỳ phiên bản MySQL/cấu hình) có thể khoá đọc/ghi trong lúc thực hiện. Với bảng nhiều triệu dòng trên production, nên dùng công cụ hỗ trợ online schema change nhưgh-osthoặcpt-online-schema-changethay vì chạyALTER TABLEtrực tiếp. - Sau khi chuyển, kiểm tra lại các câu query
COUNT(*)không điều kiện trên bảng đó — chúng sẽ chậm hơn hẳn so với khi còn là MyISAM. - Nếu bảng có
FULLTEXT INDEX, xác nhận cú pháp/behavior tìm kiếm vẫn đúng như mong đợi sau khi chuyển (một vài tuỳ chọn cấu hình full-text giữa hai engine có khác biệt nhỏ). - Luôn
ALTER TABLE ... ENGINE=InnoDBtrên môi trường staging với dữ liệu tương đương trước, đo thời gian thực hiện, trước khi chạy trên production.
Case Study: Bảng Log MyISAM Gây Nghẽn Ghi Trên Production
Một tình huống điển hình từng gặp: một bảng activity_log được tạo từ nhiều năm trước với ENGINE=MyISAM — lý do ban đầu đơn giản là "chỉ ghi log, không cần transaction". Nhiều năm sau, traffic tăng, và ứng dụng bắt đầu báo timeout hàng loạt ở các request ghi log (không phải request nghiệp vụ chính!).
Áp dụng đúng playbook ở bài Troubleshooting MySQL:
- Kiểm tra CloudWatch — CPU/Memory bình thường, loại trừ nghẽn hạ tầng.
SHOW FULL PROCESSLIST— hàng loạt session ở trạng tháiState: Locked, tất cả cùng nhắm vào bảngactivity_log.- Đây chính là dấu hiệu đặc trưng của table-level lock: khác với
Waiting for row lock(InnoDB, chỉ chặn ai đụng đúng dòng),Lockedtrên MyISAM nghĩa là mọi câu ghi vào bảng đó đang xếp hàng chờ nhau, bất kể có đụng dữ liệu của nhau hay không. - Xác nhận bằng:
SHOW OPEN TABLES WHERE In_use > 0;
SELECT table_name, engine FROM information_schema.tables
WHERE table_name = 'activity_log';
-- Engine: MyISAM ← xác nhận nguyên nhân
Giải pháp: chuyển bảng sang InnoDB (dùng gh-ost vì bảng đã hàng chục triệu dòng, tránh khoá production trong lúc chuyển). Sau khi chuyển, các request ghi log không còn xếp hàng chờ nhau nữa — mỗi ghi chỉ cần lock đúng dòng nó tạo ra. Đây là một ví dụ rất thực tế cho thấy một quyết định kỹ thuật "hợp lý lúc ban đầu" (MyISAM cho bảng log đơn giản) có thể trở thành nút thắt cổ chai sau nhiều năm hệ thống lớn lên — storage engine nên được xem lại định kỳ, không phải một quyết định "set và quên".
Checklist Chọn Storage Engine
- [ ] Bảng có cần transaction (nhiều thay đổi phải cùng thành công hoặc cùng thất bại)? → InnoDB.
- [ ] Bảng có nhiều client ghi đồng thời? → InnoDB (tránh table-level lock của MyISAM).
- [ ] Bảng cần foreign key được database thực sự kiểm tra? → InnoDB.
- [ ] Dữ liệu có cần sống sót qua một lần server crash bất ngờ? → InnoDB.
- [ ] Bảng chỉ đọc/gần như chỉ đọc, hoặc chỉ 1 tiến trình ghi tuần tự, và mất dữ liệu không nghiêm trọng? → MyISAM có thể chấp nhận được, nhưng InnoDB vẫn là lựa chọn an toàn hơn nếu không có lý do đặc biệt.
- [ ] Đang có bảng MyISAM cũ trong hệ thống? → Kiểm tra lại lý do ban đầu còn đúng không; nếu traffic/quy mô đã thay đổi, cân nhắc chuyển sang InnoDB qua công cụ online schema change.
Kết Luận
Từ MySQL 5.5, câu trả lời mặc định gần như luôn là InnoDB — transaction, row-level locking, crash recovery và foreign key không phải "tính năng cao cấp", mà là yêu cầu cơ bản của phần lớn ứng dụng hiện đại. MyISAM vẫn có chỗ đứng hẹp cho các bảng đọc nhiều/ghi hiếm hoặc pipeline xử lý offline, nhưng không nên là lựa chọn mặc định, và các bảng MyISAM còn sót lại từ hệ thống cũ luôn đáng để xem lại — như case study ở trên cho thấy, một quyết định "vô hại" từ nhiều năm trước hoàn toàn có thể là nguyên nhân của một sự cố production hôm nay.
Để hiểu sâu hơn về cách InnoDB đạt được row-level locking và MVCC, đọc tiếp InnoDB Storage Engine: Hướng dẫn toàn diện; để biết cách chẩn đoán một bảng đang bị table-level lock chặn đứng khi sự cố xảy ra, xem lại Troubleshooting MySQL.
