SQLite — Cơ Sở Dữ Liệu Nhẹ Nhưng Mạnh Hơn Bạn Nghĩ

Database tutorial - IT technology blog
Database tutorial - IT technology blog

Vấn Đề Dùng Database Quá Mức Cần Thiết

Bạn đang xây dựng một công cụ nội bộ nhỏ — theo dõi task, lưu trữ config cục bộ, hay một pipeline xử lý dữ liệu nhẹ. Theo thói quen, bạn với tay lấy PostgreSQL hoặc MySQL. Rồi bỗng nhiên bạn phải khởi động Docker container, tạo user, cấu hình connection pool, viết schema migration… chỉ để phục vụ ba người dùng.

Sai tool cho bài toán này. Vấn đề không phải là thiếu tính năng — mà là kéo cả hạ tầng server-grade vào một bài toán không cần đến nó. SQLite tồn tại chính xác cho những trường hợp như thế này.

Tôi đã dùng MySQL, PostgreSQL và MongoDB qua nhiều dự án khác nhau. Mỗi cái đều có điểm mạnh thực sự. Nhưng có điều cứ khiến tôi bất ngờ lúc ban đầu: SQLite thường là câu trả lời đúng, vậy mà tôi cứ bỏ qua nó.

Kiến Trúc: SQLite vs. Database Dạng Server

Để dùng SQLite hiệu quả, bạn cần hiểu nó khác MySQL hay Postgres đến mức nào — không chỉ về quy mô, mà về thiết kế.

Database Dạng Server (MySQL, PostgreSQL)

  • Cần có tiến trình daemon đang chạy
  • Dùng giao tiếp client-server (TCP hoặc Unix socket)
  • Được thiết kế cho truy cập đa người dùng đồng thời
  • Cần cài đặt riêng, quản lý user và cấu hình backup
  • Lý tưởng cho ứng dụng web với nhiều kết nối đồng thời

SQLite

  • Không cần server — engine database là một thư viện liên kết trực tiếp vào ứng dụng của bạn
  • Toàn bộ database nằm trong một file .db duy nhất
  • Truy cập theo tiến trình (với giới hạn ghi đồng thời)
  • Đi kèm với Python, có trong Android/iOS SDK, nhúng bên trong trình duyệt
  • Lý tưởng cho ứng dụng đơn người dùng, hệ thống nhúng, prototype, lưu trữ dữ liệu cục bộ

SQLite không phải là “MySQL thu nhỏ”. Đây là một loại tool hoàn toàn khác. So sánh trực tiếp hai cái này là lạc đề — chúng giải quyết những bài toán khác nhau.

Ưu và Nhược Điểm

Tại Sao SQLite Hoạt Động Tốt

  • Không cần cài đặt — Python bao gồm sẵn sqlite3 trong thư viện chuẩn. Không cần cài thêm gì.
  • Dễ mang theo — database chỉ là một file. Copy, di chuyển, hoặc commit vào version control nếu đủ nhỏ.
  • Nhanh cho đọc — với dataset nhỏ, nặng về đọc, SQLite thường nhanh hơn Postgres vì không có overhead network round-trip. Query cục bộ bỏ qua hoàn toàn TCP stack.
  • Độ tin cậy vượt trội — bộ test của SQLite có hơn 92 triệu dòng code kiểm thử. Nó chạy trong hệ thống bay của máy bay, thiết bị y tế, và hàng tỷ ứng dụng di động. Tiêu chuẩn độ tin cậy thực sự rất cao.
  • Tuân thủ ACID — hỗ trợ transaction đầy đủ: commit, rollback, savepoint và foreign key. Bắt buộc FK mặc định tắt nhưng dễ bật cho từng kết nối.

Hạn Chế

  • Ghi đồng thời — SQLite dùng khóa ghi ở mức database. Nhiều writer sẽ xếp hàng hoặc báo lỗi “database is locked” khi có tải ghi thực sự.
  • Không có kiểm soát truy cập — bất kỳ ai có quyền truy cập file đều có thể đọc database. Với ứng dụng multi-tenant, điều này không thể chấp nhận được.
  • Hạn chế ALTER TABLE — các phiên bản cũ làm cho việc thay đổi schema rất khó khăn. SQLite 3.35 (tháng 3 năm 2021) cải thiện điều này với hỗ trợ DROP COLUMN đúng chuẩn, nhưng vẫn thua Postgres về tính linh hoạt schema.
  • Không được thiết kế cho hệ thống phân tán — replication, clustering, tự động failover — đây không phải lãnh địa của SQLite.

Khi Nào SQLite Là Lựa Chọn Đúng

Đây là những trường hợp nó luôn xứng đáng được chọn:

  • Công cụ CLI cần lưu trữ dữ liệu lâu dài
  • Ứng dụng desktop (Electron, PyQt, v.v.)
  • Môi trường phát triển cục bộ (dùng Postgres cho production)
  • Pipeline dữ liệu và script ETL lưu kết quả trung gian
  • Bộ test — database nhanh, độc lập và có thể loại bỏ sau dùng
  • Prototype trước khi biết mình thực sự cần database nào

Cấu hình tôi thường dùng: ứng dụng Django chạy với SQLite trong quá trình phát triển. Migration hoàn thành trong chưa đến một giây, test hoàn toàn độc lập, và không có phụ thuộc Docker trên máy cục bộ. Khi schema ổn định và cần concurrency thực sự, một thay đổi config duy nhất là đủ để chuyển sang Postgres trên staging và production. Django xử lý phần còn lại.

