- Giới thiệu cách tận dụng tính năng JSON của SQLite để lưu nguyên tài liệu JSON và trích xuất các trường cần thiết thành cột được sinh ảo (virtual generated columns) để lập chỉ mục
- Dùng hàm
json_extract để xử lý dữ liệu bên trong JSON như cột thông thường, cho phép truy vấn với tốc độ chỉ mục B-tree
- Mỗi khi cần một kiểu truy vấn mới, có thể mở rộng bằng cách thêm cột và chỉ mục mà không cần di trú dữ liệu
- Cách làm này đồng thời đạt được sự linh hoạt của dữ liệu không lược đồ và hiệu năng của cơ sở dữ liệu quan hệ
- Được nhấn mạnh như một mẫu thực tiễn mang lại cấu trúc gọn gàng và hiệu năng cao cho các lập trình viên dùng SQLite
Kết hợp SQLite với tính năng JSON
- SQLite hỗ trợ các hàm và toán tử JSON, nhờ đó có thể lưu trữ và thao tác trực tiếp trên dữ liệu JSON
- Lưu nguyên tài liệu JSON trong một cột và chỉ trích xuất các thông tin cần thiết thành cột ảo
- Cách này cho phép xử lý dữ liệu linh hoạt mà không cần định nghĩa lược đồ trước
- Trong vài tháng gần đây, đội ngũ DB Pro đã sử dụng SQLite một cách tập trung và kiểm chứng các tính năng này trong thực tế
- Nếu được cấu hình phù hợp, SQLite có thể vận hành ổn định ngay cả trong môi trường production
Cột được sinh ảo (Generated Columns)
- Dùng
json_extract để định nghĩa các giá trị cụ thể bên trong JSON thành cột được sinh ảo
- Các cột này không lưu dữ liệu thực mà được tính toán tại thời điểm truy vấn để dùng ngay
- Không cần quy trình backfill hay sao chép dữ liệu riêng
- Ví dụ, có thể xây dựng cấu trúc trích xuất một trường cụ thể từ dữ liệu JSON và xử lý nó như một cột
Thêm chỉ mục và cải thiện hiệu năng
- Khi thêm chỉ mục vào cột ảo, dữ liệu JSON cũng có thể được tìm kiếm với tốc độ chỉ mục B-tree như cột thông thường
- Cột ảo đã được lập chỉ mục cho hiệu năng tương đương cột trong cơ sở dữ liệu quan hệ
- Cách tiếp cận này vẫn cho phép tìm kiếm nhanh ngay cả khi kích thước dữ liệu JSON tăng lên
Bổ sung các kiểu truy vấn mới
- Nếu sau này cần tìm kiếm theo một trường mới, chỉ cần thêm cột ảo và chỉ mục mới
- Ví dụ: trích xuất trường
user_id và tạo chỉ mục
- Không cần sửa các hàng dữ liệu hiện có hay thực hiện di trú
- Nhờ đó có thể mở rộng khả năng truy vấn ngay lập tức mà không phải thay đổi cấu trúc dữ liệu
Ưu điểm và ý nghĩa của mẫu này
- Mẫu này kết hợp sự linh hoạt của việc lưu JSON không lược đồ với hiệu năng chỉ mục của DB quan hệ
- Không cần quyết định trước chiến lược lập chỉ mục ngay ở giai đoạn thiết kế ban đầu
- Có thể thêm cột và chỉ mục để tối ưu đúng vào thời điểm cần thiết
- Đây được xem là một cách xử lý dữ liệu vừa đơn giản vừa mạnh mẽ cho các lập trình viên sử dụng SQLite
- DB Pro cũng cho biết sẽ tiếp tục giới thiệu thêm các bài viết về nhiều tính năng khác nhau của SQLite
2 bình luận
Ý kiến trên Hacker News
Có thể mã hóa trực tiếp tài liệu JSON thành B-tree đã được tuần tự hóa
Làm vậy sẽ cho phép truy vấn trực tiếp các trường bên trong với tốc độ đã được lập chỉ mục, đồng thời không cần parse vì bản thân tài liệu đã được lập chỉ mục sẵn
Định dạng này được gọi là Lite³. Đây là dự án mà chính tôi đang thực hiện
Liên kết GitHub
Tôi đặc biệt thích việc Lite³ hỗ trợ dữ liệu nhị phân qua
lite3_val_bytesJSONB mã hóa cả độ dài mảng và offset để cân bằng giữa hiệu quả nén và tốc độ
Lite³ cho phép cập nhật in-place, nhưng cần “vacuum” định kỳ để tránh lưu lại dữ liệu nhạy cảm
JSONB thì khó cập nhật nếu không mã hóa lại, còn Lite³ có thể dọn dẹp khá đơn giản chỉ bằng cách duyệt cấu trúc
Về khả năng nén thì có lẽ JSONB tốt hơn, nhưng tôi nghĩ thiết kế của Lite³ là một cách tiếp cận rất thông minh
Tôi cũng đang bảo trì một trình biên dịch ASN.1 nên rất quan tâm đến các định dạng tuần tự hóa kiểu này. Lite³ đã cho tôi thêm vài ý tưởng mới
Tôi rất thích SQLite, nhưng khi làm phân tích thì tôi dùng DuckDB thường xuyên hơn
DuckDB dùng một tệp đơn như SQLite nhưng xử lý các tập dữ liệu lớn cực kỳ nhanh
Trên MacBook M2, ngay cả khi làm việc với 20 triệu bản ghi nó vẫn rất nhanh
Ví dụ, có thể đọc trực tiếp tệp JSON bằng truy vấn sau
Ngoài ra còn có thể nạp cột kiểu JSON và dùng cú pháp
col->>'$.key'theo kiểu PostgresTuy vậy, so với SQLite thì hơi thiếu công bằng. SQLite phù hợp để xây dựng hệ thống, còn DuckDB đúng hơn là dành cho phân tích
Khi triển khai lên nhiều nền tảng, DuckDB có phần hơi rắc rối
Tôi từng nghĩ dùng Generated Column để tối ưu hiệu năng JSON là cách làm khá phổ biến
Tôi cũng từng làm vậy trên Postgres để giữ các khóa trong cột JSON như khóa ngoại. Hơi “tà đạo” một chút nhưng hoạt động tốt
Ví dụ Blog tham khảo
Nếu schema JSON thay đổi, việc parse hoặc migration có thể thất bại
Gần đây tôi biết đến kỹ thuật này qua một ví dụ tối ưu hóa do Claude Code đề xuất
Tôi đã bỏ lỡ nó vì không biết các tính năng mới của SQLite, và mức cải thiện hiệu năng là khá lớn
Bài học rút ra là, ngay cả với công cụ quen thuộc, vẫn nên đọc lại tài liệu định kỳ
Tôi viết bài blog này sau khi thấy bình luận của bambax trên HN vào năm 2023
Liên kết bình luận gốc
Có thể tạo chỉ mục mà không cần chiếu trực tiếp JSON, nhưng computed column giúp truy vấn đơn giản hơn
Trước MS-SQL 2025(v17), hỗ trợ JSON còn hạn chế nên cách này gần như bắt buộc
Tôi mở bài viết trên HN và thấy bình luận của mình được trích dẫn, mà lại chính là chủ đề của bài viết, đúng là một trải nghiệm lạ
Khi thấy dòng “Cảm ơn, bambax!” tôi đã mỉm cười. SQLite thật sự là một công cụ tuyệt vời
Khá thú vị, nhưng chẳng phải có thể dùng "Index On Expression" của SQLite sao?
Ví dụ
CREATE INDEX idx_events_type ON events(json_extract(data, '$.type'))Tuy nhiên chỉ cần cú pháp JSON path khác đi một chút là có thể không dùng được chỉ mục. Ngược lại, Virtual Generated Column luôn đảm bảo dùng chỉ mục
Ví dụ: dự án recordlite
Ví dụ
json_extract(data, "$.type")vàdata -> '$.type'được nhận diện khác nhauNói cách khác, nếu biểu thức trong mệnh đề WHERE thay đổi thì chỉ mục sẽ trở nên vô dụng
Tôi mong các lập trình viên hạn chế việc đưa toàn bộ dữ liệu vào cột JSON(B) dù schema đã nhất quán
Việc thiết lập chỉ mục, quản lý ràng buộc sẽ khó hơn, và khi dùng thực tế còn có thêm overhead đáng kể
Ví dụ trong môi trường Haskell+TypeScript, việc tuần tự hóa cấu trúc lồng nhau phức tạp thành JSON thuận tiện hơn nhiều
Ví dụ: lưu kết quả từ nhiều payment processor vào một bảng, hoặc xử lý các thuộc tính khác nhau theo từng mặt hàng trên trang rao vặt
Trong C# hay JS/TS, nếu dùng thêm các công cụ kiểm tra kiểu như Zod, OpenAPI thì việc quản lý cũng dễ hơn
Cuối cùng vẫn là bài toán cân bằng giữa khả năng bảo trì và hiệu năng. Điểm chính của bài này là ngay cả với JSON cũng có thể lập chỉ mục dễ dàng
Hiệu năng đọc có thể bù đủ bằng chỉ mục
Ví dụ trong hệ thống cấu hình giá sản phẩm, việc biểu diễn các quy tắc giảm giá đặc thù theo từng khách hàng bằng JSON sẽ linh hoạt hơn nhiều
Nếu dùng XML thay vì JSON thì đây thực chất là cùng một mô hình với cơ sở dữ liệu tài liệu (document store) của thập niên 90~00
Dữ liệu được parse khi chèn/cập nhật, còn khi truy vấn thì chỉ truy cập chỉ mục
Việc SQLite cung cấp sẵn kiểu chức năng này thật sự rất thú vị
Vào cuối thế kỷ 20… từng có thứ gọi là cơ sở dữ liệu vạn năng… (bây giờ thì đúng, còn khi đó thì sai.)