Hướng dẫn sử dụng SQLite với Python: Database nhẹ cho script tự động hóa và ứng dụng nhỏ

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

Quick Start (Làm ngay trong 5 phút)

2 giờ sáng, server báo lỗi, một batch script chạy hàng đêm ngưng ngang vì thiếu config. Mình cần một chỗ lưu tạm vài thông số nhanh gọn mà không muốn động chạm database production. Lúc đó, SQLite chính là cứu cánh. Không cần cài đặt, không cần cấu hình, chỉ cần một file duy nhất. Đây là cách mình thường dùng để có ngay một database trong chưa đầy 5 phút:

Bước 1: Kết nối (hoặc tạo mới) database

import sqlite3

# Kết nối đến database, nếu file 'my_app.db' chưa có sẽ tự động tạo
# Dùng ':memory:' nếu muốn database chỉ tồn tại trong RAM
conn = sqlite3.connect('my_app.db')
cursor = conn.cursor()
print("Kết nối database thành công!")

Bước 2: Tạo bảng

Cần tạo một bảng để lưu dữ liệu. Giả sử mình muốn lưu thông tin lỗi của script:

cursor.execute('''
    CREATE TABLE IF NOT EXISTS error_logs (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        timestamp TEXT NOT NULL,
        error_message TEXT NOT NULL,
        severity TEXT
    )
''')
conn.commit()
print("Tạo bảng 'error_logs' thành công!")

Bước 3: Chèn dữ liệu

Ghi lại lỗi. Luôn dùng tham số hóa để tránh SQL injection. Mình đã từng mất cả buổi mò lỗi vì quên cái này:

import datetime

now = datetime.datetime.now().isoformat()
error_msg = "Lỗi đọc file config: file not found"
severity = "CRITICAL"

cursor.execute("INSERT INTO error_logs (timestamp, error_message, severity) VALUES (?, ?, ?)",
               (now, error_msg, severity))
conn.commit()
print("Chèn dữ liệu thành công!")

Bước 4: Truy vấn dữ liệu

Xem lại các lỗi đã ghi nhận:

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

for row in rows:
    print(row)

print("\nCác lỗi CRITICAL:")
cursor.execute("SELECT * FROM error_logs WHERE severity = ?", ("CRITICAL",))
critical_errors = cursor.fetchall()
for error in critical_errors:
    print(error)

Bước 5: Đừng quên đóng kết nối

Sau khi xong việc, luôn đóng kết nối để giải phóng tài nguyên, tránh lỗi lock file hoặc dữ liệu không ghi đầy đủ:

conn.close()
print("Đã đóng kết nối database.")

Chỉ trong vài dòng code Python, bạn đã có một database đầy đủ chức năng. Nhanh gọn lẹ để cứu nguy lúc nửa đêm.

Giải thích chi tiết (Mổ xẻ từng mảnh)

Mình đã làm việc với MySQL, PostgreSQL và MongoDB — mỗi cái có điểm mạnh riêng. MySQL nhanh, dễ dùng cho web apps. PostgreSQL xịn hơn với tính năng enterprise, độ tin cậy cao. MongoDB linh hoạt với dữ liệu phi cấu trúc. Nhưng có những lúc 2 giờ sáng, cần một chỗ lưu tạm vài dữ liệu config hay log nhanh mà không muốn động chạm database production to đùng, lúc đó SQLite đúng là vị cứu tinh.

SQLite là gì và tại sao lại dùng nó?

SQLite là một thư viện C nhỏ gọn, tự chứa, không yêu cầu server riêng hay cấu hình phức tạp. Toàn bộ database chỉ là một file duy nhất trên đĩa cứng (hoặc trong RAM). Nó là một database mạnh mẽ, tuân thủ chuẩn ACID.

Ưu điểm nổi bật:

  • Zero-configuration: Chỉ cần import sqlite3 trong Python.
  • File-based: Database nằm trong một file. Dễ dàng sao lưu, di chuyển, chia sẻ.
  • Nhỏ gọn và nhanh: Thư viện nhẹ, hiệu năng tốt cho tác vụ đơn người dùng.
  • Miễn phí và mã nguồn mở.
  • Tích hợp sẵn trong Python.