Hướng Dẫn Triển Khai

Sử Dụng Cơ Bản Trong Python (Không Cần Thư Viện Ngoài)

Module sqlite3 của Python là tất cả những gì bạn cần:

import sqlite3

# Mở DB đã có hoặc tạo mới
conn = sqlite3.connect("myapp.db")
cursor = conn.cursor()

# Tạo bảng
cursor.execute("""
    CREATE TABLE IF NOT EXISTS tasks (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        done INTEGER DEFAULT 0,
        created_at TEXT DEFAULT (datetime('now'))
    )
""")
conn.commit()

# Chèn dữ liệu
cursor.execute("INSERT INTO tasks (title) VALUES (?)", ("Viết hướng dẫn SQLite",))
conn.commit()

# Truy vấn
cursor.execute("SELECT * FROM tasks WHERE done = 0")
rows = cursor.fetchall()
for row in rows:
    print(row)

conn.close()

Lưu ý placeholder ?. Đừng bao giờ dùng f-string để xây dựng câu query SQL — parameterized query chặn SQL injection ngay cả trong công cụ cục bộ. Thói quen tốt không tốn gì cả.

Bật Kiểm Tra Foreign Key

Hỗ trợ foreign key đã được tích hợp vào SQLite, nhưng mặc định tắt để đảm bảo tương thích ngược. Bật nó lên ở đầu mỗi kết nối:

conn = sqlite3.connect("myapp.db")
conn.execute("PRAGMA foreign_keys = ON")

Dùng Context Manager

Kết nối SQLite hoạt động như Python context manager. Block thành công? Tự commit. Ngoại lệ? Tự rollback:

with sqlite3.connect("myapp.db") as conn:
    conn.execute("INSERT INTO tasks (title) VALUES (?)", ("Triển khai lên production",))
    # tự commit nếu thành công, tự rollback nếu có lỗi

Truy Cập Row Kiểu Dict Với row_factory

Mặc định các row trả về dạng tuple — dùng được, nhưng đọc không được thuận tiện. Đặt row_factory để truy cập cột theo tên thay vào đó:

conn = sqlite3.connect("myapp.db")
conn.row_factory = sqlite3.Row

cursor = conn.cursor()
cursor.execute("SELECT * FROM tasks")
rows = cursor.fetchall()

for row in rows:
    print(row["title"], row["done"])  # truy cập theo tên cột

Kiểm Tra Database Từ CLI

Đã cài sqlite3 CLI chưa? Bạn có thể khám phá bất kỳ database nào mà không cần viết một dòng Python nào:

# Mở database
sqlite3 myapp.db

# Trong SQLite shell:
.tables              # liệt kê tất cả bảng
.schema tasks        # hiển thị CREATE TABLE của bảng tasks
SELECT * FROM tasks LIMIT 10;
.quit
# Cài đặt trên Ubuntu/Debian
sudo apt install sqlite3

# macOS đã có sẵn
sqlite3 --version

SQLite Với SQLAlchemy

SQLAlchemy là bộ công cụ SQL và ORM Python được dùng rộng rãi. Một trong những lợi thế thực tế: chuyển đổi giữa SQLite và Postgres chỉ là một thay đổi connection string, không cần viết lại code:

# SQLite (môi trường local/dev)
DATABASE_URL = "sqlite:///./myapp.db"

# PostgreSQL (production)
DATABASE_URL = "postgresql://user:pass@localhost/myapp"

# Trong SQLAlchemy
from sqlalchemy import create_engine
engine = create_engine(DATABASE_URL)

SQLite Trong RAM Cho Testing

Truyền ":memory:" làm tên database và SQLite sẽ tạo toàn bộ database trong RAM. Không ghi gì xuống đĩa, không cần dọn dẹp:

import sqlite3

def get_test_db():
    conn = sqlite3.connect(":memory:")
    conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
    return conn

def test_insert_user():
    conn = get_test_db()
    conn.execute("INSERT INTO users (name) VALUES (?)", ("alice",))
    row = conn.execute("SELECT name FROM users").fetchone()
    assert row[0] == "alice"

Mỗi lần chạy test đều bắt đầu sạch. Không cần teardown, không có state thừa, không có lỗi ngẫu nhiên từ các row database dùng chung.

Khi Nào Nên Chuyển Từ SQLite Sang Công Cụ Khác

Những hạn chế bắt đầu lộ rõ khi:

  • Nhiều tiến trình cần ghi vào database đồng thời
  • Tải ghi đủ cao để gây ra xung đột khóa thường xuyên
  • Bạn cần kiểm soát truy cập ở mức row
  • Bạn triển khai lên môi trường container nơi filesystem không persistent

PostgreSQL là con đường nâng cấp tự nhiên. Nếu bạn xây dựng trên SQLAlchemy hoặc Django ORM ngay từ đầu, việc migration hầu như chỉ là một giá trị config. Code query thường giữ nguyên.

Với mọi thứ còn lại — công cụ cục bộ, script, prototype, ứng dụng nhúng, bộ test — SQLite là lựa chọn mặc định đúng đắn. Nó giúp bạn tránh xây dựng hạ tầng mà dự án không cần.

Share: