Hướng dẫn dùng multi-index trong PHP

Câu này slow query, hệ thống response chậm quá,... tìm nguyên nhân đi em, cải thiện tốc độ truy vấn này đi... Đó là những gì tôi nhận được văng vẳng bên tai của a PM. Việc đầu tiên là explain câu query đó ra xem như thế nào đã, và giải pháp đầu tiên tôi nghĩ đến là đánh index. Hãy nói mind set của bạn giống tôi đi. Ok, chủ đề hôm nay là Index trong Database, let's go!...

Đặt vấn đề

Giả sử bây giờ tôi có 1 table "ex" chứa thông tin người yêu cũ bao gồm: tên, tuổi, quê quán, sđt, facebook, số đo ba vòng... và bảng này cỡ khoảng triệu records đi =]]] Đến một ngày đẹp trời, tự dưng muốn tìm lại info em Xoan chẳng hạn.

Select * from ex where name = "Xoan"

Lúc này hệ thống phải đọc ra từng row trong bảng "ex", so sánh từ đầu đến cuối, quả là một cách làm tệ và chậm chạp nhất. Vậy làm sao để tăng tốc độ tìm kiếm em Xoan, đi thẳng vào vấn đề đó chính là làm thế nào để giảm được số lượng bản ghi khi truy vấn. Xong, index chắc rồi, vậy....

Index là gì

index là việc cấu trúc dữ liệu, lưu trữ theo một cơ chế chuyên biệt để tìm ra các record một cách nhanh chóng. Ví Index đơn giản như là mục lục của một quyển sách, khi cần tìm một chủ đề, bài viết nào đó thì thay vì phải xem, tìm kiếm toàn bộ quyển sách ta chỉ cần xem chỉ mục tìm đúng bài viết đó rồi từ số trang được đính kèm là ta có thể đến chính xác nội dung bài viết mong muốn.

Mysql hỗ trợ 3 loại index khác nhau cho database: B-tree, Hash, R-tree. Tuy nhiên R-tree chỉ được sử dụng cho các loại dữ liệu không gian spacial data, ví dụ như vị trí nhà hàng hoặc đa giác mà bản đồ điển hình được tạo ra: đường phố, tòa nhà... R-tree thường ít khi gặp phải nên tạm thời mình sẽ bỏ qua, nếu có dịp thì hẹn mọi người tại một bài viết khác.

Hash index