Khi nào SQLite tỏa sáng?

  • Script tự động hóa/Tools nội bộ: Lưu log, cache, cấu hình cho script Python.
  • Ứng dụng desktop/mobile: Lưu trữ dữ liệu offline.
  • Phát triển và testing: Database nhanh gọn để test tính năng dữ liệu.
  • Website nhẹ: Website ít truy cập, không cần nhiều người dùng ghi dữ liệu đồng thời.

Cấu trúc bảng và kiểu dữ liệu

SQLite hỗ trợ các kiểu dữ liệu cơ bản: NULL, INTEGER, REAL, TEXT, BLOB. Cú pháp tạo bảng quen thuộc:

CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    username TEXT NOT NULL UNIQUE,
    email TEXT
);
  • PRIMARY KEY, AUTOINCREMENT, NOT NULL, UNIQUE: Các ràng buộc cơ bản.

Thao tác cơ bản với Python (CRUD: Create, Read, Update, Delete)

Kết nối và đóng kết nối

Luôn nhớ conn.commit() để lưu thay đổi và dùng with statement để đảm bảo an toàn:

import sqlite3

try:
    with sqlite3.connect('my_app.db') as conn:
        cursor = conn.cursor()
        # Thao tác database...
        cursor.execute("INSERT INTO error_logs (timestamp, error_message, severity) VALUES (?, ?, ?)",
                       ("2026-03-22T14:30:00", "Lỗi test", "INFO"))
        # commit/rollback tự động xử lý khi thoát khối 'with'
except sqlite3.Error as e:
    print(f"Có lỗi xảy ra: {e}")

Chèn dữ liệu (INSERT)

Dùng dấu ? hoặc :placeholder cho các giá trị để chống SQL injection:

cursor.execute("INSERT INTO users (username, email) VALUES (?, ?)", ("nguyenvana", "[email protected]"))
cursor.execute("INSERT INTO users (username, email) VALUES (:user, :email)",
               {"user": "lethib", "email": "[email protected]"})
conn.commit()

Truy vấn dữ liệu (SELECT)

Kết quả trả về là list các tuple theo mặc định:

cursor.execute("SELECT id, username, email FROM users WHERE username LIKE 'nguyen%'")
users = cursor.fetchall()
for user in users:
    print(f"ID: {user[0]}, Username: {user[1]}, Email: {user[2]}")

Cập nhật dữ liệu (UPDATE)

cursor.execute("UPDATE users SET email = ? WHERE username = ?",
               ("[email protected]", "nguyenvana"))
conn.commit()

Xóa dữ liệu (DELETE)

cursor.execute("DELETE FROM users WHERE username = ?", ("lethib",))
conn.commit()

Luôn nhớ conn.commit() sau mỗi thao tác thay đổi dữ liệu.

Nâng cao (Để khỏi phải thức khuya lần nữa)

Khi script phức tạp hơn, hoặc lượng dữ liệu lớn dần, bạn sẽ cần vài kỹ thuật để mọi thứ mượt mà. Đừng để database ì ạch rồi lại phải thức đêm debug.

Tối ưu truy vấn với Indexes (Chỉ mục)

Index giúp database tìm kiếm dữ liệu nhanh hơn, đặc biệt trên các cột dùng trong WHERE hoặc ORDER BY. Nếu query chạy chậm, rất có thể là do thiếu index.

CREATE INDEX idx_error_severity ON error_logs (severity);
CREATE INDEX idx_users_username ON users (username);

Index cũng tốn tài nguyên khi thêm, sửa, xóa dữ liệu. Chỉ tạo index trên các cột thực sự cần thiết.

Chế độ kết nối và Journal Mode (Hiệu năng khi cần)

Mặc định, SQLite đảm bảo tính toàn vẹn dữ liệu cao, nhưng đôi khi ảnh hưởng hiệu năng. Có thể tinh chỉnh khi biết chắc mình đang làm gì (ví dụ: import lượng lớn dữ liệu offline):

