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

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

· 12 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.

Vì sao NOT IN của bạn trả về 0 dòng? Logic ba trị của SQL và cái bẫy NULL

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

NULL trong SQL không phải một giá trị mà là ký hiệu cho "không biết". Mọi phép so sánh với nó đều cho ra UNKNOWN, và WHERE chỉ giữ lại dòng nào cho ra TRUE — nên UNKNOWN bị loại y như FALSE. Hệ quả nghiêm trọng nhất nằm ở NOT IN: chỉ cần subquery chứa một NULL là toàn bộ câu truy vấn trả về 0 dòng, kể cả khi dữ liệu rõ ràng có. Tôi chạy thử trên PostgreSQL 16: cùng một ý định, NOT IN cho 0 dòng còn NOT EXISTS cho 3 dòng. Câu lệnh không báo lỗi, không cảnh báo, chỉ lặng lẽ trả về sai.

Đây là loại lỗi không làm ứng dụng sập. Nó chỉ làm báo cáo thiếu số, làm màn hình danh sách trống, làm một chiến dịch gửi thiếu khách hàng — và không để lại dấu vết nào trong log.

Bài này đào sâu một chi tiết trong series học SQL 30 ngày, cụ thể là phần toán tử và biểu thức cùng subquery. Mọi con số dưới đây là kết quả chạy thật trên PostgreSQL 16.11.

Một dấu tiếng Việt làm chết lời gọi API: cf-ipcity, HttpClient và giới hạn ASCII

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

Cloudflare tự chèn cf-ipcity vào request đi vào, và với khách ở Việt Nam thì giá trị là Hồ Chí Minh — có dấu, tức non-ASCII. Service .NET forward nguyên xi mọi header sang lời gọi đi ra, nên giá trị đó rơi vào HttpClient. Điều bất ngờ là Headers.Add không ném exception, TryAddWithoutValidation cũng trả về true; mọi thứ chỉ nổ ở SendAsync với HttpRequestException: Request headers must contain only ASCII characters, và không byte nào rời khỏi tiến trình. Lỗi không nằm ở Cloudflare cũng không nằm ở .NET, mà ở chỗ ứng dụng forward mọi header vô điều kiện.

Bối cảnh là một nền tảng loyalty thương mại điện tử, quy mô khoảng ba triệu khách hàng. Tôi ẩn tên khách và mọi chi tiết định danh; phần kể được là phần kỹ thuật.

Mọi thứ nhìn đều bình thường. API đang chạy. Pod trên Kubernetes healthy. Database ổn. Request vào tới ứng dụng. Nhưng một lời gọi HTTP sang service nội bộ cứ hỏng trên production — và chỉ trên production.

Thứ làm nó hỏng, hoá ra, là tên thành phố của chính người dùng.

Forward header trong ASP.NET Core: vì sao 'forward hết' là một lỗi kiến trúc

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

Vòng lặp copy mọi header từ request đi vào sang request đi ra là một anti-pattern, và nó hỏng theo ba hướng khác nhau: sai về tính đúng đắn (một giá trị non-ASCII làm HttpClient ném exception), sai về bảo mật (bạn tin và chuyển tiếp dữ liệu do client tự đặt), và sai về kiến trúc (header do hạ tầng chèn trở thành một phần hợp đồng của ứng dụng). Cách sửa là allowlist — khai tường minh danh sách header được phép đi qua — đặt trong một DelegatingHandler dùng chung. Khi viết handler đó, cẩn thận một cái bẫy: nó không sống trong scope của request.

Bài này khép lại cụm ba bài bắt đầu từ một sự cố production trên nền tảng loyalty khoảng ba triệu khách hàng, nơi header cf-ipcity: Hồ Chí Minh do Cloudflare chèn làm hỏng một lời gọi nội bộ. Bài đầu kể sự cố, bài hai giải thích vì sao header không mang được tiếng Việt. Bài này trả lời câu hỏi còn lại: viết lại chỗ đó thế nào cho đúng.

HTTP header có chứa được tiếng Việt không? ASCII, obs-text và chỗ .NET vạch ranh giới

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

Trả lời ngắn: không, nếu bạn muốn nó đi qua mọi tầng an toàn. Đặc tả HTTP định nghĩa giá trị header bằng ký tự ASCII in được, cộng thêm một nhánh lỗi thời tên là obs-text cho phép byte 0x80–0xFF nhưng không quy định bảng mã nào để diễn giải chúng. Hệ quả là một chuỗi tiếng Việt có thể đi lọt một phần: í nằm trong Latin-1 nên biểu diễn được, còn ồ thì không, vì nó là U+1ED3 — vượt ngoài 0xFF. .NET chọn cách dứt khoát: HttpClient từ chối gửi mọi giá trị header non-ASCII. Muốn mang dữ liệu có dấu thì phải mã hoá, bằng percent-encoding, RFC 8187 hoặc Base64.

Bài này tách ra từ một sự cố production, nơi header cf-ipcity: Hồ Chí Minh do Cloudflare chèn làm HttpClient ném exception. Ở đó tôi dừng lại ở hiện tượng. Ở đây tôi trả lời câu hỏi mà hiện tượng ấy đặt ra: rốt cuộc HTTP header được phép chứa những gì?