Hash index thì sao, nó được tổ chức dưới dạng key-value với key là kết quả hash value của column được đánh index và value sẽ chứa 1 con trỏ đến chính xác row tương ứng. Chính vì vậy, hash index tỏ ra mạnh mẽ khi thực hiện các phép truy vấn với toán tử = hay [IN, NOT IN]. Tuy nhiên lại vô dụng khi gặp các trường truy vấn với điều kiện như >, ‘quanlx’ thì ngược lại. … tìm kiếm nhị phân cho đến khi ra kết quả.

  • TH2: đánh index cho nhiều trường - ví dụ [country, name, email] Vẫn như trên, nhưng có điều, column country sẽ được chọn lấy làm value chính để sắp xếp index trước, sau đó mới đến nameemail.
    Tức là trong truy vấn, mặc định B-tree index sẽ đi tìm kiếm theo country trước, khi tìm được chính xác country thì mới tìm kiếm đến name thuộc country đó, và cuối cùng là email. Chính vì vậy, vị trí column để đánh index là hết sức quan trọng.
    Nếu bạn nhảy cóc bỏ qua việc tìm kiếm country trước để tìm kiếm trực tiếp name thì index sẽ không có tác dụng. Vì nó k thể biết được trước đó, name cần tìm thuộc country nào.

  • Ngoài việc có thể tăng tốc các câu truy vấn với toán tử = thì B-tree index còn có thể đối ứng với các toán tử như >, insert data --> sau khi insert hoàn thành thì chúng ta tạo lại index, đây cũng là một solution tốt trong một vài trường hợp nhất định.

    Một số cách tạo index đơn giản

    Ở trên tôi có đề cập là index sẽ được tạo tự động một số trường hợp như khi tạo Primarykey

    ALTER TABLE tbl_name ADD PRIMARY KEY [column_list]
    

    hay UNIQUE

    ALTER TABLE tbl_name ADD UNIQUE index_name [column_list]
    

    Còn đây là cú pháp khi muốn tạo INDEX riêng:

    ALTER TABLE tbl_name ADD INDEX index_name [column_list]
    hoặc:
    CREATE UNIQUE INDEX index_name ON table_name [column_list];
    

    UNIQUE INDEX [có ý nghĩa như khi tạo UNIQUE]

    ALTER TABLE tbl_name ADD UNIQUE INDEX index_name [column_list]
    

    ví dụ tạo index multiple columns:

    CREATE TABLE `wallets` [
    	`id` int not null AUTO_INCREMENT, primary key [`id`],
        `user_id` int[10] unsigned NOT NULL,
        `bank_id` int[10] unsigned NOT NULL,
        `account_id` int[10] unsigned NOT NULL,
        `amount` int[11] NOT NULL DEFAULT 0,
        `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
        `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
        UNIQUE INDEX multiple_index [user_id, bank_id, account_id]
    ];
    

    UNIQUE INDEX với 3 columns : user_id, bank_id, account_id sẽ tăng tốc độ với những truy vấn có điều kiện như:

    WHERE user_id = 2 and bank_id = 3 and account_id = 4;
    WHERE user_id = 2 and banker_id = 3;
    WHERE user_id = 2;
    WHERE user_id = 2 and account_id = 4; [do không có column bank_id ở giữa, mất thứ tự columns trong index nên index trong trường hợp này sẽ apply cho column user_id, tương tự như khi chỉ truy vấn với user_id]
    

    và sẽ vô dụng với các kiểu truy vấn như:

    WHERE bank_id = 3 and account_id = 4;
    WHERE banker_id = 3;
    WHERE account_id = 4;
    

    Nếu bạn để ý một chút thì index sẽ chỉ apply với những trường hợp có điều kiện user_id đầu tiên, đó là lý do bạn phải hết sức chú ý về thứ tự columns khi tạo index.

    Trong case này, bạn không cần tạo index cho column user_id hay [user_id, bank_id], nó sẽ là thừa và gây tốn dung lượng vì index multiple_index đã bao gồm chúng
    

    Cùng xem lại kết quả qua trình truy vấn từ không đến có index nhé [ví dụ với bảng wallets có hơn 100K records]

    Chưa có INDEX:

    EXPLAIN SELECT * FROM wallets WHERE user_id = 253 AND bank_id = 759982 AND account_id = 47
    
    idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
    1 SIMPLE wallets ALL 105908 100 Using where

    Số rows cần phải duyệt là 105908 mới tìm thấy bản ghi tương ứng và không có possible_keys nào được sử dụng. Thử tưởng tượng nếu table có 1 triệu hay nhiều hơn thì sao nhỉ? Quá kinh khủng phải không nào. [Nếu mọi người chưa biết hiểu rõ các thống số khi explain thì có thể tham khảo tại đây]

    Sau khi có UNIQUE INDEX multiple_index [user_id, bank_id, account_id]:

    EXPLAIN SELECT * FROM wallets WHERE user_id = 253 AND bank_id = 759982 AND account_id = 47
    
    idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
    1 SIMPLE wallets const multiple_index multiple_index 12 const,const,const 1 100

    Bạn thấy không, sau khi đánh INDEX việc tìm kiếm đã giảm xuống còn 1 row khi sử dụng index [key] là multiple_index. Xem tiếp vài ví dụ với index nhé.

    EXPLAIN SELECT * FROM wallets WHERE user_id = 253 AND bank_id = 759982
    
    idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
    1 SIMPLE wallets ref multiple_index multiple_index 8 const,const 41 100
     SELECT count[*] FROM wallets WHERE user_id = 253 AND bank_id = 759982
    
    count
    41

    Có 41 kết quả của câu truy vấn, trùng khớp với số rows cần duyệt để tìm kiếm.

    EXPLAIN SELECT * FROM wallets WHERE user_id = 253
    
    idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
    1 SIMPLE wallets ref multiple_index multiple_index 4 const 246 100
     SELECT count[*] FROM wallets WHERE user_id = 253
    
    count
    246

    Tương tự như trên, có 246 kết quả của cau truy vấn, trùng với số rows cầ duyệt để tìm kiếm, bạn đã thấy sức mình của index chưa nào?

    EXPLAIN SELECT * FROM wallets WHERE user_id = 253 AND account_id = 47
    
    idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
    1 SIMPLE wallets ref multiple_index multiple_index 4 const 246 100
     SELECT count[*] FROM wallets WHERE user_id = 253 AND account_id = 47
    
    count
    6

    Bạn thấy không, câu query chỉ có có 6 kết quả. tuy nhiên khi EXPLAIN thì cần duyệt qua 246 bản ghi, lý do là trong câu điều kiện truy vấn, đã bỏ mất column bank_id trong index, không còn giữ đúng thứ tự columns ban đầu [user_id, bank_id, account_id]. Vì vậy explain có kết quả tương tự như khi WHERE user_id = 253

    Tổng kết

    Trên đây mình đã giới thiệu với tất cả mọi người về việc cách sử dụng INDEX làm sao cho hiệu quả nhất. Việc hiểu và vận dụng chúng là điều vô cùng quan trọng, hãy cẩn thận với con dao hai lưỡi này. Ngoài ra, để mài bóng con dao này, để performance tăng lên gấp bội lần khi gặp bài toán data cực lớn lên cỡ vài chục triệu records, hãy kết hợp sử dụng index với Partitions. Để tìm hiểu về partitions mọi người có thể xem bài viết của mình tại đây Cám ơn mọi người đã dành thời gian đọc bài viết, nếu có bất cứ vấn đề gì, đừng ngại thắc mắc dưới phần bình luận, mình cùng nhau chia sẻ kiến thức, cùng nhau nâng cao trình độ, và nhớ tặng mình 1 vote up nhé. Tks!!!

    Tài liệu tham khảo

    //www.vertabelo.com/blog/all-about-indexes-the-very-basics/

    //www.vertabelo.com/blog/all-about-indexes-part-2-mysql-index-structure-and-performance/

    Chủ Đề