Menu

SQLite in-memory: cơ sở dữ liệu tạm, siêu nhanh với :memory:

Cách cơ sở dữ liệu trong bộ nhớ của SQLite hoạt động, khi nào nên dùng :memory:, và nó khác gì với cơ sở dữ liệu lưu trong tệp mà bạn giữ lại được.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Cơ sở dữ liệu nằm trong RAM

SQLite có một tên tệp đặc biệt: :memory:. Mở cơ sở dữ liệu với tên này, SQLite bỏ qua hoàn toàn ổ đĩa: toàn bộ cơ sở dữ liệu nằm trong RAM. Bảng, index, transaction, khóa ngoại, mọi tính năng đều hoạt động y hệt. Khác biệt duy nhất là khi kết nối đóng lại, cơ sở dữ liệu cũng biến mất.

Từ dòng lệnh:

sqlite3 :memory:

Giờ bạn đang ở dấu nhắc SQLite với một cơ sở dữ liệu mới tinh, trống trơn, chỉ tồn tại trong bộ nhớ. Tạo bảng, thêm vài hàng, truy vấn chúng, mọi thứ đều bình thường:

Thoát phiên làm việc là dữ liệu đó bay hơi. Không có tệp nào còn lại, vì chưa từng có tệp nào được tạo ra.

Vì sao bạn cần đến nó

Một cơ sở dữ liệu không sống sót qua lần khởi động lại nghe giống bug hơn là tính năng. Thực ra nó rất hữu ích trong ba tình huống.

Kiểm thử. Mỗi bài test có một cơ sở dữ liệu sạch chỉ trong vài mili giây. Không phải dọn tệp tạm, không còn trạng thái sót lại từ lần chạy trước, không có tệp fixture dùng chung bị khóa. Hầu hết bộ test Python, Node và Go dùng SQLite đều mở :memory: chính vì lý do này.

Phân tích dùng một lần. Nạp một tệp CSV, chạy vài truy vấn, rồi bỏ đi. Nhanh hơn dựng một cơ sở dữ liệu thật và dễ hơn phân tích tệp bằng code mỗi lần.

Bộ nhớ đệm và vùng nháp. Bên trong một chương trình chạy lâu, cơ sở dữ liệu SQLite trong bộ nhớ là một công cụ truy vấn tùy biến tốt đến bất ngờ cho dữ liệu bạn đã nạp sẵn.

Điểm chung: bạn muốn có SQL, nhưng không cần lưu trữ lâu dài.

Hiệu năng: nhanh hơn, nhưng không phải phép màu

Cơ sở dữ liệu trong bộ nhớ bỏ qua ổ đĩa, nên những thao tác ghi lẽ ra phải chạm đến hệ thống tệp giờ chỉ là cập nhật bộ nhớ. Các tác vụ bị giới hạn bởi I/O nhanh lên rõ rệt. Các tác vụ bị giới hạn bởi CPU (lập kế hoạch truy vấn phức tạp, sắp xếp lớn) gần như không đổi, vì SQLite vốn đã lưu các trang hay dùng trong bộ nhớ đệm.

Minh họa nhanh cho thấy cú pháp giống hệt nhau:

Đoạn code trên chạy trên cơ sở dữ liệu trong bộ nhớ, nhưng đó cũng chính là câu SQL bạn sẽ chạy trên một tệp. Engine cơ sở dữ liệu không quan tâm.

Trong bộ nhớ hay trong tệp: khi nào chọn cái nào

Sự đánh đổi rất đơn giản, và nên nói rõ ra:

  • Cơ sở dữ liệu dạng tệp (mydata.db): tồn tại qua các lần khởi động lại. Nhiều tiến trình có thể cùng mở. Sống sót khi bị crash (với chế độ WAL, phần lớn là vậy). Dùng cho mọi thứ cần ghi nhớ dữ liệu.
  • Cơ sở dữ liệu trong bộ nhớ (:memory:): biến mất khi đóng. Riêng tư với kết nối đã mở nó (theo mặc định). Nhanh hơn cho các tác vụ ghi nhiều, dùng xong bỏ. Dùng cho kiểm thử, việc nháp và bộ nhớ đệm ngắn hạn.

Nếu không chắc, bạn cần một tệp. Trong bộ nhớ là trường hợp đặc biệt.

Mỗi kết nối có cơ sở dữ liệu riêng

Một điểm tinh tế hay khiến người dùng vấp: mở :memory: hai lần cho bạn hai cơ sở dữ liệu riêng biệt. Chúng không dùng chung bảng, không dùng chung dữ liệu, hoàn toàn không thấy nhau.

-- Terminal 1
sqlite3 :memory:
sqlite> CREATE TABLE t (x); INSERT INTO t VALUES (1);

-- Terminal 2
sqlite3 :memory:
sqlite> SELECT * FROM t;
Error: no such table: t

Đó không phải bug mà là thiết kế. :memory: nghĩa là "một cơ sở dữ liệu riêng cho kết nối này". Điều tương tự áp dụng bên trong một chương trình: nếu code mở hai kết nối đến :memory:, mỗi kết nối có một cơ sở dữ liệu tách biệt.

Dùng chung cơ sở dữ liệu trong bộ nhớ giữa các kết nối

Nếu bạn thực sự cần nhiều kết nối cùng thấy một cơ sở dữ liệu trong bộ nhớ, SQLite hỗ trợ điều đó qua tên tệp dạng URI và shared cache. Chuỗi đặc biệt là file::memory:?cache=shared:

sqlite3 'file::memory:?cache=shared'

Mọi kết nối trong cùng tiến trình mở đúng URI đó sẽ tham gia vào cùng một cơ sở dữ liệu. Đóng hết các kết nối là cơ sở dữ liệu biến mất.

Bạn cũng có thể đặt tên cho cơ sở dữ liệu trong bộ nhớ, hữu ích khi bạn muốn có nhiều cơ sở dữ liệu dùng chung khác nhau:

sqlite3 'file:mydb?mode=memory&cache=shared'

Tên mydb ở đây chỉ là nhãn: vẫn không có tệp nào cả. Hai kết nối mở file:mydb?mode=memory&cache=shared dùng chung một cơ sở dữ liệu; kết nối mở file:other?mode=memory&cache=shared nhận một cơ sở dữ liệu khác.

Lưu cơ sở dữ liệu trong bộ nhớ xuống đĩa

Đôi khi bạn làm cả quy trình trong bộ nhớ rồi quyết định muốn giữ lại kết quả. CLI có lệnh chấm .backup cho việc này:

sqlite3 :memory:
sqlite> CREATE TABLE results (id INTEGER, score REAL);
sqlite> INSERT INTO results VALUES (1, 0.91), (2, 0.87);
sqlite> .backup snapshot.db
sqlite> .quit

Giờ snapshot.db là một cơ sở dữ liệu dạng tệp bình thường với nội dung y hệt. Bạn có thể mở nó sau này bằng sqlite3 snapshot.db và làm tiếp từ chỗ đã dừng.

Chiều ngược lại cũng được: .restore nạp một cơ sở dữ liệu dạng tệp vào bộ nhớ của kết nối hiện tại:

sqlite3 :memory:
sqlite> .restore snapshot.db
sqlite> SELECT * FROM results;

Từ code ứng dụng, API C của SQLite cung cấp cùng cơ chế này qua sqlite3_backup_init, và hầu hết thư viện cho các ngôn ngữ đều bọc lại nó. Ví dụ, module sqlite3 của Python có Connection.backup().

Một cạm bẫy thường gặp

Đôi khi người ta cố "lưu" cơ sở dữ liệu trong bộ nhớ bằng cách gắn thêm một tệp rồi chép dữ liệu:

Cách đó dùng được để chép bảng đơn giản, nhưng không giữ nguyên index, trigger, view hay khóa ngoại như ban đầu. Để có bản sao trung thực của toàn bộ cơ sở dữ liệu, hãy dùng .backup (hoặc API sao lưu): nó chép chính xác từng byte ở cấp trang.

Những điều cần nhớ

  • :memory: là tên tệp đặc biệt của SQLite, tạo cơ sở dữ liệu trong RAM mà không cần tệp.
  • SQL giống hệt với cơ sở dữ liệu dạng tệp: cùng bảng, cùng truy vấn, cùng ràng buộc.
  • Mỗi kết nối đến :memory: là riêng tư; dùng URI shared cache (file::memory:?cache=shared) khi nhiều kết nối cần dùng chung.
  • Đây là công cụ phù hợp cho kiểm thử, phân tích dùng một lần và bộ nhớ đệm ngắn hạn, không dành cho thứ gì cần tồn tại qua lần khởi động lại.
  • Chuyển cơ sở dữ liệu trong bộ nhớ xuống đĩa bằng .backup khi bạn quyết định giữ lại nó.

Tiếp theo: tạo bảng

Bạn đã thấy CREATE TABLE lướt qua trong vài ví dụ. Trang tiếp theo sẽ đi chậm lại và giải thích nó kỹ càng: định nghĩa cột, kiểu dữ liệu, các ràng buộc bạn có thể gắn vào, và những lựa chọn nhỏ giúp cấu trúc bảng dễ sống cùng về lâu dài.

Câu hỏi thường gặp

Làm sao tạo cơ sở dữ liệu SQLite trong bộ nhớ?

Mở SQLite với tên tệp đặc biệt :memory: thay vì một đường dẫn. Từ CLI là sqlite3 :memory:; từ thư viện thì là lệnh kết nối tương ứng, với :memory: làm tên tệp. Cơ sở dữ liệu nằm trong RAM và biến mất khi kết nối đóng lại.

:memory: trong SQLite là gì?

:memory: là tên tệp đặc biệt mà SQLite hiểu là 'đừng dùng tệp, giữ mọi thứ trong RAM'. Bạn có một cơ sở dữ liệu SQLite đầy đủ (bảng, index, transaction, mọi thứ) nhưng không có gì được ghi xuống đĩa. Mỗi kết nối mở :memory: có cơ sở dữ liệu riêng của nó.

Hai kết nối có thể dùng chung một cơ sở dữ liệu SQLite trong bộ nhớ không?

Mặc định thì không: mỗi kết nối :memory: là tách biệt. Để dùng chung, hãy mở bằng URI như file::memory:?cache=shared và bật shared cache. Mọi kết nối mở đúng URI đó trong cùng một tiến trình đều thấy cùng một cơ sở dữ liệu.

Có thể lưu cơ sở dữ liệu SQLite trong bộ nhớ xuống đĩa không?

Có. Dùng lệnh .backup trong CLI hoặc API sao lưu trong thư viện của bạn để chép cơ sở dữ liệu trong bộ nhớ ra một tệp. Bạn cũng có thể ATTACH một cơ sở dữ liệu dạng tệp và chạy INSERT INTO file.table SELECT * FROM main.table để chép dữ liệu sang.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu