Một chỉ mục, nhiều cột
Chỉ mục kết hợp (composite index), đôi khi gọi là chỉ mục nhiều cột, là một chỉ mục duy nhất được xây trên hai cột trở lên. Bạn tạo nó bằng cách liệt kê các cột theo thứ tự:
Chỉ mục idx_orders_customer_status lưu các mục được sắp xếp trước tiên theo customer_id, rồi theo status trong từng khách hàng. Thứ tự đó là toàn bộ câu chuyện: mọi điều khác về chỉ mục kết hợp đều bắt nguồn từ đây.
Mô hình dễ hình dung: một cuốn danh bạ đã sắp xếp
Hãy hình dung một cuốn danh bạ điện thoại kiểu cũ. Các mục được sắp theo họ, và trong mỗi họ thì sắp theo tên. Đó chính xác là hình dạng của một chỉ mục trên (last_name, first_name).
Có những lần tra cứu nhanh, có những lần thì không:
- "Tìm mọi người họ Patel": dễ, tất cả người họ Patel nằm cạnh nhau.
- "Tìm Priya Patel": dễ, nhảy tới Patel rồi dò tới Priya.
- "Tìm mọi người tên Priya": chậm, bạn phải lật qua từng trang. Những người tên Priya nằm rải rác ở mọi họ.
Chỉ mục kết hợp trong SQLite hoạt động y như vậy. Cột đầu tiên là khóa sắp xếp chính; cột thứ hai chỉ sắp xếp các mục có cùng giá trị ở cột đầu.
Quy tắc tiền tố bên trái
SQLite chỉ dùng được chỉ mục kết hợp cho một truy vấn khi mệnh đề WHERE ràng buộc một tiền tố bên trái (leftmost prefix) của các cột. Với chỉ mục trên (a, b, c):
- Lọc theo
a: có dùng chỉ mục. - Lọc theo
avàb: có dùng chỉ mục. - Lọc theo
a,bvàc: có dùng chỉ mục. - Chỉ lọc theo
b, hoặc chỉ theoc, hoặc theobvàc: chỉ mục không được dùng.
Bạn có thể kiểm chứng trực tiếp bằng EXPLAIN QUERY PLAN:
Kế hoạch đầu tiên báo SEARCH events USING INDEX idx_events_user_kind_time. Kế hoạch thứ hai quay về SCAN events: chỉ lọc theo kind là bỏ qua cột dẫn đầu user_id, nên chỉ mục vô dụng với truy vấn đó.
Thứ tự cột là một quyết định thiết kế
Vì tiền tố bên trái quan trọng, thứ tự bạn liệt kê cột trong CREATE INDEX là một lựa chọn thật sự, không phải chuyện phong cách. Hai quy tắc kinh nghiệm:
- Đặt cột bạn lọc thường xuyên nhất lên đầu. Cột đó mở khóa chỉ mục cho nhiều loại truy vấn nhất.
- Đặt cột so sánh bằng trước cột so sánh theo khoảng. SQLite có thể đi thẳng vào chỉ mục bằng
=, rồi quét một đoạn liên tục bằng<,>hoặcBETWEEN, nhưng chỉ trên cột được dùng cuối cùng.
Kế hoạch hiển thị SEARCH sales USING INDEX idx_sales_region_time (region=? AND sold_at>?). SQLite nhảy thẳng tới region = 'EU', rồi đi tiếp qua khoảng ngày. Đảo thứ tự cột thành (sold_at, region) thì cùng truy vấn đó phải quét mọi hàng trong khoảng ngày và kiểm tra lại region cho từng hàng.
Chỉ mục kết hợp hay nhiều chỉ mục một cột
Một câu hỏi hay gặp: nên tạo một chỉ mục trên (a, b), hay hai chỉ mục riêng trên a và b?
Với bộ lọc kết hợp, chỉ mục kết hợp nhanh hơn: SQLite đi thẳng tới các mục (project_id, state) khớp. Với hai chỉ mục một cột, SQLite thường chọn một chỉ mục, dùng nó để thu hẹp số hàng, rồi kiểm tra lại cột còn lại trên từng hàng khớp. Đôi khi nó có thể giao hai chỉ mục với nhau, nhưng chỉ mục kết hợp là lời giải gọn hơn khi các cột được truy vấn cùng nhau.
Nếu project_id và state cũng được truy vấn riêng lẻ, có thể bạn cần cả hai: chỉ mục kết hợp cho bộ lọc kết hợp, cộng thêm một chỉ mục một cột trên state cho các truy vấn chỉ lọc theo nó.
Covering index
Khi một chỉ mục chứa mọi cột mà truy vấn cần, cả cột dùng để lọc lẫn cột được chọn, SQLite trả lời được truy vấn mà không cần đụng tới bảng. Đó là covering index, và đó là mức nhanh nhất mà một truy vấn có thể đạt.
Kế hoạch hiển thị USING COVERING INDEX idx_invoices_cover. Truy vấn đọc issued_at và total thẳng từ chỉ mục; không cần notes và id, nên bảng không bao giờ được mở. Thêm một cột vào chỉ mục kết hợp chỉ để bao trọn một truy vấn nóng là một sự đánh đổi đáng giá khi truy vấn đó chạy liên tục.
Ràng buộc UNIQUE trên nhiều cột
Chỉ mục kết hợp còn đảm bảo tính duy nhất trên tổ hợp cột. Rất hữu ích khi không cột nào duy nhất một mình, nhưng tổ hợp của chúng thì phải duy nhất:
Lệnh insert thứ ba gây lỗi UNIQUE constraint failed: enrollments.student_id, enrollments.course_id. Cặp giá trị đó đã có trong chỉ mục, nên SQLite từ chối bản trùng.
Những cái bẫy nên biết
ORgiữa các cột không dẫn đầu chặn chỉ mục.WHERE a = 1 OR b = 2trên chỉ mục(a, b)thường hoàn toàn không dùng được chỉ mục, vì SQLite phải xét riêng từng nhánh.- Hàm áp lên cột có chỉ mục làm vô hiệu chỉ mục.
WHERE lower(email) = 'x'sẽ không dùng chỉ mục trênemail. Thay vào đó hãy tạo chỉ mục trên biểu thức, hoặc chuẩn hóa dữ liệu khi insert. - Chỉ mục không miễn phí. Mỗi chỉ mục được cập nhật ở mọi lệnh
INSERT,UPDATE(trên các cột có chỉ mục) vàDELETE. Ba chỉ mục kết hợp trên một bảng ghi nhiều có thể chiếm phần lớn chi phí ghi. - Chạy
ANALYZEsau khi tạo chỉ mục. Bộ lập kế hoạch của SQLite dùng số liệu thống kê doANALYZEthu thập để chọn giữa các chỉ mục ứng viên. Không có số liệu đó, nó quay về các quy tắc ước lượng không phải lúc nào cũng tối ưu.
Quy trình thực tế
Khi tối ưu một truy vấn chậm, vòng lặp thường như sau:
- Chạy
EXPLAIN QUERY PLANtrên truy vấn để xem SQLite hiện đang làm gì. - Nếu nó đang quét toàn bảng, xem mệnh đề
WHERE: cột so sánh bằng là gì? Cột so sánh khoảng là gì? Những cột nào được chọn? - Tạo chỉ mục kết hợp theo thứ tự cột so sánh bằng trước, cột khoảng sau, thêm các cột được chọn vào cuối nếu covering giúp ích.
- Chạy
ANALYZE. - Chạy lại
EXPLAIN QUERY PLAN. Xác nhận kế hoạch đã thay đổi và chỉ mục đang được dùng. - Đo thời gian truy vấn trước và sau trên dữ liệu tiêu biểu.
Bỏ qua bước 6 là tự chuốc rủi ro. Một chỉ mục trông đúng trong kế hoạch vẫn có thể chậm hơn trong thực tế nếu bảng nhỏ hoặc bộ lập kế hoạch chọn đường khác.
Tiếp theo: chỉ mục một phần
Chỉ mục kết hợp bao trùm mọi hàng trong bảng. Nhưng thường chỉ một tập nhỏ các hàng là quan trọng: ticket đang mở, job chưa xử lý, bản ghi chưa bị xóa. Chỉ mục một phần (partial index) cho phép bạn chỉ đánh chỉ mục những hàng đó, với một mệnh đề WHERE nằm ngay trong chỉ mục. Đó là nội dung của trang tiếp theo.
Câu hỏi thường gặp
Chỉ mục kết hợp trong SQLite là gì?
Chỉ mục kết hợp (composite index) là một chỉ mục duy nhất bao trùm hai cột trở lên. Bạn tạo nó bằng CREATE INDEX idx_name ON table(col_a, col_b). SQLite lưu các mục được sắp xếp theo col_a trước, rồi theo col_b trong từng giá trị col_a, giống một cuốn danh bạ sắp theo họ, rồi theo tên.
Thứ tự cột có quan trọng trong chỉ mục kết hợp của SQLite không?
Có, rất quan trọng. SQLite chỉ dùng được chỉ mục kết hợp cho một truy vấn khi mệnh đề WHERE lọc trên một tiền tố bên trái của các cột trong chỉ mục. Chỉ mục trên (a, b, c) giúp các truy vấn lọc theo a, theo a và b, hoặc theo cả ba, nhưng không giúp được truy vấn chỉ lọc theo b hoặc chỉ theo c.
Khi nào nên dùng chỉ mục kết hợp thay vì nhiều chỉ mục một cột?
Dùng chỉ mục kết hợp khi các truy vấn thường xuyên lọc hoặc sắp xếp trên cùng một tổ hợp cột. Các chỉ mục một cột riêng lẻ phù hợp khi từng cột được truy vấn độc lập. Chạy EXPLAIN QUERY PLAN để xem SQLite thực sự chọn chỉ mục nào: đó là phản hồi đáng tin cậy duy nhất.
Covering index trong SQLite là gì?
Covering index chứa mọi cột mà truy vấn cần, nên SQLite trả lời truy vấn ngay từ chỉ mục mà không phải đụng tới bảng. Khi điều này xảy ra, EXPLAIN QUERY PLAN hiển thị USING COVERING INDEX. Thêm cột vào chỉ mục kết hợp chỉ để bao trọn một truy vấn chạy thường xuyên là cách tối ưu phổ biến.