cursor.execute("PRAGMA journal_mode = WAL;") # Write-Ahead Logging
cursor.execute("PRAGMA synchronous = NORMAL;") # Hoặc OFF (cẩn thận!)
  • journal_mode = WAL: Tăng hiệu năng ghi (WRITE) và cho phép nhiều reader đọc cùng lúc khi có writer. Khuyến nghị cho hầu hết trường hợp cần hiệu năng cao hơn.
  • synchronous = OFF: Hủy bỏ đồng bộ hóa dữ liệu xuống đĩa ngay lập tức. Cực kỳ nguy hiểm nếu mất điện, dữ liệu có thể bị mất/hỏng. Chỉ dùng khi hiểu rõ rủi ro. NORMAL là cân bằng tốt hơn.

Sử dụng Row objects (Truy cập dữ liệu bằng tên cột)

Mặc định, cursor.fetchone()/fetchall() trả về tuple (row[0]). Dùng sqlite3.Row để truy cập bằng tên cột (row['column_name']), giúp code dễ đọc và bảo trì hơn:

with sqlite3.connect('my_app.db') as conn:
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()

    cursor.execute("SELECT id, username, email FROM users WHERE id = 1")
    user = cursor.fetchone()

    if user:
        print(f"User ID: {user['id']}, Username: {user['username']}, Email: {user['email']}")

Tips thực tế (Kinh nghiệm xương máu)

Sau nhiều đêm vật lộn với các loại database, mình rút ra vài điều quan trọng khi làm việc với SQLite:

  • Đường dẫn database: Luôn cẩn thận với đường dẫn file database. Nếu bạn chỉ dùng sqlite3.connect('my_db.db'), nó sẽ tạo file my_db.db ngay trong thư mục script Python chạy. Tốt nhất là dùng đường dẫn tuyệt đối hoặc định nghĩa rõ ràng trong config để tránh tình trạng “không thấy database đâu”.
  • Sao lưu (Backup) dễ dàng: Điểm mạnh lớn nhất của SQLite là sao lưu. Chỉ cần copy file .db là có bản backup hoàn chỉnh. Nhưng nhớ đảm bảo không có quá trình nào đang ghi vào database khi sao lưu, nếu không file có thể hỏng. Hoặc dùng Online Backup API của SQLite nếu cần sao lưu khi database đang hoạt động.
  • Xử lý đồng thời (Concurrency): Đây là điểm yếu cố hữu của SQLite. Nó chỉ hỗ trợ một writer duy nhất tại một thời điểm. Nghĩa là, nếu nhiều tiến trình/thread ghi vào database cùng lúc, các tiến trình khác sẽ phải chờ hoặc nhận lỗi database is locked. Nếu ứng dụng cần hàng trăm hay hàng nghìn request ghi dữ liệu đồng thời, SQLite không phải lựa chọn đúng đắn. Hãy xem xét các database server thực thụ như MySQL, PostgreSQL, hoặc MongoDB mà mình đã nhắc đến.
  • Các công cụ quản lý: Mình hay dùng DBeaver hoặc DB Browser for SQLite. Chúng giúp bạn dễ dàng xem cấu trúc bảng, dữ liệu, chạy query mà không cần viết code Python.
  • Khi nào thì KHÔNG nên dùng SQLite?
    • Ứng dụng web cần khả năng mở rộng cao và nhiều người dùng ghi đồng thời: Các database server truyền thống như MySQL, PostgreSQL giải quyết tốt hơn.
    • Dữ liệu quá lớn (terabytes): Quản lý một file .db hàng trăm GB/TB khó khăn hơn.
    • Cần truy cập dữ liệu qua mạng: SQLite không có khả năng truy cập từ xa qua mạng. Nếu cần nhiều server cùng truy cập một database, bạn cần database server.
  • Tích hợp vào script tự động hóa: Mình thường dùng SQLite để lưu trạng thái của script (ví dụ: đã xử lý đến bản ghi nào), log lỗi chi tiết, hoặc làm cache cho các API call tốn kém. Nó rất hiệu quả và đáng tin cậy.

Nhớ những điểm này, bạn sẽ tránh được nhiều rắc rối và tối ưu được việc sử dụng SQLite trong các dự án Python của mình. Đừng để nó thành cái bẫy bắt bạn phải thức khuya nữa!

Share: