SQLite là một cơ sở dữ liệu quan hệ nhúng hoạt động mà không cần tiến trình máy chủ riêng biệt và lưu trữ toàn bộ cơ sở dữ liệu trong một tệp duy nhất trên thiết bị. Nhờ cấu hình bằng không, kích thước thư viện nhỏ và hỗ trợ SQL đầy đủ, SQLite đã trở thành tiêu chuẩn cho lưu trữ dữ liệu cục bộ trong các ứng dụng di động. Theo SQLite Consortium (2025), DBMS này được sử dụng trên hơn 4 tỷ thiết bị, bao gồm mọi điện thoại thông minh trên iOS và Android.
Những điểm chính
SQLite là một thư viện ngôn ngữ C triển khai DBMS quan hệ không cần máy chủ chuyên dụng. Nó được nhúng trực tiếp vào ứng dụng, đọc và ghi dữ liệu vào một tệp thông thường trên hệ thống tệp của thiết bị. Kích thước thư viện khoảng 600 KB, khiến SQLite trở thành cơ sở dữ liệu SQL đầy đủ tính năng nhẹ nhất.
SQLite hỗ trợ hầu hết tiêu chuẩn SQL:1999, bao gồm JOIN, truy vấn con, trigger, view, chỉ mục và hàm cửa sổ. Các hạn chế liên quan đến ALTER TABLE (hỗ trợ hạn chế) và RIGHT/FULL OUTER JOIN đầy đủ. Tuy nhiên, đối với ứng dụng di động, chức năng của SQLite đủ dùng trong 99% trường hợp lưu trữ cục bộ.
Theo khảo sát nhà phát triển Stack Overflow (2025), SQLite là cơ sở dữ liệu phổ biến nhất cho các giải pháp nhúng và đứng thứ ba về mức độ phổ biến trong tất cả DBMS sau MySQL và PostgreSQL. Trong phát triển di động, SQLite được sử dụng trong mọi ứng dụng — trực tiếp hoặc thông qua các trình bao bọc.
Cấu hình bằng không — SQLite không yêu cầu cài đặt, thiết lập quyền, tạo người dùng hay khởi động dịch vụ. Thư viện được liên kết với dự án và cơ sở dữ liệu được tạo bằng cách gọi một hàm duy nhất. Điều này giúp đơn giản hóa triển khai một cách triệt để so với các DBMS client-server yêu cầu cài đặt máy chủ, cấu hình cổng và thiết lập người dùng.
Tệp cơ sở dữ liệu SQLite là một tệp đa nền tảng thông thường có thể được sao chép, phân tích, gửi qua mạng hoặc khôi phục từ bản sao lưu. Định dạng tệp ổn định ở cấp API: các tệp SQLite 3 được tạo năm 2004 vẫn mở được bằng phiên bản thư viện hiện tại, đảm bảo khả năng tương thích dữ liệu lâu dài.
Kiến trúc của SQLite bao gồm tám máy ảo: Tokenizer, Parser, Code Generator, VM, B-Tree, Pager, OS Interface và Utilities. Một truy vấn SQL đi qua Tokenizer (chia thành token), Parser (xây dựng AST), Code Generator (chuyển đổi thành bytecode) và được thực thi trên máy ảo, máy này đọc các trang dữ liệu thông qua B-Tree và Pager.
SQLite sử dụng B-Tree để lưu trữ bảng và chỉ mục. Mỗi bảng được lưu trữ dưới dạng một B-Tree riêng biệt, trong đó các nút lá chứa các hàng dữ liệu. Các chỉ mục cũng được lưu trữ dưới dạng B-Tree nhưng với các khóa trong lá. Pager quản lý việc tải các trang (mặc định 4096 byte) từ tệp vào bộ nhớ, cung cấp các giao dịch ACID thông qua nhật ký hoặc WAL.
WAL (Write-Ahead Logging) là chế độ được khuyến nghị cho ứng dụng di động. Các thay đổi trước tiên được ghi vào một tệp WAL riêng biệt, sau đó định kỳ được chuyển vào cơ sở dữ liệu chính. WAL cho phép đọc (dữ liệu cũ) và ghi (qua WAL) đồng thời vào cơ sở dữ liệu, cải thiện hiệu suất của các ứng dụng đa luồng. Nhật ký tiêu chuẩn (rollback journal) chặn đọc trong khi ghi.
| Tham số | Rollback Journal | WAL (Write-Ahead Logging) |
|---|---|---|
| Đọc trong khi ghi | Bị chặn | Được phép (đọc dữ liệu cũ) |
| Hiệu suất ghi | Trung bình | Cao (ghi tuần tự vào WAL) |
| Tiêu thụ đĩa | Ít hơn (chỉ nhật ký rollback) | Nhiều hơn (WAL + cơ sở dữ liệu chính) |
| Khôi phục sự cố | Rollback đến điểm kiểm tra cuối | Khôi phục từ WAL (không mất dữ liệu) |
| Khuyến nghị | Cho kịch bản đơn luồng | Cho ứng dụng di động thông thường |
Việc chuyển đổi giữa các chế độ được thực hiện bằng một truy vấn SQL duy nhất: PRAGMA journal_mode=WAL. Đối với ứng dụng di động có đồng bộ hóa nền và luồng UI đọc dữ liệu đồng thời, WAL cung cấp hiệu suất tốt hơn và không bị khóa giao diện.
SQLite không phải là lựa chọn duy nhất cho lưu trữ dữ liệu cục bộ, nhưng là linh hoạt nhất. Realm cung cấp tốc độ truy cập trực tiếp vào đối tượng trong bộ nhớ cao hơn nhưng sử dụng định dạng NoSQL riêng và có kích thước thư viện lớn hơn. Core Data trên iOS là một lớp ORM trên SQLite giúp thêm quản lý đồ thị đối tượng và hoàn tác thao tác.
Đối với hầu hết ứng dụng, SQLite vẫn là lựa chọn tối ưu nhờ hiệu suất có thể dự đoán, không bị khóa nhà cung cấp và độ ổn định đã được kiểm chứng qua thời gian. Realm và Core Data phù hợp trong các dự án có đồ thị đối tượng phức tạp, truy vấn phản ứng hoặc yêu cầu đồng bộ hóa giữa các thiết bị.
| Đặc điểm | SQLite | Realm | Core Data |
|---|---|---|---|
| Loại cơ sở dữ liệu | Quan hệ (SQL) | NoSQL (dựa trên đối tượng) | ORM (trên SQLite) |
| Kích thước thư viện | ~600 KB | ~4 MB | Tích hợp trong SDK Apple |
| Hiệu suất | Trung bình | Cao (đối tượng trong bộ nhớ) | Trung bình (chi phí ORM) |
| Nền tảng | iOS, Android, Web, Desktop | iOS, Android, Node.js | iOS, macOS |
| Khóa nhà cung cấp | Không có (tiêu chuẩn mở) | Trung bình (định dạng độc quyền) | Cao (chỉ Apple) |
Việc lựa chọn giữa SQLite, Realm và Core Data phụ thuộc vào nền tảng, yêu cầu mô hình đối tượng và chiến lược đồng bộ hóa. Đối với các dự án đa nền tảng (KMP, Flutter), SQLite vẫn là lựa chọn phổ quát duy nhất hoạt động trên tất cả các nền tảng mục tiêu mà không thay đổi mô hình dữ liệu.
Room là một thư viện từ Android Jetpack cung cấp lớp ORM trên SQLite. Room tự động tạo truy vấn SQL từ các giao diện DAO được chú thích, xác thực tính đúng đắn của truy vấn tại thời điểm biên dịch và hỗ trợ di chuyển cơ sở dữ liệu khi lược đồ thay đổi. Room là cách được khuyến nghị để làm việc với SQLite trên Android.
SQLiteOpenHelper là API cấp thấp để quản lý SQLite trực tiếp mà không cần ORM. Lớp này quản lý việc tạo, mở và nâng cấp cơ sở dữ liệu. SQLiteOpenHelper phù hợp cho các dự án có truy vấn SQL đơn giản hoặc khi cần kiểm soát hoàn toàn logic SQL mà không cần sự trừu tượng hóa của Room.
Một entity trong Room được chú thích bằng @Entity, và DAO bằng @Dao. Room dịch các phương thức được chú thích thành truy vấn SQL: @Insert tạo INSERT, @Query tạo SELECT với SQL được chỉ định. Di chuyển được thêm qua Migration với các phiên bản lược đồ cũ và mới được chỉ định. Room xác thực SQL tại thời điểm biên dịch, loại bỏ lỗi cú pháp trong sản xuất.
@Entity
data class User(
@PrimaryKey val id: Long,
val name: String,
@ColumnInfo(name = "created_at")
val createdAt: Long
)
@Dao
interface UserDao {
@Query("SELECT * FROM user ORDER BY name ASC")
suspend fun getAllUsers(): List<User>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
@Query("DELETE FROM user WHERE id = :id")
suspend fun deleteUser(id: Long)
}
Room tự động tạo ra triển khai UserDao_Impl, chứa các truy vấn SQLite thời gian chạy thông qua RoomDatabase nội bộ. Nhờ coroutine (suspend), các phương thức DAO thực thi bất đồng bộ trên luồng nền mà không chặn UI. Kiểu trả về Flow trong @Query tự động cập nhật kết quả khi bảng thay đổi.
FMDB là một trình bao bọc Objective-C trên API C của SQLite, trong lịch sử là thư viện phổ biến đầu tiên cho iOS. Nó cung cấp các đối tượng FMDatabase và FMResultSet để thực thi truy vấn và lấy kết quả. FMDB đơn giản và tối giản, nhưng không hỗ trợ các cấu trúc đặc thù của Swift — optionals, Codable, async/await.
GRDB là một thư viện Swift hiện đại để làm việc với SQLite. Nó cung cấp API an toàn kiểu, hỗ trợ Codable, Combine Publishers, async/await, di chuyển và quan sát thay đổi thời gian thực. GRDB được ưa chuộng cho các dự án Swift mới nhờ tích hợp đầy đủ với Swift Concurrency và khả năng đọc mã tốt hơn.
GRDB định nghĩa bảng thông qua các lớp Record tuân thủ các giao thức FetchableRecord và TableRecord. Các truy vấn được viết bằng Swift với cú pháp an toàn kiểu thay vì SQL thô. GRDB cũng hỗ trợ DatabaseMigrator để quản lý phiên bản lược đồ và di chuyển giữa các phiên bản ứng dụng.
struct User: Codable, FetchableRecord, TableRecord {
var id: Int64
var name: String
var createdAt: Date
}
let dbPool = try DatabasePool(path: dbPath)
var migrator = DatabaseMigrator()
migrator.registerMigration("v1") { db in
try db.create(table: "user") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text).notNull()
t.column("createdAt", .datetime).notNull()
}
}
let users = try await dbPool.read { db in
try User.order(Column("name")).fetchAll(db)
}
DatabasePool sử dụng chế độ WAL của SQLite để đọc đồng thời. Nhiều trình đọc có thể truy cập cơ sở dữ liệu đồng thời trong khi một trình ghi duy nhất cập nhật dữ liệu qua WAL. GRDB tự động quản lý kết nối và giao dịch, cung cấp quyền truy cập cơ sở dữ liệu an toàn luồng từ bất kỳ luồng nào mà không cần đồng bộ hóa thủ công.
Chỉ mục là cách hiệu quả nhất để tăng tốc truy vấn SQLite. Một chỉ mục được tạo trên các cột được sử dụng trong mệnh đề WHERE, JOIN và ORDER BY. Đối với bảng có 100000 bản ghi, tìm kiếm trên cột được đánh chỉ mục mất mili giây thay vì giây. Tuy nhiên, chỉ mục làm chậm thao tác INSERT và UPDATE, do đó số lượng của chúng cần được cân bằng với tần suất ghi.
Chèn hàng loạt (batch insert) trong một giao dịch duy nhất giúp tăng tốc tải dữ liệu hàng loạt một cách triệt để. Chèn 1000 bản ghi từng cái một gây ra chi phí khoảng 1 giây. Cùng 1000 bản ghi trong một giao dịch duy nhất mất khoảng 5-10 mili giây. Sự khác biệt được giải thích bởi mỗi INSERT riêng lẻ tạo ra một giao dịch mới với ghi đồng bộ lên đĩa.
PRAGMA là các lệnh SQLite để cấu hình hành vi thư viện. Các pragma tối ưu hóa chính: PRAGMA synchronous=NORMAL (giảm tần suất fsync), PRAGMA cache_size=-8000 (cấp phát 8 MB bộ nhớ đệm), PRAGMA temp_store=MEMORY (bảng tạm thời trong bộ nhớ). Đối với ứng dụng di động có khối lượng dữ liệu lớn, kết hợp các pragma này giúp tăng tốc truy vấn gấp 2-3 lần.
Một tối ưu hóa quan trọng khác là biên dịch trước các truy vấn SQL (prepared statements). Nếu một truy vấn được thực thi nhiều lần (ví dụ: chèn 10000 hàng), biên dịch SQL một lần và tái sử dụng câu lệnh giúp giảm tải CPU 30-50%. Room và GRDB tự động lưu cache prepared statements, nhưng khi sử dụng trực tiếp API C của SQLite, việc biên dịch phải được thực hiện thủ công.
class UserRepository(private val db: RoomDatabase) {
suspend fun insertBatch(users: List<User>) {
db.withTransaction {
users.chunked(500).forEach { batch ->
batch.forEach { user ->
insertUser(user)
}
}
}
}
}
Chèn hàng loạt với withTransaction đảm bảo tất cả thao tác INSERT được thực thi trong một giao dịch duy nhất. Việc chia thành các lô nhỏ (chunked) ngăn một giao dịch trở nên quá lớn, có thể chặn các luồng khác trong thời gian dài. Đối với đồng bộ hóa nền, kích thước lô nhỏ 500 bản ghi cung cấp sự cân bằng tối ưu giữa tốc độ và khả năng phản hồi của UI.
Câu hỏi thường gặp
Có, SQLite hỗ trợ truy cập đa luồng ở chế độ WAL. Nhiều luồng có thể đọc dữ liệu đồng thời, nhưng chỉ một luồng có thể ghi. Room và GRDB quản lý đồng bộ hóa tự động. Ở chế độ rollback journal (mặc định), cơ sở dữ liệu bị khóa hoàn toàn trong bất kỳ thao tác ghi nào.
Giới hạn của SQLite là 281 TB (tối đa lý thuyết). Trong thực tế, kích thước cơ sở dữ liệu bị giới hạn bởi bộ nhớ khả dụng của thiết bị. Đối với ứng dụng di động, kích thước thoải mái lên đến 1-2 GB. Cơ sở dữ liệu lớn hơn 2 GB làm chậm sao lưu, cập nhật qua App Store và tăng tiêu thụ RAM.
SQLite không mã hóa dữ liệu theo mặc định — bất kỳ tiến trình nào có quyền truy cập tệp đều có thể đọc được. Để mã hóa, hãy sử dụng SQLCipher (tiện ích mở rộng với AES-256), Room với EncryptedDatabase (Android) hoặc Encrypted Core Data trên iOS. Mã hóa thêm 5-15% chi phí cho các thao tác đọc và ghi dữ liệu.
SQLite là một thư viện nhúng không yêu cầu tiến trình máy chủ. MySQL là DBMS client-server với máy chủ riêng biệt, người dùng, quyền truy cập và giao thức mạng. SQLite lưu trữ cơ sở dữ liệu trong một tệp duy nhất; MySQL lưu trữ trong nhiều tệp do máy chủ quản lý. SQLite đơn giản và nhẹ hơn; MySQL mạnh mẽ và có khả năng mở rộng hơn.
Để di chuyển SQLite, hãy sử dụng ALTER TABLE (thêm cột) hoặc tạo bảng mới với chuyển dữ liệu và xóa bảng cũ. Room tự động hóa quy trình này thông qua các lớp Migration: chỉ định startVersion, endVersion và truy vấn SQL cho thay đổi lược đồ. GRDB và FMDB cung cấp DatabaseMigrator tương tự.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm