Chuyển tới nội dung chính

6 bài viết được gắn thẻ "SQL"

JOIN, window function, NULL ba trạng thái và những câu truy vấn trông đúng nhưng trả sai dữ liệu.

Xem tất cả thẻ

Thêm 4 index làm INSERT chậm 6 lần: cái giá không ai nhắc khi bảo bạn đánh index

· 13 phút để đọc
Nguyễn Huỳnh Minh Tiến
Fullstack Developer @ Utop.io
Tóm tắt

Index tăng tốc đọc bằng cách trả giá ở mỗi lần ghi, và cái giá đó lớn hơn hầu hết người ta hình dung. Đo trên PostgreSQL 16 với cùng 200.000 dòng: bảng không index chèn xong trong 287,8 ms và chiếm 10,2 MB; bảng có bốn index mất 1.722,3 ms và chiếm 27 MB. Tức là chậm gấp 6 lần và tốn gấp 2,6 lần dung lượng — chỉ để thêm bốn cấu trúc mà có thể chẳng câu truy vấn nào dùng tới. Câu hỏi đúng không phải "cột này có nên có index không" mà là "phần đọc tiết kiệm được có bù nổi phần ghi phải trả không".

Mọi bài viết về tối ưu database đều kết thúc bằng lời khuyên thêm index. Rất ít bài nói về hoá đơn đi kèm, và càng ít bài đưa con số.

Bài này đào sâu bài Thiết kế database và bài Index trong series học SQL 30 ngày. Mọi số liệu đo thật trên PostgreSQL 16.11.

Luỹ kế của bạn sai ngay dòng đầu: window function, RANGE và cái mặc định ít ai đọc

· 12 phút để đọc
Nguyễn Huỳnh Minh Tiến
Fullstack Developer @ Utop.io
Tóm tắt

GROUP BY gom dòng lại, window function giữ nguyên dòng mà vẫn tính được số tổng của nhóm — đo thật: cùng một bảng 200.000 dòng, GROUP BY trả về 200 dòng, window trả về 200.000 dòng. Nó cũng nhanh hơn cách cũ: thay self-join bằng window đưa thời gian từ 79,2 ms xuống 22,7 ms. Nhưng có một mặc định gây sai số liệu mà rất ít người đọc tới: OVER (ORDER BY ...) dùng khung RANGE, nên mọi dòng đồng hạng đều nhận cùng một giá trị luỹ kế. Muốn cộng dồn từng dòng một thì phải ghi rõ ROWS.

Window function là thứ biến những câu truy vấn báo cáo dài dòng thành vài dòng đọc được. Nhưng nó cũng có đúng một cái bẫy đủ tinh vi để lọt qua review: bảng luỹ kế trông hợp lý ở giữa và sai ở chỗ có giá trị trùng nhau.

Bài này đào sâu bài Recursive Queries và Window Functions trong series học SQL 30 ngày. Mọi con số là kết quả chạy thật trên PostgreSQL 16.11.

Hai giao dịch cùng cộng 100, số dư chỉ tăng 100: isolation level qua thí nghiệm thật

· 13 phút để đọc
Nguyễn Huỳnh Minh Tiến
Fullstack Developer @ Utop.io
Tóm tắt

Isolation level quyết định một giao dịch nhìn thấy gì khi có giao dịch khác chạy song song. Ở mức mặc định của hầu hết database là READ COMMITTED, hai phiên cùng đọc số dư 1000 rồi cùng ghi 1100 sẽ cho kết quả cuối là 1100 — một lần cộng biến mất, không có lỗi nào được ném. Nâng lên REPEATABLE READ thì phiên thứ hai nhận ERROR: could not serialize access due to concurrent update: câu trả lời sai âm thầm biến thành một lỗi rõ ràng mà ứng dụng phải thử lại. Toàn bộ số liệu dưới đây đo bằng hai session psql song song trên PostgreSQL 16.11.

Đây là loại lỗi gần như không thể tái hiện trên máy dev, vì ở đó bạn chỉ có một người dùng. Nó chỉ xuất hiện khi hai request thật chạm vào cùng một dòng trong cùng một khoảnh khắc — và lúc đó nó không sập, không log, chỉ làm sai số liệu.

Bài này đào sâu bài Transactions và ACID trong series học SQL 30 ngày.

Có index rồi mà truy vấn vẫn quét toàn bảng? Sargability và một hàm bọc quanh cột

· 14 phút để đọc
Nguyễn Huỳnh Minh Tiến
Fullstack Developer @ Utop.io
Tóm tắt

Index là một cấu trúc sắp xếp theo giá trị của cột. Khi bạn bọc một hàm quanh cột — date_part('year', tao_luc), LOWER(email), CAST(...) — thì thứ bạn đang so sánh không còn là giá trị đã được sắp xếp nữa, nên database không dùng index được và phải quét toàn bảng. Điều kiện dùng được index gọi là sargable. Đo trên PostgreSQL 16 với 200.000 dòng: bản bọc hàm chạy 29,6 ms với Seq Scan, bản viết lại thành khoảng chạy 5,4 ms với Bitmap Index Scan — nhanh hơn ~5,4 lần và trả về cùng 37.518 dòng.

Tình huống quen thuộc: truy vấn chậm, bạn tạo index lên đúng cột đang lọc, chạy lại — vẫn chậm y như cũ. Index nằm đó, \d thấy rõ, nhưng execution plan không hề nhắc tới nó.

Bài này đào sâu bài Index và bài tối ưu truy vấn trong series học SQL 30 ngày. Mọi con số là kết quả chạy thật trên PostgreSQL 16.11.

LEFT JOIN của bạn đã thành INNER JOIN mà không ai báo

· 11 phút để đọc
Nguyễn Huỳnh Minh Tiến
Fullstack Developer @ Utop.io
Tóm tắt

LEFT JOIN giữ lại mọi dòng của bảng bên trái và điền NULL cho phần không khớp. Nhưng WHERE chạy sau phép join, nên bất kỳ điều kiện nào đặt lên cột của bảng bên phải sẽ loại luôn những dòng NULL ấy — và LEFT JOIN biến thành INNER JOIN mà không có cảnh báo nào. Chạy thật trên PostgreSQL 16: LEFT JOIN thuần cho 4 dòng, thêm WHERE còn 2 dòng, nhưng đưa đúng điều kiện đó vào ON thì được 3 dòng — mới là con số đúng.

Đây là lỗi tôi thấy nhiều nhất trong các câu truy vấn báo cáo. Nó không sai cú pháp, không chậm, không ném lỗi. Nó chỉ trả về thiếu dòng, và thường là thiếu đúng những dòng quan trọng nhất: khách chưa có đơn nào, sản phẩm chưa bán được cái nào, nhân viên chưa chốt được hợp đồng nào.

Bài này đào sâu bài JOIN trong series học SQL 30 ngày. Mọi con số bên dưới là kết quả chạy thật trên PostgreSQL 16.11.