Bài 7: Xử lý NULL đúng cách – Tránh "sập bẫy" logic và hiệu năng
Dưới đây là nội dung chi tiết cho Bài 7: Xử lý NULL đúng cách – Phân tích sâu "bẫy" logic và hiệu năng, khám phá từng lớp nguyên nhân và giải pháp triệt để:
Hiểu sâu bản chất NULL và cách database xử lý giá trị "vô hình"
1. NULL là gì? Tại sao nó phức tạp?
1.1. Định nghĩa
NULL: Không phải là giá trị, mà là trạng thái "không xác định" hoặc "thiếu giá trị".
Khác biệt cốt lõi:
NULL = NULL
→ KHÔNG đúng (trả vềNULL
thay vìTRUE
).
1.2. Ví dụ minh họa
SELECT NULL = NULL; -- Kết quả: NULL
SELECT NULL IS NULL; -- Kết quả: TRUE
SELECT 1 + NULL; -- Kết quả: NULL
2. Phân tích sâu "bẫy" logic khi xử lý NULL
2.1. So sánh với NULL dùng =
Ví dụ sai:
-- Lọc khách hàng chưa có số điện thoại
SELECT * FROM customers WHERE phone = NULL; -- Sai! Luôn trả về 0 rows
Cách đúng:
SELECT * FROM customers WHERE phone IS NULL;
Nguyên nhân:
Ba giá trị logic (Three-Valued Logic):
TRUE
/FALSE
/UNKNOWN
(NULL).WHERE
chỉ trả về rows có điều kiệnTRUE
.
2.2. NULL trong phép JOIN
Ví dụ:
SELECT *
FROM employees e
LEFT JOIN bonuses b ON e.id = b.employee_id
WHERE b.amount > 1000; -- Loại bỏ các employees không có bonus (b.amount IS NULL)
Kết quả sai: Những người chưa có bonus (b.amount IS NULL
) bị loại khỏi kết quả.
Cách đúng:
SELECT *
FROM employees e
LEFT JOIN bonuses b ON e.id = b.employee_id
WHERE (b.amount > 1000 OR b.amount IS NULL);
3. Ảnh hưởng của NULL đến hiệu năng
3.1. Index và NULL
B-tree Index: Thường không lưu trữ entries cho rows có giá trị NULL (tùy database).
Partial Index (PostgreSQL): Tạo index chỉ cho rows không NULL.
Ví dụ:
-- Tạo index bỏ qua NULL
CREATE INDEX idx_customers_phone ON customers(phone)
WHERE phone IS NOT NULL;
-- Truy vấn tận dụng index
SELECT * FROM customers
WHERE phone = '0123456789'; -- Sử dụng index
SELECT * FROM customers
WHERE phone IS NULL; -- Full Scan (không có trong index)
3.2. Thống kê dữ liệu
Cardinality Estimate: Số lượng NULLs ảnh hưởng đến ước lượng của Optimizer.
Ví dụ: Nếu 90% rows có
phone IS NULL
, queryWHERE phone IS NOT NULL
có thể bị ước lượng sai → Chọn plan không tối ưu.
4. Giải pháp xử lý NULL toàn diện
4.1. Hàm hỗ trợ NULL
COALESCE: Thay thế NULL bằng giá trị mặc định.
SELECT COALESCE(phone, 'N/A') AS phone FROM customers;
NULLIF: Chuyển giá trị cụ thể thành NULL.
SELECT NULLIF(description, '') AS description FROM products; -- Chuyển '' thành NULL
IS DISTINCT FROM (PostgreSQL): So sánh cả NULL.
SELECT * FROM table WHERE col IS DISTINCT FROM 0; -- Bao gồm cả col IS NULL
4.2. Thiết kế schema thông minh
NOT NULL Constraint: Giảm NULL từ gốc nếu dữ liệu luôn yêu cầu giá trị.
CREATE TABLE users ( id INT PRIMARY KEY, email VARCHAR(255) NOT NULL -- Tránh NULL trong email );
Giá trị mặc định: Thay thế NULL bằng giá trị hợp lý.
CREATE TABLE orders ( status VARCHAR(20) DEFAULT 'PENDING' NOT NULL );
5. Case Study: Tối ưu hóa query chứa NULL
5.1. Bối cảnh
Bảng
sales
có 10 triệu rows, cộtdiscount
(20% rows códiscount IS NULL
).Truy vấn:
SELECT product_id, SUM(price * quantity) AS total FROM sales WHERE discount IS NULL OR discount = 0 GROUP BY product_id;
Hiệu năng ban đầu: 8 giây (Full Table Scan).
5.2. Tối ưu hóa
Bước 1: Thêm Partial Index cho discount
:
CREATE INDEX idx_sales_discount ON sales(discount)
WHERE discount IS NULL OR discount = 0;
Bước 2: Viết lại query sử dụng Expression Filter:
SELECT product_id, SUM(price * quantity) AS total
FROM sales
WHERE discount IS NULL OR discount = 0
GROUP BY product_id;
Execution Plan mới:
Index Scan trên
idx_sales_discount
→ Chỉ quét 20% dữ liệu.Thời gian: 0.9 giây (cải thiện ~9x).
6. Bài tập thực hành
Viết lại các truy vấn sau để xử lý NULL an toàn:
Truy vấn gốc (dễ mất dữ liệu):
SELECT * FROM orders WHERE cancelled_at = NULL;
Truy vấn gốc (sai logic):
SELECT AVG(rating) FROM reviews WHERE rating != 0; -- Loại bỏ cả rating = NULL
Gợi ý đáp án:
1.
SELECT * FROM orders
WHERE cancelled_at IS NULL;
SELECT AVG(rating) FROM reviews
WHERE rating IS NOT NULL AND rating != 0;
7. Tổng kết
NULL là "ẩn số" cần được đặc biệt lưu ý trong cả logic và hiệu năng.
Luôn dùng
IS NULL
/IS NOT NULL
thay vì phép so sánh thông thường.Tối ưu hóa với Partial Index và hàm hỗ trợ để giảm tác động tiêu cực của NULL.
Thiết kế schema chặt chẽ để hạn chế NULL từ gốc.
Làm chủ NULL giúp bạn tránh những lỗi "khó hiểu" và xây dựng hệ thống robust hơn!
Preview bài tiếp theo:
Bài 8: JOIN an toàn và hiệu quả – Tránh tích Đề-các và tối ưu hóa phép kết nối.