- Tác giả

- Name
- Nguyễn Đức Xinh
- Ngày xuất bản
- Ngày xuất bản
Troubleshooting MySQL: Xử Lý Sự Cố Database Từ Cơ Bản Đến Nâng Cao
Giới Thiệu
3 giờ sáng, điện thoại rung: "API đang timeout hàng loạt, khách hàng không đặt được đơn." Bạn SSH vào server, mở MySQL client lên — và giờ thì sao? Đây chính là lúc kỹ năng troubleshooting khác hẳn với việc "biết SQL" hay "biết index". Troubleshooting là khả năng, trong vài phút, trả lời được: vấn đề nằm ở hạ tầng hay ở database, đang có bao nhiêu kết nối, ai đang chạy gì, query nào đang chặn ai, và mình có nên KILL nó không.
Bài viết này là một playbook xử lý sự cố theo từng bước, đi từ cơ bản (nhìn metric hạ tầng CPU/Memory trước, đếm connection, đọc process list) đến nâng cao (phân biệt query chậm với query bị khoá, đọc deadlock, dùng đúng lệnh FLUSH). Nếu bạn chưa đọc, hai bài trước trong khoá học sẽ hữu ích để hiểu sâu hơn phần "vì sao": Tối ưu query MySQL với Index (cách sửa tận gốc một query chậm) và MySQL Monitoring (cách giám sát chủ động, trước khi sự cố xảy ra). Bài này tập trung vào tình huống ngược lại: sự cố đã xảy ra rồi, cần xử lý ngay bây giờ.
Troubleshooting Khác Gì Monitoring?
Một phân biệt quan trọng trước khi bắt đầu:
| Monitoring | Troubleshooting | |
|---|---|---|
| Thời điểm | Liên tục, chủ động | Khi sự cố đã xảy ra |
| Mục tiêu | Phát hiện xu hướng, cảnh báo sớm | Xác định nguyên nhân ngay lập tức và khôi phục dịch vụ |
| Công cụ chính | Dashboard, Prometheus/Grafana, slow query log tổng hợp | SHOW PROCESSLIST, EXPLAIN ANALYZE, SHOW ENGINE INNODB STATUS trực tiếp |
| Câu hỏi điển hình | "Xu hướng connection tuần này thế nào?" | "Ngay lúc này ai đang chặn ai?" |
Nói cách khác: monitoring là radar, troubleshooting là phòng cấp cứu. Bài này là quy trình cho phòng cấp cứu.
Bước 1: Vấn Đề Nằm Ở Hạ Tầng Hay Ở Database?
Trước khi gõ bất kỳ câu lệnh SQL nào, câu hỏi đầu tiên phải trả lời là: MySQL đang chậm vì chính nó đang xử lý một khối lượng công việc lớn, hay vì server/instance bên dưới nó đã hết tài nguyên? Đây là bước rất hay bị bỏ qua — nhiều người debug thẳng vào process list và query, trong khi nguyên nhân thật sự là instance đang bị nghẽn CPU hoặc hết bộ nhớ, mọi query nhìn "chậm" chỉ vì tất cả đang tranh nhau tài nguyên vật lý.
Với AWS RDS/Aurora: Nhìn CloudWatch Trước Tiên
Vào RDS Console → chọn instance → tab Monitoring, hoặc dùng AWS CLI, kiểm tra 6 metric quan trọng nhất:
aws cloudwatch get-metric-statistics \
--namespace AWS/RDS \
--metric-name CPUUtilization \
--dimensions Name=DBInstanceIdentifier,Value=my-db-instance \
--start-time "$(date -u -d '30 minutes ago' +%Y-%m-%dT%H:%M:%S)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%S)" \
--period 60 --statistics Average Maximum
| Metric | Ngưỡng đáng lo ngại | Ý nghĩa nếu vượt ngưỡng |
|---|---|---|
CPUUtilization |
Duy trì > 80-90% | CPU là nút thắt — có thể do quá nhiều query nặng chạy song song, không nhất thiết do 1 query cụ thể |
FreeableMemory |
Giảm liên tục, gần 0 | Sắp/đang phải dùng swap — cực kỳ chậm với database, cần xử lý ngay |
SwapUsage |
> 0 và tăng dần | Dấu hiệu nghiêm trọng nhất — RDS đang swap RAM ra đĩa, hiệu năng có thể giảm hàng chục lần so với bình thường |
ReadIOPS/WriteIOPS |
Chạm giới hạn của instance/loại đĩa | Bị I/O throttling — AWS giới hạn tốc độ đọc/ghi, mọi query đều chậm dù index đã đúng |
DiskQueueDepth |
> 1-2 kéo dài | Disk đang là nút thắt, query phải xếp hàng chờ I/O |
DatabaseConnections |
Gần max_connections |
Khớp với những gì sẽ kiểm tra chi tiết ở Bước 2 |
FreeStorageSpace |
Gần 0 | InnoDB có thể từ chối ghi khi hết dung lượng — hậu quả nghiêm trọng hơn cả chậm |
ReplicaLag (nếu có read replica) |
Tăng dần, không giảm | App đọc từ replica có thể thấy dữ liệu cũ, hoặc replica đang không theo kịp write |
RDS Performance Insights — Xem "Ai/Cái Gì" Đang Chiếm CPU
Nếu instance đã bật Performance Insights (miễn phí 7 ngày dữ liệu gần nhất, không cần cấu hình thêm), đây là công cụ nhanh nhất để trả lời "CPU cao là do query nào" mà không cần tự chạy SHOW PROCESSLIST liên tục — biểu đồ DB Load chia theo Wait state (đang chờ CPU, đang chờ I/O, đang chờ lock...) và theo SQL digest (nhóm theo dạng câu query), giúp bạn thấy ngay câu query nào đang chiếm phần lớn tải mà không cần đoán.
Với MySQL Tự Host (Không Qua RDS)
Không có CloudWatch thì dùng công cụ OS-level tương đương:
top -o %CPU # CPU tổng thể, xem process mysqld đang chiếm bao nhiêu %
free -h # RAM còn trống, cột "available" quan trọng hơn "free"
iostat -x 2 5 # %util của disk — gần 100% nghĩa là disk đã bão hoà
vmstat 2 5 # cột "si"/"so" (swap in/out) khác 0 liên tục = đang swap, rất xấu
Quy Tắc Rẽ Nhánh
- CPU/Memory/Disk đều bình thường, nhưng một vài query cụ thể chậm/treo → vấn đề nằm ở tầng database/query — tiếp tục sang Bước 2 trở đi trong bài này.
- CPU/Memory/Disk đã bão hoà trên diện rộng (không phải 1 query, mà mọi thứ đều chậm) → vấn đề nằm ở tầng hạ tầng — hướng xử lý là scale up instance (đổi instance class lớn hơn), scale out (thêm read replica để giảm tải đọc), hoặc giảm số lượng job/request chạy đồng thời — việc tối ưu từng câu SQL lúc này chỉ là giảm nhẹ triệu chứng, không giải quyết được gốc rễ nếu tải tổng thể đã vượt quá năng lực instance hiện tại.
- Cả hai cùng lúc (thường gặp nhất trong thực tế): một query tệ (thiếu index, quét toàn bảng) là nguyên nhân, và hạ tầng bị bão hoà là hậu quả. Trường hợp này vẫn cần đi tiếp các bước dưới đây để tìm ra chính câu query đó.
Bước 2: Có Bao Nhiêu Connection Đang Mở?
Câu hỏi đầu tiên khi nghi ngờ có sự cố: hệ thống có đang bị ngộp kết nối không.
SHOW STATUS WHERE Variable_name IN
('Threads_connected', 'Threads_running', 'Max_used_connections');
SHOW VARIABLES LIKE 'max_connections';
Threads_connectedgần bằngmax_connections→ nguy cơ lỗiToo many connectionssắp xảy ra hoặc đã xảy ra.Threads_running(số thread đang thực sự thực thi, không tính "Sleep") cao bất thường → CPU/IO đang bị dồn nén thật sự, không chỉ là kết nối idle.
Connection "Sleep" Hàng Loạt — Dấu Hiệu Connection Leak
SELECT user, host, count(*) AS total,
SUM(command = 'Sleep') AS sleeping
FROM information_schema.processlist
GROUP BY user, host
ORDER BY total DESC;
Nếu một user/host có hàng trăm connection ở trạng thái Sleep cùng lúc, gần như chắc chắn là connection pool phía application đang bị leak (không trả connection về pool sau khi dùng xong, hoặc pool size cấu hình sai). Đây là lỗi ở tầng application, không phải ở MySQL — troubleshooting DB chỉ giúp bạn phát hiện ra, không tự sửa được.
Lỗi "Too Many Connections" — Xử Lý Khẩn Cấp
Khi đã bị lỗi này, bạn không thể kết nối thêm để chẩn đoán — đây là lúc tài khoản mysql cần một cơ chế dự phòng:
# MySQL luôn dành riêng 1 kết nối cho user có quyền SUPER/CONNECTION_ADMIN,
# ngoài giới hạn max_connections — dùng chính user quản trị của bạn để vào lúc này.
mysql -u root -p
Sau khi vào được, tăng tạm giới hạn (không cần restart) để "câu giờ" trong lúc tìm nguyên nhân thật sự:
SET GLOBAL max_connections = 500;
Lưu ý: đây chỉ là biện pháp tạm thời để hệ thống thở được — nguyên nhân thật sự (leak connection, traffic tăng đột biến, hay thiếu connection pooling) vẫn phải điều tra tiếp bằng Bước 2.
Bước 3: Ai Đang Làm Gì? Đọc Process List Đúng Cách
SELECT id, user, host, db, command, time, state, info
FROM information_schema.processlist
WHERE command != 'Sleep'
ORDER BY time DESC;
Cột quan trọng nhất — và hay bị bỏ qua nhất — là State. Đây là bảng tra cứu nhanh các giá trị State thường gặp nhất và ý nghĩa thực sự của chúng:
State |
Ý nghĩa | Hành động gợi ý |
|---|---|---|
Sending data |
Đang đọc/gửi dữ liệu (dù tên gọi gây hiểu lầm, đây là lúc query đang quét bảng) | Query chậm thật sự — dùng EXPLAIN |
Copying to tmp table |
Đang tạo bảng tạm (do GROUP BY/ORDER BY/DISTINCT không dùng được index) |
Xem lại index cho cột GROUP BY/ORDER BY |
Sorting result |
Đang filesort | Tương tự — thiếu index đúng thứ tự |
Waiting for table metadata lock |
Đang chờ một DDL (ví dụ ALTER TABLE) hoặc một transaction khác đang giữ khoá |
Tìm ai đang giữ metadata lock (thường là 1 transaction quên COMMIT) |
Locked / Waiting for lock |
Đang chờ row lock từ transaction khác | Tìm blocking transaction (xem phần dưới) |
Sending to client |
Đã xử lý xong, đang gửi kết quả về | Thường vô hại, trừ khi tồn tại rất lâu (network chậm) |
Opening tables |
Đang chờ mở bảng — nếu kéo dài, thường do một session khác giữ lock ở tầng bảng | Kiểm tra SHOW OPEN TABLES WHERE In_use > 0 |
Nguyên tắc vàng: đừng nhìn cột Info (câu SQL) trước — hãy nhìn State trước. Hai query giống hệt nhau về SQL nhưng một cái State = Sending data (đang quét thật) và một cái State = Waiting for table metadata lock (đang chờ, không tốn CPU) cần hai hướng xử lý hoàn toàn khác nhau.
Bước 4: Query Đang "Treo" — Chậm Thật Hay Đang Chờ Lock?
Đây là phân biệt quan trọng nhất trong troubleshooting mà người mới hay nhầm.
- Chậm thật (
Sending data,timetăng dần đều): MySQL đang làm việc, chỉ là chậm — có thể do thiếu index, quét quá nhiều dòng (xem lại bài Tối ưu query với Index). - Bị treo do lock (
Waiting for ...lock,timecũng tăng nhưng CPU của MySQL server lại không tăng tương ứng): query không đang làm việc — nó đang xếp hàng chờ một session khác nhả khoá.
Tìm Blocking Transaction — Ai Đang Giữ Khoá Khiến Người Khác Phải Chờ
-- MySQL 8.0 / MariaDB 10.6+
SELECT
r.trx_id AS waiting_trx,
r.trx_mysql_thread_id AS waiting_thread,
r.trx_query AS waiting_query,
b.trx_id AS blocking_trx,
b.trx_mysql_thread_id AS blocking_thread,
b.trx_query AS blocking_query,
TIMESTAMPDIFF(SECOND, b.trx_started, NOW()) AS blocking_since_seconds
FROM performance_schema.data_lock_waits w
JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_engine_transaction_id
JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_engine_transaction_id;
Kết quả cho bạn chính xác: thread nào đang chờ, và thread nào đang chặn nó. Rất thường gặp: blocking_query trả về rỗng (không có câu SQL nào đang chạy) — đây là dấu hiệu kinh điển của một transaction đã BEGIN từ lâu, chạy xong vài câu lệnh rồi quên COMMIT/ROLLBACK (application giữ transaction mở, ví dụ đang chờ gọi một API bên ngoài giữa transaction). Nó không làm gì cả, nhưng vẫn giữ khoá — chặn mọi transaction khác muốn sửa cùng dòng dữ liệu.
KILL QUERY vs KILL CONNECTION — Dùng Cái Nào?
-- Chỉ dừng câu query đang chạy, giữ nguyên kết nối và mọi session variable
KILL QUERY 12345;
-- Ngắt hẳn kết nối (kéo theo dừng query, rollback transaction dở dang nếu có)
KILL CONNECTION 12345;
-- hoặc rút gọn:
KILL 12345;
Quy tắc chọn lệnh:
- Nếu blocking session đang giữ transaction treo không làm gì (như ví dụ trên) → dùng
KILL(hoặcKILL CONNECTION) để buộc rollback và nhả khoá hoàn toàn. - Nếu bạn chỉ muốn dừng một câu query đang chạy quá lâu nhưng vẫn muốn giữ session/connection đó (ví dụ một session tool đang mở) → dùng
KILL QUERY. - Với application dùng connection pool: gần như luôn nên dùng
KILL(không phảiKILL QUERY) — vì connection đó, sau khi bị buộc dừng giữa chừng, thường ở trạng thái application không lường trước được; ngắt hẳn để pool tự tạo connection mới sạch sẽ hơn.
Rủi Ro Khi KILL — Không Phải Lúc Nào Cũng Tức Thì
Một hiểu lầm phổ biến: KILL không dừng ngay lập tức. Với InnoDB, nếu transaction đang rollback một khối lượng thay đổi lớn (ví dụ một UPDATE hàng triệu dòng bị kill giữa chừng), quá trình rollback đó còn tốn thời gian hơn cả bản thân câu lệnh — vì InnoDB phải hoàn tác từng dòng đã sửa. Trong lúc đó, session vẫn hiện diện với Command: Killed, và vẫn giữ lock cho tới khi rollback xong. Đây là lý do troubleshooting thực chiến luôn đi kèm việc theo dõi tiếp sau khi kill, không phải kill xong là coi như xong việc.
Case Study: Từ "API Bị Treo" Đến Tìm Ra Root Cause
Đây là đúng quy trình đã dùng trong một sự cố thực tế — một API export dữ liệu (bulk export, không phân trang) bắt đầu timeout sau nhiều tháng chạy ổn định. CloudWatch (Bước 1) được kiểm tra trước tiên: CPU, Memory, IOPS của RDS instance đều bình thường — loại trừ ngay khả năng nghẽn hạ tầng, vấn đề chắc chắn nằm ở tầng query.
Xác nhận có đang có query chạy lâu không:
SELECT id, time, state, LEFT(info, 100) AS query_preview
FROM information_schema.processlist
WHERE command != 'Sleep' AND time > 5
ORDER BY time DESC;
Kết quả: một session Id = 8842, time = 11, State = Sending data — đang chạy thật (không phải chờ lock), đã 11 giây. Loại trừ ngay khả năng "bị chặn bởi transaction khác" — vấn đề nằm ở chính câu query.
Không đoán, đo bằng EXPLAIN ANALYZE (hoặc ANALYZE FORMAT=JSON trên MariaDB) đúng câu SQL đang chạy (lấy từ Info/SHOW FULL PROCESSLIST):
Kết quả lộ ra một DEPENDENT SUBQUERY chạy lại ~19.600 lần, mỗi lần đọc dư hàng chục dòng vì thiếu composite index đúng cột — chiếm 97% tổng thời gian query. Đây chính xác là case study đã phân tích chi tiết ở bài Tối ưu query MySQL với Index: quy trình sửa tận gốc (thêm đúng composite index) khiến câu query giảm từ 11 giây xuống dưới 2 giây.
Nhưng nếu sự cố đang diễn ra NGAY LÚC NÀY, chưa kịp deploy fix thì sao? Đây là phần troubleshooting thuần tuý, khác với tối ưu: nếu request đó đang chặn các request khác (ví dụ chiếm hết connection pool, hoặc đang giữ lock không cần thiết trên bảng đang export), bạn có hai lựa chọn ứng cứu tạm thời trong lúc chờ deploy fix:
-- Dừng ngay request export đang treo, trả connection lại cho pool
KILL 8842;
hoặc — nếu vấn đề là do quá nhiều request export đồng thời — giới hạn tạm thời số connection của riêng user/API đó:
ALTER USER 'export_service'@'%' WITH MAX_USER_CONNECTIONS 2;
Đây chính là điểm khác biệt cốt lõi giữa troubleshooting và tối ưu: troubleshooting là hành động ngay để hệ thống thở được; sửa tận gốc (thêm index, viết lại query) là bước tiếp theo, thường cần deploy nên không thể tức thời.
Bước 5: Slow Query Log — Tìm Query Chậm Đã/Đang Xảy Ra Trên Diện Rộng
SHOW PROCESSLIST chỉ cho bạn thấy những gì đang chạy ngay lúc bạn nhìn vào — nếu sự cố chập chờn (query chậm rồi tự hết), bạn cần dữ liệu lịch sử:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2; -- ghi log mọi query chạy quá 2 giây
Nếu server đang dùng MySQL 5.6+ và ghi slow log vào bảng (log_output = 'TABLE'):
SELECT start_time, query_time, rows_examined, sql_text
FROM mysql.slow_log
ORDER BY start_time DESC
LIMIT 20;
Với Performance Schema, bạn có thể xem thống kê tổng hợp theo "hình dạng" query (đã bỏ giá trị cụ thể, gom các lần gọi giống nhau lại) — rất hữu ích để tìm ra query nào đang chạy nhiều nhất và tốn thời gian tổng cộng nhiều nhất, không chỉ một lần chạy chậm đơn lẻ:
SELECT DIGEST_TEXT AS query_pattern,
COUNT_STAR AS exec_count,
SUM_TIMER_WAIT/1000000000000 AS total_seconds,
AVG_TIMER_WAIT/1000000000 AS avg_ms
FROM performance_schema.events_statements_summary_by_digest
ORDER BY total_seconds DESC
LIMIT 10;
Đây là câu hỏi khác với "query nào đang chạy lâu nhất ngay lúc này" — nó trả lời "query nào đang âm thầm ăn nhiều tài nguyên nhất cộng dồn", thường là thủ phạm thật sự đứng sau một hệ thống "cứ chậm dần đều" mà không có một request đơn lẻ nào trông có vẻ bất thường.
Bước 6: FLUSH — Dùng Đúng Lệnh, Đúng Lúc
FLUSH là nhóm lệnh hay bị dùng sai nhất trong lúc hoảng loạn xử lý sự cố — vì tên gọi nghe như "reset mọi thứ", nhưng mỗi lệnh FLUSH làm một việc rất cụ thể và khác nhau.
| Lệnh | Tác dụng thật sự | Khi nào dùng |
|---|---|---|
FLUSH TABLES |
Đóng mọi bảng đang mở, ghi các thay đổi MyISAM còn treo xuống đĩa | Sau khi sửa file trực tiếp ngoài MySQL (hiếm khi cần với InnoDB) |
FLUSH TABLES WITH READ LOCK |
Khoá ghi toàn bộ server, dùng để lấy backup nhất quán | Trước khi chạy backup thủ công không dùng công cụ hỗ trợ online backup |
FLUSH LOGS |
Đóng binlog/error log hiện tại, mở file log mới | Khi binlog đã quá lớn, cần xoay vòng log theo lịch |
FLUSH HOSTS |
Xoá cache lỗi kết nối, gỡ trạng thái "host bị chặn" | Khi gặp lỗi Host '...' is blocked (xem bảng lỗi bên dưới) |
FLUSH PRIVILEGES |
Nạp lại bảng phân quyền (mysql.user,...) từ đĩa |
Sau khi sửa quyền trực tiếp bằng INSERT/UPDATE vào bảng hệ thống (không dùng GRANT/CREATE USER) |
RESET QUERY CACHE / FLUSH QUERY CACHE |
Dọn query cache (MySQL cũ, đã bỏ từ 8.0) | Không còn áp dụng cho MySQL 8.0+/MariaDB mới |
Cảnh báo quan trọng nhất bài này: FLUSH TABLES WITH READ LOCK khoá ghi toàn bộ server, không chỉ một bảng. Chạy lệnh này trên production mà quên UNLOCK TABLES ngay sau khi lấy xong snapshot cần thiết là một trong những nguyên nhân gây downtime "tự gây ra" phổ biến nhất khi troubleshooting trong hoảng loạn. Luôn chuẩn bị sẵn câu lệnh UNLOCK TABLES; trước khi gõ FLUSH TABLES WITH READ LOCK, và cân nhắc dùng công cụ backup online (Percona XtraBackup, mysqldump --single-transaction) thay vì khoá tay trong hầu hết trường hợp.
Bước 7: Deadlock — Đọc SHOW ENGINE INNODB STATUS
Khác với "bị treo chờ lock" (một session chờ, các session khác vẫn chạy bình thường), deadlock là khi hai (hoặc nhiều) transaction chờ lẫn nhau theo vòng tròn — không transaction nào có thể tiếp tục. InnoDB tự phát hiện và tự động rollback một trong hai (transaction "rẻ" hơn để rollback), nhưng ứng dụng của bạn cần biết để retry đúng cách.
SHOW ENGINE INNODB STATUS\G
Tìm phần LATEST DETECTED DEADLOCK trong kết quả — nó liệt kê chính xác: transaction nào giữ lock nào, transaction nào đang chờ lock nào, và transaction nào bị chọn làm "victim" (bị rollback). Đọc kỹ hai câu SQL của cả hai transaction — deadlock kinh điển nhất là hai transaction cùng update hai dòng theo thứ tự ngược nhau (transaction A: sửa dòng 1 rồi dòng 2; transaction B: sửa dòng 2 rồi dòng 1). Cách phòng deadlock loại này ở tầng application: luôn sửa các dòng liên quan theo cùng một thứ tự cố định (ví dụ luôn sort theo khoá chính trước khi update hàng loạt).
Giải Mã Nhanh Các Lỗi Thường Gặp
| Thông báo lỗi | Nguyên nhân | Hướng xử lý |
|---|---|---|
Too many connections |
Threads_connected chạm max_connections |
Vào bằng user quản trị, tăng tạm max_connections, tìm connection leak (Bước 1) |
Lock wait timeout exceeded; try restarting transaction |
Chờ row lock quá innodb_lock_wait_timeout (mặc định 50s) |
Tìm blocking transaction (Bước 3), xem có cần KILL không |
Deadlock found when trying to get lock |
Hai transaction chờ vòng tròn | Đọc SHOW ENGINE INNODB STATUS, sửa thứ tự update ở application, thêm retry logic |
MySQL server has gone away |
Kết nối bị đóng — do timeout (wait_timeout), gói tin quá lớn (max_allowed_packet), hoặc server bị restart |
Kiểm tra max_allowed_packet nếu query/payload lớn; kiểm tra kết nối có bị idle quá lâu không |
Host '...' is blocked because of many connection errors |
Quá nhiều lần kết nối lỗi liên tiếp từ 1 host, MySQL tự chặn (max_connect_errors) |
FLUSH HOSTS để gỡ chặn, đồng thời tìm lý do gây lỗi kết nối liên tục (sai password, network chập chờn) |
Table '...' is marked as crashed |
Bảng MyISAM bị lỗi (hiếm gặp với InnoDB) | CHECK TABLE ..., sau đó REPAIR TABLE ... |
Out of sort memory |
sort_buffer_size không đủ cho một ORDER BY/GROUP BY lớn |
Xem lại xem query có nên tối ưu bằng index thay vì sort trong bộ nhớ (xem bài Index) |
Checklist Ứng Cứu Sự Cố
Khi có báo động "database chậm/treo", đi theo đúng thứ tự này:
- [ ] Hạ tầng: CloudWatch/Performance Insights (hoặc
top/free/iostatnếu tự host) — CPU, Memory, Swap, IOPS có đang bão hoà không? Nếu có → hướng xử lý là scale, không phải tối ưu 1 query. - [ ] Connection:
Threads_connectedso vớimax_connections— có đang ngộp không? - [ ] Process list: Có query nào
command != 'Sleep'chạy quá vài giây không?Statecủa nó là gì? - [ ] Phân loại: Đang chạy thật (
Sending data) hay đang chờ lock (Waiting for ... lock)? - [ ] Nếu chờ lock → tìm blocking transaction, quyết định
KILLhay chờ. - [ ] Nếu chạy thật và chậm →
EXPLAIN ANALYZEcâu query đó ngay, đừng đoán. - [ ] Nếu cần dừng ngay để cứu hệ thống →
KILL(không phảiKILL QUERY) để giải phóng connection hoàn toàn, rồi theo dõi tiếp vì rollback có thể không tức thì. - [ ] Ghi lại toàn bộ query/EXPLAIN/thời gian trước khi kill — đây là dữ liệu duy nhất bạn có để tìm root cause sau khi hệ thống đã ổn định trở lại.
- [ ] Sau khi qua cơn nguy cấp: xử lý tận gốc (thêm index, sửa query, sửa logic transaction ở application) — không dừng lại ở bước "đã hết đỏ".
Kết Luận
Troubleshooting MySQL không phải là một tập hợp lệnh cần học thuộc, mà là một quy trình chẩn đoán có thứ tự: đếm connection → đọc process list → phân biệt "chạy thật" với "đang chờ" → xác định blocking transaction nếu có → quyết định hành động (KILL, FLUSH, chờ) dựa trên bằng chứng, không dựa trên phỏng đoán. Khác với tối ưu query (có thể làm thong thả, đo đi đo lại), troubleshooting đòi hỏi ra quyết định nhanh với thông tin chưa đầy đủ — nhưng quy trình đúng thứ tự ở trên vẫn giúp bạn ra quyết định nhanh mà không đoán mò.
Sau khi hệ thống ổn định trở lại, đừng dừng ở đó — quay lại bài Tối ưu query MySQL với Index để sửa tận gốc nguyên nhân, và thiết lập lại giám sát chủ động để lần sau phát hiện sớm hơn, trước khi nó trở thành một cuộc gọi lúc 3 giờ sáng.
