本篇目标

理解 SQLite 的基本概念与使用场景,掌握原生 SQLite 的建库、建表、增删改查,了解常用第三方库的选型与基本用法。

一、SQLite 是什么

SQLite 是一个轻量级的嵌入式关系型数据库,iOS 系统内置支持。

核心特点:

  • 无需服务器,单文件存储

  • 支持标准 SQL 语法

  • 事务安全

  • 跨平台

  • 系统自带,无额外依赖

与 Core Data 的区别:

维度

SQLite

Core Data

定位

数据库

对象图管理框架

操作方式

SQL 语句

面向对象

学习曲线

需懂 SQL

需理解框架概念

性能

高,可控

高,自动优化

灵活度

高

中

适用场景

复杂查询、跨平台

iOS 原生应用

选择建议:

  • iOS 原生项目、SwiftUI 深度集成:优先 Core Data。

  • 需要精确控制 SQL、跨平台、已有数据库:考虑 SQLite。

  • 第三方封装库(如 GRDB)可以兼顾易用性和性能。

二、原生 SQLite 使用

导入:

import SQLite3

打开数据库:

var db: OpaquePointer?

let fileURL = FileManager.default
    .urls(for: .documentDirectory, in: .userDomainMask)
    .first!
    .appendingPathComponent("app.db")

if sqlite3_open(fileURL.path, &db) == SQLITE_OK {
    print("打开成功")
} else {
    print("打开失败")
}

关闭数据库:

sqlite3_close(db)

创建表:

let createSQL = """
CREATE TABLE IF NOT EXISTS notes (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT,
    created_at REAL
);
"""

var statement: OpaquePointer?

if sqlite3_prepare_v2(db, createSQL, -1, &statement, nil) == SQLITE_OK {
    if sqlite3_step(statement) == SQLITE_DONE {
        print("建表成功")
    }
}

sqlite3_finalize(statement)

插入数据:

let insertSQL = "INSERT INTO notes (id, title, content, created_at) VALUES (?, ?, ?, ?);"

if sqlite3_prepare_v2(db, insertSQL, -1, &statement, nil) == SQLITE_OK {
    sqlite3_bind_text(statement, 1, (UUID().uuidString as NSString).utf8String, -1, nil)
    sqlite3_bind_text(statement, 2, ("标题" as NSString).utf8String, -1, nil)
    sqlite3_bind_text(statement, 3, ("内容" as NSString).utf8String, -1, nil)
    sqlite3_bind_double(statement, 4, Date().timeIntervalSince1970)

    if sqlite3_step(statement) == SQLITE_DONE {
        print("插入成功")
    }
}

sqlite3_finalize(statement)

查询数据:

let querySQL = "SELECT id, title, content FROM notes ORDER BY created_at DESC;"

if sqlite3_prepare_v2(db, querySQL, -1, &statement, nil) == SQLITE_OK {
    while sqlite3_step(statement) == SQLITE_ROW {
        let id = String(cString: sqlite3_column_text(statement, 0))
        let title = String(cString: sqlite3_column_text(statement, 1))
        let content = String(cString: sqlite3_column_text(statement, 2))
        print(id, title, content)
    }
}

sqlite3_finalize(statement)

更新与删除:

let updateSQL = "UPDATE notes SET title = ? WHERE id = ?;"
let deleteSQL = "DELETE FROM notes WHERE id = ?;"

使用原生 SQLite 的痛点:

  • C API,代码冗长。

  • 需要手动管理 statement 生命周期。

  • 类型转换繁琐。

  • 容易出错,不易维护。

三、第三方库选型

常见 SQLite 封装库:

库

特点

适用场景

GRDB.swift

功能强大,类型安全,Swift 原生

中大型项目

SQLite.swift

类型安全,链式语法

中小型项目

FMDB

Objective-C 封装,成熟稳定

老项目、OC 项目

Realm

面向对象,非 SQLite

需要实时同步

WCDB

微信开源,性能好

高性能场景

选型建议:

  • 新项目、纯 Swift:GRDB.swift。

  • 需要轻量链式 API:SQLite.swift。

  • 维护老 OC 项目:FMDB。

  • 不想写 SQL、需要对象映射:Realm(注意闭源风险)。

  • 极致性能:WCDB。

四、GRDB.swift 入门

GRDB 是 Swift 社区广泛使用的 SQLite 封装库,类型安全、性能好。

安装(Swift Package Manager):

  1. Xcode → File → Add Packages

  2. 输入仓库地址:https://github.com/groue/GRDB.swift

  3. 选择版本,添加到项目

定义模型:

import GRDB

struct Note: Codable, FetchableRecord, PersistableRecord {
    var id: String
    var title: String
    var content: String
    var createdAt: Date

    static let databaseTableName = "notes"
}

打开数据库:

import GRDB

let dbQueue = try DatabaseQueue(path: fileURL.path)

创建表:

try dbQueue.write { db in
    try db.create(table: "notes") { t in
        t.column("id", .text).primaryKey()
        t.column("title", .text).notNull()
        t.column("content", .text)
        t.column("createdAt", .datetime)
    }
}

插入数据:

try dbQueue.write { db in
    var note = Note(id: UUID().uuidString, title: "标题", content: "内容", createdAt: Date())
    try note.insert(db)
}

查询数据:

let notes = try dbQueue.read { db in
    try Note.order(Column("createdAt").desc).fetchAll(db)
}

条件查询:

let notes = try dbQueue.read { db in
    try Note.filter(Column("title") == "标题").fetchAll(db)
}

更新数据:

try dbQueue.write { db in
    var note = try Note.fetchOne(db, key: "id")!
    note.title = "新标题"
    try note.update(db)
}

删除数据:

try dbQueue.write { db in
    _ = try Note.deleteOne(db, key: "id")
}

观察数据变化:

let observation = ValueObservation.tracking { db in
    try Note.fetchAll(db)
}

observation.start(in: dbQueue) { notes in
    print("数据更新:\(notes.count) 条")
}

优点:

  • Swift 原生语法,类型安全。

  • 支持 Codable。

  • 支持数据观察。

  • 支持事务、迁移。

五、SQLite.swift 入门

SQLite.swift 提供类型安全的链式 API。

安装:

通过 SPM 添加 https://github.com/stephencelis/SQLite.swift

使用:

import SQLite

let db = try Connection("path/to/db.sqlite3")

let notes = Table("notes")
let id = Expression<String>("id")
let title = Expression<String>("title")
let content = Expression<String>("content")

try db.run(notes.create { t in
    t.column(id, primaryKey: true)
    t.column(title)
    t.column(content)
})

try db.run(notes.insert(id <- UUID().uuidString, title <- "标题", content <- "内容"))

for note in try db.prepare(notes) {
    print(note[title])
}

特点:

  • 链式调用,接近 SQL 语法。

  • 类型安全。

  • 学习成本低。

六、Realm 简介

Realm 是一个面向对象的移动数据库,不是 SQLite 封装。

特点:

  • 直接操作 Swift 对象。

  • 实时同步(Realm Sync)。

  • 性能好。

  • 跨平台。

示例:

import RealmSwift

class Note: Object {
    @Persisted(primaryKey: true) var id: String
    @Persisted var title: String
    @Persisted var content: String
    @Persisted var createdAt: Date
}

let realm = try! Realm()

try! realm.write {
    let note = Note()
    note.id = UUID().uuidString
    note.title = "标题"
    note.content = "内容"
    note.createdAt = Date()
    realm.add(note)
}

let notes = realm.objects(Note.self).sorted(byKeyPath: "createdAt", ascending: false)

注意:

  • Realm 需要额外依赖。

  • 部分功能闭源(Realm Sync)。

  • 与 SwiftUI 集成需要额外处理。

七、选型对比

方案

学习成本

性能

灵活度

推荐场景

原生 SQLite

高

高

高

学习底层

GRDB

中

高

高

新 Swift 项目

SQLite.swift

低

高

中

中小项目

Core Data

中

高

中

iOS 原生

Realm

低

高

中

快速开发

建议:

  • 项目已经在用 Core Data,继续用。

  • 新项目需要复杂查询,选 GRDB。

  • 想快速上手,用 SQLite.swift。

  • 尽量不用原生 SQLite,维护成本高。

八、数据库设计原则

表设计:

  • 主键:UUID 或自增 ID。

  • 字段类型:TEXT、INTEGER、REAL、BLOB。

  • 时间字段:存储为 REAL 或 TEXT。

索引:

CREATE INDEX idx_notes_created_at ON notes(created_at);

索引使用建议:

  • 经常查询的字段加索引。

  • 索引过多会影响写入性能。

  • 复合索引注意字段顺序。

事务:

try dbQueue.write { db in
    try db.inTransaction {
        try note1.insert(db)
        try note2.insert(db)
        return .commit
    }
}

事务的好处:

  • 保证数据一致性。

  • 提升批量写入性能。

📌 进阶内容:数据迁移(版本升级、结构变更)属进阶内容,已放到第 6 章《数据迁移与备份》。

📌 进阶内容:性能优化(批量写入、事务、索引、分页、避免 N+1)已放到第 8 章《本地存储性能》。

九、避坑指南

  • 原生 SQLite 不建议直接使用:封装库更安全。

  • SQL 注入:使用参数绑定,不要拼接字符串。

  • 线程安全:数据库操作要在合适队列,不要跨线程共享连接。

  • 事务不要嵌套太深:容易死锁。

  • 索引不是越多越好:写入性能会下降。

  • 时间字段统一格式:存储和读取保持一致。

  • 迁移要测试:升级前测试旧数据兼容性。

十、检查清单

  • [ ] 理解 SQLite 的定位与场景

  • [ ] 了解原生 SQLite 的基本 API

  • [ ] 掌握常用第三方库选型

  • [ ] 能使用 GRDB 完成增删改查

  • [ ] 了解 SQLite.swift 与 Realm

  • [ ] 理解数据库设计原则

  • [ ] 会使用事务和索引

  • [ ] 了解数据迁移与性能优化