Vì sao WHERE YEAR(created_at) = 2026 không dùng được index?
Đáp án mẫu
Triệu chứng: query quét toàn bộ bảng dù created_at đã có index.
Nguyên nhân: B-tree index được sắp xếp theo giá trị lưu của created_at. Khi điều kiện bọc cột trong một hàm, database phải tính YEAR() cho từng dòng để so sánh, nên không seek vào index được. Predicate này không sargable (không dùng được làm search argument).
Cách sửa: so sánh cột gốc với một khoảng giá trị và không đặt hàm ở phía cột.
-- slow: function on the column
SELECT * FROM orders WHERE YEAR(created_at) = 2026;
-- fast: range on the raw column
SELECT * FROM orders
WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';
Bẫy tương tự xuất hiện với LOWER(email) = '[email protected]', price * 2 > 100 (hãy viết price > 50), ép kiểu ngầm trên cột, và LIKE '%abc' có wildcard đứng đầu.
Khi không sửa được query, hãy thêm expression index (PostgreSQL) hoặc generated column có index (MySQL) để lưu giá trị đã tính.
Quy tắc nhớ: để cột có index đứng một mình ở một vế của phép so sánh.
Ý chính
- Hàm bọc cột có index chặn việc seek index
- Viết lại thành khoảng giá trị trên cột gốc
- Expression index hoặc generated column là phương án dự phòng
- Wildcard đứng đầu và ép kiểu ngầm gây vấn đề tương tự
Câu hỏi nối tiếp thường gặp
- Bạn lọc theo một ngày với cột timestamp như thế nào?
Lỗi thường gặp
- Thêm index thay vì viết lại predicate
- Dùng BETWEEN với ngày kết thúc inclusive trên cột timestamp