Một câu lệnh SQL cứu cả ca trực nghẽn dữ liệu
Câu chuyện tối ưu hoá truy vấn Deadlock và missing index giúp giảm 90% tải Database trong giờ cao điểm tiếp đón.

7 giờ 45 sáng thứ Hai. Khung giờ “vàng” mà bất kỳ nhân viên IT bệnh viện nào cũng nín thở.
Hàng trăm bệnh nhân đang xếp hàng lấy số và đăng ký khám. Đột nhiên, màn hình quầy tiếp đón quay vòng tròn: “Đang tải dữ liệu tiếp nhận…”. Một giây chậm trễ là hàng chục người đợi, tiếng thở dài bắt đầu xuất hiện ngoài sảnh.
Triệu chứng: CPU máy chủ Database chạm ngưỡng 99%
Khi tôi mở trình giám sát Database Server (SQL Server), biểu đồ CPU gần như dựng đứng một đường thẳng ở mốc 99%.
Các tiến trình sp_who2 đổ về liên tục hàng loạt lệnh SELECT và UPDATE ở trạng thái LCK_M_U (Lock Update) và CXPACKET. Các session thi nhau tranh chấp tài nguyên và xảy ra hiện tượng dây chuyền (Cascade Blocking).
-- Kiểm tra các session đang chặn nhau (Blocking Sessions)
SELECT
blocking_session_id AS Blocker,
session_id AS Victim,
wait_type,
wait_time / 1000 AS Wait_Seconds,
wait_resource
FROM sys.dm_exec_requests
WHERE blocking_session_id <> 0;
Kết quả: Hơn 40 kết nối đang xếp hàng đợi duy nhất một tiến trình đang chạy câu truy vấn tìm kiếm lịch sử khám bệnh.
Thủ phạm: Quét bảng toàn bộ (Table Scan) với LIKE ‘%…%’
Khi trích xuất câu lệnh SQL đang chiếm giữ bảng, câu lệnh trông đơn giản như sau:
-- Câu lệnh gây tắc nghẽn toàn bộ hệ thống
SELECT PatientID, FullName, BirthYear, MedicalRecordNo
FROM Patients WITH (NOLOCK) -- Nhiều người tưởng NOLOCK là thần dược
WHERE PatientCode LIKE '%' + @SearchCode + '%'
AND ReceptionDate >= CAST(GETDATE() AS DATE);
Bảng Patients có hơn 3 triệu dòng dữ liệu.
- Việc dùng cú pháp
LIKE '%...%'với dấu%ở đầu khiến hệ thống bắt buộc phải Index Scan / Table Scan toàn bộ 3 triệu dòng. - Càng nghiêm trọng hơn, trường
ReceptionDatekhông có index kết hợp, khiến mỗi lần một nhân viên gõ tìm kiếm, SQL Server phải đọc hàng chục Gigabyte dữ liệu từ ổ đĩa vào bộ nhớ đệm.
Câu lệnh “cứu nguy” chỉ trong 30 giây
Trong lúc hàng người vẫn đang chờ, không thể ngồi viết lại toàn bộ code phần mềm. Giải pháp nhanh nhất là can thiệp cấu trúc dữ liệu ngay tại tầng DBMS:
- Tạo ngay một Filtered Index có bao gồm (Included Columns) cho các ca khám trong ngày hiện tại.
-- Tạo Index có điều kiện lọc giúp giảm từ 3 triệu dòng quét xuống còn vài nghìn dòng
CREATE NONCLUSTERED INDEX IX_Patients_TodayActive
ON dbo.Patients (PatientCode, ReceptionDate)
INCLUDE (FullName, BirthYear, MedicalRecordNo)
WHERE ReceptionDate >= '2026-01-01'
WITH (ONLINE = ON, MAXDOP = 4);
Tại sao câu lệnh này hiệu quả?
- Sử dụng
WITH (ONLINE = ON)giúp bảng không bị lock cứng trong lúc xây index, tiếp nhận bệnh nhân vẫn diễn ra bình thường.WHERElọc phạm vi giúp index nhẹ chỉ vài megabytes, tạo xong trong chưa đầy 20 giây.- Cung cấp đủ các cột trong
INCLUDEgiúp tối ưu hóa thành Index Seek trọn vẹn.
Kết quả kinh ngạc
Ngay sau khi lệnh tạo index chạy xong:
- CPU máy chủ lập tức rơi tự do từ 99% xuống còn 14%.
- Thời gian tìm kiếm hồ sơ từ 8.4 giây giảm xuống 0.08 giây.
- Màn hình tiếp đón trở lại mượt mà, quầy tiếp nhận giải tỏa ùn ứ.
Đúc kết nghề
Làm việc với cơ sở dữ liệu hàng triệu dòng, viết đúng cú pháp thôi là chưa đủ. Bạn cần hiểu cách Query Optimizer vận hành, cách công cụ lưu trữ đọc từng trang dữ liệu (Data Pages), để một câu lệnh cứu cả một buổi sáng bão táp.