
PostgreSQL JSONB là gì và tại sao mạnh hơn JSON thuần

JSONB (Binary JSON) là kiểu dữ liệu PostgreSQL để lưu trữ và truy vấn dữ liệu JSON dưới dạng nhị phân đã được tối ưu. Khác với JSON (kiểu text lưu trữ nguyên văn), JSONB bỏ whitespace, sắp xếp key, và thêm metadata để index, search, và xử lý nhanh hơn đáng kể. PostgreSQL 9.4 ra mắt JSONB và nhanh chóng trở thành lựa chọn mặc định khi làm việc với dữ liệu phi cấu trúc trong SQL.
Ưu điểm JSONB: hỗ trợ GIN index cho truy vấn dựa trên key/value, thao tác atomic trên trường JSONB mà không cần đọc toàn bộ document, và tích hợp hoàn toàn với SQL — có thể kết hợp JOIN, WHERE, và subquery trên trường JSONB như bất kỳ cột nào khác. Điều này cho phép mô hình dữ liệu linh hoạt mà không sacrificing tính ACID và khả năng mở rộng của RDBMS.
So sánh với MongoDB: PostgreSQL JSONB cung cấp ACID toàn bộ database thay vì chỉ document-level, hỗ trợ JOIN và truy vấn phức tạp, và có sinh thái công cụ SQL giàu hơn. Tuy nhiên, MongoDB vẫn thắng về horizontal scaling tự động và aggregation pipeline phong phú hơn. Đối với use case cần cả truy cấu trúc và phi cấu trúc, PostgreSQL JSONB thường là lựa chọn tốt hơn.
Lưu trữ dữ liệu phi cấu trúc với JSONB
JSONB cho phép lưu trữ bất kỳ cấu trúc JSON nào — object, array, string, number, boolean, null — trong một trường duy nhất. Điều này hiệu quả khi schema thay đổi thường xuyên hoặc dữ liệu có độ sâu biến đổi. Ví dụ: lưu trữ log sự kiện từ microservice, mỗi sự kiện có field khác nhau tuỳ loại sự kiện.
Cách tạo bảng với trường JSONB:
CREATE TABLE events (
id SERIAL PRIMARY KEY,
event_type VARCHAR(50),
data JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
Chèn dữ liệu JSONB: dùng cú pháp JSON thuần hoặc hàm to_jsonb. PostgreSQL tự động chuyển đổi.
INSERT INTO events (event_type, data)
VALUES ('user_login', '{"user_id": 123, "ip": "192.168.1.1", "success": true}');
INSERT INTO events (event_type, data)
VALUES ('order_created', to_jsonb('{"order_id": 789, "items": [{"sku": "A1", "qty": 2}], "total": 250.50}'));
JSONB cho phép nested object và array sâu, không giới hạn độ sâu. Tuy nhiên, quá sâu làm giảm hiệu suất index và truy vấn — nên thiết kế hợp lý, tránh nesting hơn 3-4 cấp độ.
Truy vấn JSONB: Toán tử và hàm mạnh mẽ
PostgreSQL cung cấp bộ toán tử và hàm đặc biệt cho JSONB, cho phép truy vấn sâu mà không cần deserialize toàn bộ document.
- Toán tử → (get key): Trả về giá trị của key dưới dạng JSONB.
data → 'user_id'trả về123dưới dạng JSONB number. - Toán tử →> (get text): Trả về giá trị dưới dạng text.
data →> 'ip'trả về chuỗi"192.168.1.1". - Toánátor #> (get nested path):
data #> '{items,0,sku}'trả về JSONB giá trị tại path. - Toánátor #>> (get nested text): Tương tự nhưng trả về text.
Hàm jsonb_each và jsonb_each_text lặp qua từng key-value trong object, trả về set of rows. Hàm jsonb_array_elements làm tương tự với array. Hàm jsonb_typeof trả về kiểu của giá trị (object, array, string, number, boolean, null). Hàm jsonb_pretty định dạng JSONB để hiển thị đẹp.
Ví dụ: tìm tất cả sự kiện có ip địa chỉ bắt đầu bằng 192.168:
SELECT id, event_type FROM events
WHERE data →> 'ip' LIKE '192.168.%';
Ví dụ: tính tổng số lượng item trong tất cả đơn hàng:
SELECT SUM((data → 'items') #>> '{*}')::int FROM events
WHERE event_type = 'order_created';
Lưu ý: toán tử #>> {*} không tồn tại — chỉ là minh hoá ý tưởng truy xuất từng phần tử array. Thực tế cần dùng jsonb_array_elements để lặp.
Index JSONB: Tăng tốc truy vấn gấp 10-100x

JSONB hỗ trợ GIN index để truy vấn based trên key/value tồn tại. Index này làm cho các truy vấn như WHERE data @> '{"user_id": 123}' nhanh gấp 10-100 lần so với sequential scan.
Créate GIN index đơn giản:
CREATE INDEX idx_events_data ON events USING GIN (data);
Index này hỗ trợ các toán tử containment (@>), existence (?), và các thay thể của chúng. Ví dụ: tìm sự kiện có field user_id bất kể giá trị:
SELECT * FROM events WHERE data ? 'user_id';
Index cũng hỗ trợ truy vấn dựa trên giá trị cụ thể:
SELECT * FROM events WHERE data @> '{"success": true}';
Đối với truy vấn thường xuyên trên một hoặc một vài key cụ thể, tạo expression index tốt hơn GIN toàn cục. Ví dụ: index trên trường ip để truy vấn nhanh theo IP:
CREATE INDEX idx_events_ip ON events ((data →> 'ip'));
Expression index cho phép sử dụng các toán tử bình thường như =, LIKE, IN trên trường JSONB như thể là cột thông thường. Index này nhỏ hơn GIN vì chỉ index một path cụ thể.
Cập nhật JSONB: Các thao tác atomic
PostgreSQL cung cấp các hàm cập nhật JSONB mà không cần đọc, sửa, và ghi lại toàn bộ document — thao tác atomic và hiệu quả.
- jsonb_set: Thay đổi hoặc thêm key-value tại path cụ thể.
jsonb_set(data, '{status}', '"processed"', true)thay đổi hoặc thêm key status. - jsonb_insert: Thêm giá trị vào array tại vị trí cụ thể mà không ghi đè.
- jsonb_concat: Hợp nhất hai JSONB object — key trùng nhau sẽ bị ghi đè bởi giá trị thứ hai.
- jsonb_strip_nulls: Loại bỏ tất cả key có giá trị null — hữu ích trước khi lưu trữ để giảm kích thước.
- jsonb_patch: Áp dụng JSON Patch (RFC 6902) để sửa đổi theo mô tả thay đổi.
Ví dụ: đánh dấu sự kiện đã xử lý và thêm timestamp:
UPDATE events
SET data = jsonb_set(
jsonb_set(data, '{processed}', 'true'),
'{processed_at}',
to_jsonb(now()::text)
)
WHERE id = 42;
Thao tác này diễn ra trong một UPDATE statement duy nhất, tránh race condition khi nhiều tiến trình cùng cập nhật cùng một hàng.
JSONB trong ứng dụng thực tế
JSONB tỏa sáng trong các trường hợp cần schema linh hoạt nhưng vẫn muốn tận dụng lợi thế của RDBMS.
- Microservice logging: Mỗi service ghi log sự kiện với cấu trúc khác nhau vào cùng một bảng. Truy vấn tổng hợp log qua các service vẫn nhanh nhờ GIN index.
- Cấu hình người dùng: Lưu trữ preference và setting của người dùng dưới dạng JSONB — mỗi người có cấu hình khác nhau, thay đổi thường xuyên.
- API gateway payload: Lưu trữ request/response payload để debug và audit — payload có thể là bất kỳ JSON nào, thay đổi theo endpoint.
- E-commerce product catalog: Thuộc tính sản phẩm biến đổi theo danh mục (size, color, weight) — JSONB lưu trữ thuộc tính động, còn các trường cố định như price, sku lưu trong cột riêng.
Trong microservice architecture, JSONB giúp giảm số lượng bảng cần thiết. Thay vì tạo bảng riêng cho mỗi loại sự kiện hoặc thuộc tính, dùng một bảng với trường JSONB và event_type/category để phân loại. Giảm migration complexity và đơn giản hóa backup/restore.
Best practices khi sử dụng JSONB

JSONB là công cụ mạnh mẽ nhưng cần dùng đúng cách để tránh antipattern.
- Không thay thế thiết kế quan hệ tốt: Nếu dữ liệu có cấu trúc ổn định và truy vấn thường xuyên, dùng cột riêng thay vì đẩy tất cả vào JSONB. Ví dụ: user_id, email, created_at nên là cột riêng.
- Tránh truy vấn scans toàn bộ bảng: Luôn tạo index cho các trường JSONB thường được truy vấn. GIN toàn cục hoặc expression index tùy use case.
- Giỏi JSONB không quá lớn: Dokument trên 1MB làm chậm index và mạng. C cân nhắc chia thành nhiều document hoặc dùng LOB cho blob lớn.
- Kiểm tra loại dữ liệu trước khi truy vấn: Dùng
jsonb_typeofđể tránh lỗi khi truy vấn key có thể bị null hoặc có kiểu không mong đợi. - Sử dụng transaction cho thao tác phức tạp: Nếu cần cập nhật nhiều trường JSONB dựa trên giá trị cũ, bọc trong transaction để đảm bảo nhất quán.
PostgreSQL 15+ cải tiến JSONB với toán tử mới và hiệu suất tốt hơn. Cải tiến đáng chú ý: deduplication key trong object giảm kích thước lưu trữ, và improved GIN index cho nesting sâu hơn. Đối với ứng dụng mới, luôn ưu tiên JSONB thay vì JSON vì lợi thế về hiệu suất và tính năng mà không có bất kỳ trade-off nào đáng kể.
