Hai cơ sở dữ liệu, hai hình dạng khác nhau
SQLite và PostgreSQL đều dùng SQL, đều lưu dữ liệu quan hệ và đều có thể vận hành ứng dụng thật. Ngoài những điểm đó, chúng được xây dựng cho hai thế giới khác nhau.
- SQLite là một thư viện. Nó sống bên trong tiến trình của ứng dụng và đọc từ một tệp
.dbduy nhất trên đĩa. Không máy chủ, không cổng, không người dùng nào phải cấu hình. - PostgreSQL là một máy chủ. Nó chạy như một tiến trình riêng, lắng nghe trên một cổng mạng, và ứng dụng của bạn kết nối tới nó như một máy khách.
Gần như mọi khác biệt khác giữa hai hệ, từ xử lý đồng thời, triển khai, độ chặt của kiểu dữ liệu đến hiệu năng, đều đến từ sự phân tách kiến trúc đó. Hãy ghi nhớ điều này khi đọc tiếp.
Kiến trúc: trong tiến trình và máy khách/máy chủ
Mở một cơ sở dữ liệu SQLite chính là mở một tệp:
Không có daemon nào phải khởi động, không có pg_hba.conf nào phải sửa, không có cổng nào phải mở. Ứng dụng nạp thư viện SQLite, mở notes.db và bắt đầu chạy truy vấn. Triển khai nghĩa là "sao chép tệp".
Postgres thì trông giống thế này hơn:
# Start the server (once, as an admin):
sudo systemctl start postgresql
# Then connect from your app:
psql -h localhost -U alice -d mydb
Ứng dụng giao tiếp với một tiến trình riêng, thường qua TCP, đôi khi qua Unix socket. Lớp trung gian đó tốn thêm thời gian cài đặt và một vòng gửi nhận kết nối cho mỗi truy vấn, nhưng đổi lại bạn có truy cập qua mạng, xác thực nhiều người dùng và nhiều tiến trình ghi đồng thời thực sự.
Xử lý đồng thời là điểm mấu chốt
Đây thường là yếu tố quyết định. SQLite tuần tự hóa việc ghi: tại bất kỳ thời điểm nào, chỉ một tiến trình ghi giữ khóa trên tệp cơ sở dữ liệu, các tiến trình ghi khác phải chờ. Việc đọc có thể diễn ra song song (nhất là ở chế độ WAL), nhưng việc ghi đi lần lượt từng cái một.
Postgres dùng MVCC (multi-version concurrency control, kiểm soát đồng thời đa phiên bản) và khóa cấp hàng. Nhiều giao dịch có thể ghi vào các hàng khác nhau cùng lúc mà không chặn nhau.
Trên thực tế:
- Một blog có 50 người đọc mỗi giây và một tác giả thỉnh thoảng viết bài? SQLite là đủ.
- Một trang thanh toán thương mại điện tử nơi hàng trăm người dùng cập nhật tồn kho cùng lúc? Postgres.
- Bộ nhớ đệm cục bộ của một ứng dụng di động? SQLite, không cần bàn.
- Backend SaaS nhiều khách hàng với hàng chục worker chạy nền? Postgres.
Chế độ WAL (PRAGMA journal_mode = WAL;) cải thiện đáng kể khả năng xử lý đồng thời của SQLite, vì tiến trình đọc không chặn tiến trình ghi, nhưng không thay đổi quy tắc mỗi lúc chỉ một tiến trình ghi.
Hệ thống kiểu: lỏng và chặt
Postgres rất chặt. Một cột khai báo INTEGER sẽ từ chối chuỗi, không có ngoại lệ:
-- Postgres
CREATE TABLE t (n INTEGER);
INSERT INTO t (n) VALUES ('not a number');
-- ERROR: invalid input syntax for type integer
Theo mặc định, SQLite dùng type affinity (ái lực kiểu): một gợi ý chứ không phải quy tắc. Cùng câu lệnh thêm đó vẫn thành công:
Chuỗi vẫn nằm đó trong một cột INTEGER. SQLite đã lưu nó dưới dạng văn bản. Sự linh hoạt này là một lựa chọn thiết kế có chủ đích: hữu ích cho prototype nhanh, nhưng nguy hiểm với schema dùng lâu dài.
SQLite hiện đại (3.37 trở lên) hỗ trợ bảng STRICT, hoạt động giống Postgres hơn:
Nếu bạn bắt đầu một dự án SQLite mới, hãy dùng STRICT. Nó loại bỏ cả một nhóm bất ngờ kiểu "sao lại có chuỗi trong cột số của mình".
Phạm vi tính năng
Postgres có nhiều hơn ở gần như mọi mặt: kiểu dữ liệu (mảng, khoảng giá trị, hình học, mạng, enum tự định nghĩa), ngôn ngữ thủ tục (PL/pgSQL, PL/Python), tìm kiếm toàn văn có xếp hạng, materialized view, phân vùng bảng, replication, bảo mật theo vai trò và một hệ sinh thái extension phong phú (PostGIS, TimescaleDB, pgvector).
SQLite bao quát những thứ thiết yếu và thêm vài tính năng hay phù hợp với quy mô của nó: hàm JSON, tìm kiếm toàn văn qua FTS5, index R-Tree, window function, CTE, cột generated. Những gì nó bỏ qua là mọi thứ giả định có máy chủ: người dùng, vai trò, replication, truy cập qua mạng.
Một cách hình dung đại khái:
- Cần GIS, tìm kiếm vector hay replication? Postgres.
- Cần đóng gói cơ sở dữ liệu vào một ứng dụng iOS? SQLite.
- Cần cả hai? Nhiều nhóm phát triển và kiểm thử với SQLite, rồi triển khai trên Postgres, dù cách kết hợp này có thể gây rắc rối vì khác biệt cú pháp (xem bên dưới).
Những khác biệt cú pháp bạn sẽ thực sự gặp
Phần lớn SQL hằng ngày là giống hệt nhau. Các khác biệt tập trung ở schema, kiểu dữ liệu và vài hàm có sẵn:
-- Auto-incrementing primary key
-- SQLite:
CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT);
-- Postgres:
CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT);
-- or, modern Postgres:
CREATE TABLE users (id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name TEXT);
-- Current timestamp
-- SQLite: CURRENT_TIMESTAMP (returns text)
-- Postgres: NOW() (returns timestamp)
-- Boolean type
-- SQLite: no real BOOLEAN; use INTEGER 0/1
-- Postgres: BOOLEAN with TRUE/FALSE
Nếu bạn phát triển trên SQLite và triển khai trên Postgres, nên đặt một ORM hoặc công cụ migration giữa bạn và SQL thô; nếu không, những khác biệt này sẽ lọt vào ứng dụng.
Nói thật về hiệu năng
"Nhanh hơn" tùy thuộc vào câu hỏi. Với một tiến trình đọc và ghi ít, SQLite rất khó bị đánh bại: không có bước nhảy qua mạng, không phân tích giao thức, không có kết nối máy khách. Trong các benchmark chỉ có một máy khách, SQLite thường chạy nhanh hơn Postgres với các truy vấn đơn giản.
Thêm vào nhiều tiến trình ghi đồng thời, tập dữ liệu lớn cần thực thi truy vấn song song, hoặc kế hoạch truy vấn phức tạp được lợi từ bộ lập kế hoạch trưởng thành của Postgres, thì Postgres sẽ dẫn trước. Postgres cũng mở rộng theo chiều dọc (máy lớn hơn, nhiều nhân hơn) theo cách mà SQLite đơn giản là không được thiết kế để làm.
Tóm lại một cách thẳng thắn: SQLite nhanh với đúng việc của nó. Postgres nhanh với đúng việc của nó. Hãy chọn theo đặc điểm khối lượng công việc, không theo tiêu đề benchmark.
Hướng dẫn quyết định nhanh
Chọn SQLite khi:
- Dữ liệu nằm cạnh một ứng dụng duy nhất: desktop, di động, thiết bị nhúng, công cụ CLI.
- Việc ghi đến từ một tiến trình hoặc một số ít tiến trình.
- Bạn muốn triển khai không cần cấu hình.
- Bạn đang làm prototype và muốn tập trung vào schema, không phải hạ tầng.
Chọn Postgres khi:
- Nhiều máy chủ ứng dụng hoặc worker cùng ghi vào cơ sở dữ liệu.
- Bạn cần truy cập qua mạng từ nhiều máy khách.
- Bạn cần tính năng nâng cao: vai trò, replication, GIS, kiểu tự định nghĩa, stored procedure.
- Dữ liệu là kho lưu trữ trung tâm, bền vững của một dịch vụ production.
Một hướng đi phổ biến: bắt đầu dự án nhỏ với SQLite, rồi chuyển sang Postgres nếu và khi lưu lượng đòi hỏi. Việc chuyển đổi không miễn phí, nhưng là thao tác đã biết rõ, và phần lớn dự án không bao giờ cần đến.
Tiếp theo: khi nào SQLite là lựa chọn đúng
Phần so sánh trên cho bạn thấy các đánh đổi. Trang tiếp theo đi sâu hơn vào lý do nên chọn SQLite: những khối lượng công việc mà nó không chỉ đủ tốt mà thực sự là công cụ tốt hơn, và những dấu hiệu cho thấy bạn đã vượt quá khả năng của nó.
Câu hỏi thường gặp
Khác biệt chính giữa SQLite và Postgres là gì?
SQLite là một thư viện nhúng, đọc và ghi một tệp duy nhất ngay trong tiến trình của ứng dụng. PostgreSQL là một máy chủ riêng mà bạn kết nối tới qua mạng. Chỉ một khác biệt kiến trúc đó đã quyết định gần như mọi điểm so sánh khác: xử lý đồng thời, triển khai, kiểu dữ liệu và công cụ đều bắt nguồn từ đó.
SQLite có nhanh hơn Postgres không?
Với việc đọc và ghi ít trong một tiến trình, thường là có: SQLite không có vòng gửi nhận qua mạng và không tốn chi phí giao thức máy khách/máy chủ. Với nhiều máy khách ghi đồng thời, Postgres vượt lên nhờ khóa cấp hàng và MVCC. 'Nhanh hơn' thực ra phụ thuộc vào khối lượng công việc, không phải vào bộ máy.
Có thể dùng SQLite trên production không?
Có, nếu khối lượng công việc phù hợp. SQLite vẫn chạy tốt cho trang web, ứng dụng desktop và thiết bị nhúng trên production. Ranh giới nằm ở số tiến trình ghi đồng thời: nếu nhiều tiến trình cần ghi cùng lúc, Postgres xử lý việc đó một cách tự nhiên, còn SQLite tuần tự hóa việc ghi. Chế độ WAL giúp ích nhưng không xóa bỏ giới hạn này.
Làm sao chuyển từ SQLite sang Postgres?
Xuất schema và dữ liệu bằng sqlite3 mydb.db .dump, rồi chỉnh lại SQL: AUTOINCREMENT thành SERIAL hoặc GENERATED AS IDENTITY, tên kiểu dữ liệu thay đổi, và vài điểm riêng của SQLite như kiểu lỏng lẻo cần được dọn dẹp. Các công cụ như pgloader tự động hóa phần lớn việc này. Hãy chuẩn bị viết lại mọi thứ dựa vào kiểu dữ liệu linh hoạt của SQLite.