+19

[Performance Tuning SQL] Tại sao code SQL của ông bên cạnh lại chạy nhanh hơn mình nhỉ?

Mayfest2023

Vấn đề

Chào mọi người, hôm nay mình muốn chia sẻ cho mọi người về một vài mẹo dùng để tối ưu câu truy vấn và thời gian chạy cho SQL, giúp cho những đoạn code của ta truy vấn được nhanh hơn.

Một ngày đẹp trời, bạn thấy code SQL của bạn chạy đã ra đúng kết quả của báo cáo, nhưng lại thấy thời gian chờ để nó hiển thị lên lại rất lâu và nó lại chạy lâu hơn rất nhiều so với ông ngồi bên cạnh mình. Kiểm tra đi kiểm tra lại và chắc chắn rằng mình đã viết code rất đơn giản và dễ hiểu chính xác từng câu lệnh, từng đoạn lệnh, không có nhẽ nào nó lại chạy chậm. Nhưng đôi khi chính những cái ta nghĩ rằng nó đúng và đơn giản đó lại làm cho code chạy chậm hơn.

Khác với việc đi học trên trường, ta chỉ cần viết code đúng ra kết quả của bài toán truy vấn và không quan tâm lắm đến thời gian truy vấn, nhưng đối với đi làm thì khác, ngoài khoảng thời gian viết code ra kết quả đúng, ta còn quan tâm đến thời gian truy vấn dữ liệu từ Database lên được các báo cáo số liệu hoàn chỉnh, và khi đưa chúng lên trên hệ thống người dùng, thì còn cần truy vấn nhanh hơn nữa, hơn nữa. Điều đó yêu cầu chúng ta cần phải viết code làm sao để thời gian truy vấn là nhỏ nhất có thể.

Một vài Tips

Tip 1: Hãy sử dụng chính xác tên cột cần truy vấn thay vì dùng 'SELECT * FROM '

Nếu truy vấn không cần thiết phải hiển thị hết các cột thì ta không nên dùng 'SELECT *', vì khi kích thước bảng hiện ra quá lớn cũng sẽ gây nên chậm và thay vì thế hãy chỉ chọn những cột mà ta cần cho việc tính toán hoặc hiển thị.

SELECT * FROM EMPLOYEE
---------------
SELECT EMP_ID, NAME, SEX FROM EMPLOYEE

Tip 2: Xem xét nếu không cần thiết dùng DISTINCT

Khi chúng ta truy vấn một trường là khóa chính hoặc ta đã biết chắc chắn rằng nó không xảy ra trùng lặp dữ liệu thì ta không cần thiết phải dùng DISTINCT, vì khi sử dụng, nó khiến cho phép truy vấn phải thực hiện thêm một phép so sánh nữa để loại bỏ các dòng trùng nhau trong dữ liệu.

SELECT DISTINCT * FROM SH.sales s JOIN SH.customers c 
ON s.cust_id= c.cust_id
WHERE c.cust_marital_status = 'single';

------------------

SELECT * FROM SH.sales s JOIN SH.customers c 
ON s.cust_id = c.cust_id
WHERE c.cust_marital_status='single';

Tip 3: Nếu có thể thì hãy loại bỏ các truy vấn con (Sub-query)

Tại sao truy vấn con lại gây nên chậm thời gian truy vấn.
Giải thích một cách đơn giản như thế này: Khi ta sử dụng truy vấn con, mỗi lần câu truy vấn chính thực hiện so sánh điều kiện hoặc chọn một số trường dữ liệu trả về trong truy vấn con, nó đều phải truy vấn lại câu truy vấn con và như thế là truy vấn con được thực hiện lặp lại nhiều lần. Hay nói theo độ phức tạp tính toán gọi là O(n^2). Đối với cơ sở dữ liệu lớn, thì đó thực sự là chi phí truy vấn rất lớn.

Giả sử chúng ta có hai bảng: "Orders" và "OrderDetails". Bảng "Orders" lưu trữ thông tin về các đơn hàng, trong khi bảng "OrderDetails" lưu trữ thông tin về các chi tiết đơn hàng. Chúng ta muốn lấy danh sách tất cả các đơn hàng và số lượng chi tiết của từng đơn hàng.

Câu lệnh sử dụng subquery để lấy số lượng chi tiết của mỗi đơn hàng có thể được viết như sau:

SELECT o.order_id, 
(SELECT COUNT(*) FROM order_details od WHERE od.order_id = o.order_id) as num_details
FROM orders o;

Câu lệnh trên sử dụng một subquery để lấy số lượng chi tiết của mỗi đơn hàng. Tuy nhiên, nếu bảng "OrderDetails" chứa một lượng lớn dữ liệu, câu lệnh này có thể trở nên chậm và ảnh hưởng đến hiệu suất của truy vấn. Thay vì sử dụng subquery, chúng ta có thể sử dụng left join để lấy thông tin từ bảng "Orders" và "OrderDetails" như sau:

SELECT o.order_id, COUNT(od.order_id) as num_details
FROM orders o
LEFT JOIN order_details od ON o.order_id = od.order_id
GROUP BY o.order_id;

Câu lệnh trên sử dụng left join để kết hợp dữ liệu từ hai bảng và sử dụng hàm COUNT để đếm số lượng chi tiết của từng đơn hàng. Sử dụng left join thay vì subquery có thể tăng hiệu suất và giúp cho truy vấn chạy nhanh hơn.

Vậy nên theo như mình thấy, việc không dùng Subquery giảm được rất rất nhiều chi phí tính toán, và mang lại hiệu quả đáng kinh ngạc trong 1 task của mình.

Tip 4: Sử dụng UNION ALL thay cho UNION

Khi sử dụng UNION ALL, truy vấn sẽ trả về tất cả các bản ghi từ cả hai bảng, bao gồm các bản ghi trùng lặp. Việc sử dụng UNION ALL sẽ giúp truy vấn thực hiện nhanh hơn so với UNION khi không cần loại bỏ các bản ghi trùng lặp.

Hãy ưu tiên sử dụng UNION ALL khi chúng ta biết chắc chắn mỗi dòng trong kết quả sẽ là duy nhất hoặc có thể chấp nhận việc trùng lặp.

Tip 5: Không nên sử dụng OR cho các mệnh đề nhiều điều kiện

Sử dụng OR trong các mệnh đề có thể làm chậm tốc độ truy vấn và khiến kết quả trả về không chính xác nếu không được sử dụng đúng cách. Thay vào đó, nên sử dụng các điều kiện AND hoặc UNION ALL để tối ưu hóa truy vấn.

SELECT * FROM customers WHERE name = 'John' OR address = 'New York';
---------------------
SELECT * FROM customers WHERE name = 'John' UNION ALL SELECT * FROM customers 
WHERE address = 'New York';

Thay vì dùng OR, ta có thể thay thế bằng UNION ALL để tối ưu hóa truy vấn.

Kết

Đấy, bạn đã biết tại sao của mình lại chậm hơn ông hàng xóm chưa nào! Vừa rồi là một vài mẹo mà mình học hỏi được trong quá trình đi làm thực tế, rất vui khi được chia sẻ với mọi người để có thể khắc phục được tình trạng truy vấn chậm gây ảnh hưởng đến kết quả làm việc.

Mong rằng sẽ nhận được những phản hồi tích cực từ mọi người. Cảm ơn rất nhiều!


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.