10/10/2018, 11:00
Tối ưu hóa truy vấn MySQL cho site có lượng truy cập lớn
Thân chào các bạn Lập Trình Web!
Trong sự nghiệp lập trình web của mình, hẳn không ít coder chúng ta không có những mục tiêu, hoài bão là xây dựng, thực hiện được những dự án lớn, có tầm cỡ quốc gia, quốc tế. Chẳng hạn Vật Giá, Chợ Điện Tử, Zing, Zooz... ở Việt Nam, hay lớn hơn là những YouTube, MySpace, Facebook... trên qui mô toàn cầu.
Khi nói đến những dự án lớn thì một trong những vấn đề được các coder quan tâm hàng đầu đó là hiệu suất của dự án. Một site nhỏ với qui mô vài trăm user, dung lượng database chưa đáng kể thì thời gian truy vấn, tải trang chưa phải là vấn đề bạn cần quan tâm. Nhưng theo thời gian site phát triển với tốc độ chóng mặt, chẳng mấy chốc đã có tới hàng triệu users tham gia, tỉ lệ thuận với nó là bảng users có hàng triệu records, database phình to, dung lượng có thể lên đến hàng gigabyte, hàng chục gigabyte thậm chí hàng trăm gigabyte... Lúc này, ngoài việc triển khai các kế hoạch kinh doanh, khai thác lợi nhuận (có thể để bộ phận kinh doanh lo) thì một vấn đề lớn đặt ra cho các coder chúng ta là làm sao để website với 1 database to như vậy vẫn chạy mượt mà như là database nhỏ!? Ngoài những vấn đề về đầu tư cơ sở hạ tầng khủng với server cấu hình cao, database server riêng rẽ, chuẩn hóa code ra mình mạo muội viết bài viết này để chúng ta cùng chia sẽ những kinh nghiệm, thủ thuật về tối ưu truy vấn MySQL với một database lớn.
Mình xin bắt đầu với 1 database có bảng users với khoảng 1 triệu records, nó là kinh nghiệm mình gặt hái được trong quá trình tham gia phát triển dự án game online UGH!
users
- Yêu cầu: một ngày đẹp trời, bạn muốn gửi thông điệp đến tất cả các users có số money ít hơn 1 USD rằng: Tai khoan cua ban sap het! Hay nop them tien vao tai khoan.
- Cách làm thông thường:
=> Cách làm tối ưu: dùng 1 query để giải quyết tình huống này
Kết quả: thời gian xử lí giảm xuống gần 20 lần
Thủ thuật trong trường hợp này:
- Kết hợp INSERT và SELECT để thay thế cho while của PHP.
- Dùng CONCAT để lấy name của user.
users
- Yêu cầu: một user sẽ được cộng thêm 1 số điểm là scores trong bảng user_scores tương ứng với mỗi score_type_id (ưu tiên theo score_type_id) mà user đang có. Nhưng tổng số scores hiện có và scores của các score_type_id này không được vượt quá con số max_scores_can_contain trong bảng users, nếu vượt quá thì chỉ lấy số scores tương ứng với tổng số scores bằng max_scores_can_contain. Sao yêu cầu loằng ngoằng vậy ta ? Chắc do nó là advanced nên mới thế .
- Giải quyết vấn đề:
=> Tối ưu:
Kết quả: thời gian xử lí giảm đi gần 10 lần
Thủ thuật:
- Dùng WHEN ELSE chia case và cộng ngay trong câu truy vấn.
Ok, đây là 2 tut đầu của mình với cơ sở hạ tầng đơn giản chỉ là Xampp server được cài đặt trên laptop của mình. Có lẽ 1 phần vì cấu hình server không được gút cho lắm nên có đoạn script chạy hơi bị lâu. Không sao, điều quan trọng là chúng ta so sánh được thời gian trước và sau khi tối ưu. Còn trên thực tế với database này thì chắc là bạn đã phải sắm database server từ lâu rồi !
Mình cũng đính kèm theo source của 2 tut này http://www.mediafire.com/?lhbcwmodm9q8czo, các bạn có thể thao tác:
- Giải nén, copy thư mục này lên server, ví dụ tên thư mục là mysql_optimize
- Tạo database mysql_optimize
- Import file mysql_optimize.sql vào database này
- Chỉnh lại thông số trong file config.php
- Test tutorial nào thì bỏ comment out tương ứng của tutorial đó trong file index.php, đoạn include tutorial_1.php, tutorial_2.php
- Trong file tutorial:
+ Bỏ comment out trong step 1, chạy file index.php để có sample data
+ Bỏ comment out trong step 2, chạy file index.php để có kết quả lúc chưa tối ưu
+ Bỏ comment out trong step 3, chạy file index.php để có kết quả lúc đã tối ưu
Test, so sánh và thưởng thức thành quả !
Trong sự nghiệp lập trình web của mình, hẳn không ít coder chúng ta không có những mục tiêu, hoài bão là xây dựng, thực hiện được những dự án lớn, có tầm cỡ quốc gia, quốc tế. Chẳng hạn Vật Giá, Chợ Điện Tử, Zing, Zooz... ở Việt Nam, hay lớn hơn là những YouTube, MySpace, Facebook... trên qui mô toàn cầu.
Khi nói đến những dự án lớn thì một trong những vấn đề được các coder quan tâm hàng đầu đó là hiệu suất của dự án. Một site nhỏ với qui mô vài trăm user, dung lượng database chưa đáng kể thì thời gian truy vấn, tải trang chưa phải là vấn đề bạn cần quan tâm. Nhưng theo thời gian site phát triển với tốc độ chóng mặt, chẳng mấy chốc đã có tới hàng triệu users tham gia, tỉ lệ thuận với nó là bảng users có hàng triệu records, database phình to, dung lượng có thể lên đến hàng gigabyte, hàng chục gigabyte thậm chí hàng trăm gigabyte... Lúc này, ngoài việc triển khai các kế hoạch kinh doanh, khai thác lợi nhuận (có thể để bộ phận kinh doanh lo) thì một vấn đề lớn đặt ra cho các coder chúng ta là làm sao để website với 1 database to như vậy vẫn chạy mượt mà như là database nhỏ!? Ngoài những vấn đề về đầu tư cơ sở hạ tầng khủng với server cấu hình cao, database server riêng rẽ, chuẩn hóa code ra mình mạo muội viết bài viết này để chúng ta cùng chia sẽ những kinh nghiệm, thủ thuật về tối ưu truy vấn MySQL với một database lớn.
Mình xin bắt đầu với 1 database có bảng users với khoảng 1 triệu records, nó là kinh nghiệm mình gặt hái được trong quá trình tham gia phát triển dự án game online UGH!
* Thủ thuật 1: INSERT
- Ngữ cảnh: chúng ta có 2 bảng users (1 triệu records), messages (empty) với cấu trúc:users
- user_id
- name
- money
messages- name
- money
- message_id
- user_id
- subject
- body
- user_id
- subject
- body
- Yêu cầu: một ngày đẹp trời, bạn muốn gửi thông điệp đến tất cả các users có số money ít hơn 1 USD rằng: Tai khoan cua ban sap het! Hay nop them tien vao tai khoan.
- Cách làm thông thường:
PHP Code:
$query = mysql_query("SELECT * FROM users WHERE money < 1");
$subject = "Money cua ban sap het!";
while ( $row = db_fetch_object($query) ) {
$body = $row->name ." than men! So money trong tai khoan cua ban chi con chua den 1 USD - mua duoc 1 kg rau muong luoc. Hay nop them tien vao tai khoan de giao dich khong bi gian doan.";
mysql_query("INSERT INTO messages (user_id, subject, body) VALUES ($row->user_id, '$subject', '$body')");
}
// Processed in 67.0436019897 sec
PHP Code:
mysql_query("
INSERT INTO messages
(user_id, subject, body)
SELECT
user_id, 'Money cua ban sap het!', CONCAT(name, ' than men! So money trong tai khoan cua ban chi con chua den 1 USD - mua duoc 1 kg rau muong luoc. Hay nop them tien vao tai khoan de giao dich khong bi gian doan.')
FROM users
WHERE money < 1
");
// Processed in: 3.5900 sec
Thủ thuật trong trường hợp này:
- Kết hợp INSERT và SELECT để thay thế cho while của PHP.
- Dùng CONCAT để lấy name của user.
* Thủ thuật 2: UPDATE
- Ngữ cảnh: có 2 bảng users (1 triệu records), user_scores (2 triệu records)users
- user_id
- name
- total_scores
- max_scores_can_contain
user_scores- name
- total_scores
- max_scores_can_contain
- user_score_id
- user_id
- score_type_id
- scores
- user_id
- score_type_id
- scores
- Yêu cầu: một user sẽ được cộng thêm 1 số điểm là scores trong bảng user_scores tương ứng với mỗi score_type_id (ưu tiên theo score_type_id) mà user đang có. Nhưng tổng số scores hiện có và scores của các score_type_id này không được vượt quá con số max_scores_can_contain trong bảng users, nếu vượt quá thì chỉ lấy số scores tương ứng với tổng số scores bằng max_scores_can_contain. Sao yêu cầu loằng ngoằng vậy ta ? Chắc do nó là advanced nên mới thế .
- Giải quyết vấn đề:
PHP Code:
// Query tat ca users, chi update nhung user co scores > 0
$query = mysql_query("SELECT * FROM user_scores WHERE scores > 0");
while ( $row = mysql_fetch_object($query) ) {
// Lay object cua user nay
$user = mysql_fetch_object(mysql_query("SELECT * FROM users WHERE user_id = $row->user_id"));
// Chi cong nhung user cos total_scores < max_scores_can_contain
if ( $user->total_scores < $user->max_scores_can_contain ) {
// Bat dau kiem tra bien scores_addition se cong vao
if ( $user->total_scores + $row->scores >= $user->max_scores_can_contain ) {
// Chi cong vao de total scores = max scores can contain
$scores_addition = $user->max_scores_can_contain - $user->total_scores;
} else {
// Cong binh thuong
$scores_addition = $row->scores;
}
// Bat dau cong
mysql_query("UPDATE users SET total_scores = total_scores + $scores_addition WHERE user_id = $user->user_id");
}
}
// Processed in 530.916620016 sec
PHP Code:
mysql_query("
UPDATE users AS u
LEFT JOIN user_scores AS us
ON u.user_id = us.user_id
SET u.total_scores = u.total_scores +
(
CASE
WHEN (u.total_scores + us.scores) > u.max_scores_can_contain
THEN (u.max_scores_can_contain - u.total_scores)
ELSE us.scores
END
)
WHERE u.total_scores < u.max_scores_can_contain
AND us.scores > 0
");
// Processed in 59.2287611961 sec
Thủ thuật:
- Dùng WHEN ELSE chia case và cộng ngay trong câu truy vấn.
Ok, đây là 2 tut đầu của mình với cơ sở hạ tầng đơn giản chỉ là Xampp server được cài đặt trên laptop của mình. Có lẽ 1 phần vì cấu hình server không được gút cho lắm nên có đoạn script chạy hơi bị lâu. Không sao, điều quan trọng là chúng ta so sánh được thời gian trước và sau khi tối ưu. Còn trên thực tế với database này thì chắc là bạn đã phải sắm database server từ lâu rồi !
Mình cũng đính kèm theo source của 2 tut này http://www.mediafire.com/?lhbcwmodm9q8czo, các bạn có thể thao tác:
- Giải nén, copy thư mục này lên server, ví dụ tên thư mục là mysql_optimize
- Tạo database mysql_optimize
- Import file mysql_optimize.sql vào database này
- Chỉnh lại thông số trong file config.php
- Test tutorial nào thì bỏ comment out tương ứng của tutorial đó trong file index.php, đoạn include tutorial_1.php, tutorial_2.php
- Trong file tutorial:
+ Bỏ comment out trong step 1, chạy file index.php để có sample data
+ Bỏ comment out trong step 2, chạy file index.php để có kết quả lúc chưa tối ưu
+ Bỏ comment out trong step 3, chạy file index.php để có kết quả lúc đã tối ưu
Test, so sánh và thưởng thức thành quả !
Bài liên quan
ko nhớ rõ nhưng hình như MYSQL 1 table tối đa 4GB,vậy có quá ít cho các dự án lớn thế này
dù sao bài viết của bác cũng rất có ích
trước kia dùng zend framework thấy nó chậm,sau đó mày mò hỏi và xài zend cache và nhất là tối ưu truy vấn sql thì nó nhanh lên gấp mấy lần
mà bài viết vậy là hết hả bác?
Chỉ khổ lúc phải thay đổi schema, thêm index..
Còn với MySQL thì kích thước table nó có ghi rõ mà
http://dev.mysql.com/doc/refman/5.0/en/full-table.html
Mình đang viết các tutorial tiếp theo. Mình cũng sẽ rất vui nếu được bạn chia sẽ những truy vấn tối ưu của bạn!
thủ thuật 1 thì chỉ dành cho newbie mà kém thông minh thôi.
thủ thuật 2 thì lạ quá hehe