主要的替代方案是 @capacitor-community/sqlite 在 Capacitor 8 上是 @capgo/capacitor-fast-sql:一款适用于 iOS、Android 和 Web 的原生 SQLite 插件,它将 SQLCipher 设为可选,并通过本地 HTTP 通道而非 Capacitor bridge 发送查询数据。本指南将对两者进行客观对比,告诉你何时应继续使用社区插件,并指导你如何迁移 code 以及用户现有的数据库。
为什么团队会寻找替代方案
@capacitor-community/sqlite 多年来一直是 Capacitor 默认的 SQLite 插件,且目前仍在维护(2026 年 8 月发布了 8.1.1 版本)。人们选择迁移的原因非常明确:
- Bridge 序列化问题。 每个查询结果都必须通过 Capacitor bridge 以 JSON 格式传输。对于小规模查询这没问题,但在进行同步或搜索并加载数千行数据时,性能损耗会非常明显。
- SQLCipher 始终被包含在内。 即使你从不使用加密,该插件在 iOS 和 Android 上也依赖 SQLCipher。这增加了原生包体积,并意味着你的二进制文件中包含加密 code,在填写出口合规性问卷时必须考虑这一点。
- Web 存储模型。 在 Web 端,它使用
jeep-sqlite组件:sql.js 运行在内存中,并在你调用saveToStore()时持久化到 IndexedDB。你必须记得手动保存,且整个数据库都存在于内存中。 - 庞大的 API 攻击面。 包含连接封装、一致性检查、只读与非规范连接、同步表、JSON 导入导出。功能强大,但对于只想“运行 SQL”的用户来说学习成本较高。
- 每个 App 使用一个密码。 加密使用由插件存储的单一密钥 (
setEncryptionSecret),该密钥由 App 中所有加密数据库共享。
对比分析
@capacitor-community/sqlite 8.1 |
@capgo/capacitor-fast-sql 8.2 |
|
|---|---|---|
| 支持平台 | iOS, Android, Web, Electron | iOS, Android, Web |
| 数据路径 | Capacitor bridge | 带有单连接 Token 的本地 HTTP 服务器 (Native) |
| Web 引擎 | jeep-sqlite (sql.js + IndexedDB,手动) saveToStore) |
官方 @sqlite.org/sqlite-wasm 支持 OPFS,写入直接存入文件 |
| 加密 | 始终内置 SQLCipher,由单个插件管理的密码 | 可选 SQLCipher,按数据库传入密钥 connect() |
| Swift Package Manager | 是 (Package.swift) |
是 (Package.swift) |
| Schema 版本控制 | upgradeStatements / addUpgradeStatement |
通常由您自定义 PRAGMA user_version |
| JSON 导入/导出,同步表 | 是 | 否 |
| 从 assets 预加载数据库 | copyFromAssets() |
提前手动复制文件 connect() |
| 键值对辅助工具 | 否 | KeyValueStore |
| BLOB 数据 | 是 | 是的,作为 Uint8Array |
| ORM 支持 | 内置 TypeORM capacitor 驱动 |
自定义驱动(Kysely 兼容性良好) |
| 额外的原生配置 | Web 端:复制 sql-wasm.wasm |
iOS NSAllowsLocalNetworking,Android localhost 明文传输规则 |
| 维护者 | Capacitor 社区 | Capgo |
何时继续使用社区插件
务实一点。如果满足以下条件,请继续使用:
- 你正在发布一个 Electron 构建,
- 你正在使用 TypeORM (其 capacitor 驱动是基于社区插件编写的,参见 配合 Capacitor 使用 TypeORM),
- 你依赖于 JSON 导入/导出或同步表 且不想重新构建它们,
- 你的查询量很小,且目前应用运行良好。
何时 Fast SQL 更合适
如果符合以下情况,请迁移:
- 你需要加载或写入大数据集(同步引擎、离线目录、搜索索引、CRDT 日志),
- 你只想对部分数据库进行加密,并使用 Keychain 或 Keystore 中的密钥,
- 你不想在不需要加密的应用中引入 SQLCipher,
- 你希望通过 OPFS 在 Web 端使用真实的 SQLite 文件,而非内存副本,
- 你想要一个简单的 API:连接、查询、执行、事务。
有关 Fast SQL 的完整指南,请参阅 如何在 Capacitor 应用中使用 SQLite.
第 1 步:在旧插件旁安装 Fast SQL
请保留社区版插件一个版本周期,以便迁移现有数据。
bun add @capgo/capacitor-fast-sql
bunx cap sync
Fast SQL 所需的 Native 配置:
- iOS:
NSAppTransportSecurity>NSAllowsLocalNetworking=true在Info.plist. - Android: 将
network_security_config.xml允许明文传输localhost和127.0.0.1,引用自清单文件。 - 加密:
net.zetetic:sqlcipher-android在 Android 应用模块和CapgoCapacitorFastSql/SQLCipheriOS pod 中。详情请参阅 在 Capacitor 中加密 SQLite. - Web:
Cross-Origin-Opener-Policy: same-origin和Cross-Origin-Embedder-Policy: require-corp用于 OPFS 的请求头。
步骤 2:映射 API
打开数据库
改造前:
import { CapacitorSQLite, SQLiteConnection } from '@capacitor-community/sqlite';
const sqlite = new SQLiteConnection(CapacitorSQLite);
const db = await sqlite.createConnection('app', false, 'no-encryption', 1, false);
await db.open();
改造后:
import { FastSQL } from '@capgo/capacitor-fast-sql';
const db = await FastSQL.connect({ database: 'app', walMode: true, performancePresets: true });
FastSQL.connect() 如果数据库已打开,则返回现有连接,因此无需 isConnection / retrieveConnection 繁琐操作。
方法映射
| 社区插件 | 快速 SQL |
|---|---|
createConnection() + open() |
FastSQL.connect({ database }) |
closeConnection(name) |
FastSQL.disconnect(name) |
db.query(sql, values) 返回 { values } |
db.query(sql, params) 返回行 |
db.run(sql, values) 返回 { changes: { changes, lastId } } |
db.run(sql, params) 返回 { rowsAffected, insertId } |
db.execute(multiStatementString) |
一个 db.execute(sql) 每个语句,或 db.executeBatch([...]) |
db.executeSet(set) |
db.executeBatch(ops) 带 { statement, params } |
beginTransaction / commitTransaction / rollbackTransaction |
db.transaction(async (tx) => ...) 或 beginTransaction() / commit() / rollback() |
addUpgradeStatement / upgradeStatements |
一个 PRAGMA user_version 迁移运行器 |
copyFromAssets() |
复制已打包的 .db 移至 Directory.Data 在第一个之前 connect() |
setEncryptionSecret() + mode: 'secret' |
encrypted: true, encryptionKey 在 connect() |
initWebStore() / saveToStore() |
不需要,OPFS 会持久化写入 |
查询与写入
改造前:
const res = await db.query('SELECT * FROM notes WHERE pinned = ?', [1]);
const notes = res.values ?? [];
const { changes } = await db.run('INSERT INTO notes (title) VALUES (?)', ['Hello']);
const id = changes?.lastId;
改造后:
const notes = await db.query('SELECT * FROM notes WHERE pinned = ?', [1]);
const { insertId } = await db.run('INSERT INTO notes (title) VALUES (?)', ['Hello']);
这两个插件都以列名为键,将行返回为对象。
多语句执行
社区版插件 execute() 接受以以下符号分隔的包含多个语句的字符串: ;。Fast SQL 在每次调用时仅准备一条语句。请拆分您的 SQL,或者更好的做法是将语句存为数组:
const schema = [
'CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL)',
'CREATE INDEX IF NOT EXISTS idx_notes_title ON notes (title)',
];
await db.executeBatch(schema.map((statement) => ({ statement })));
不要仅通过 ; 使用简单的 split(';') 进行拆分,因为如果语句中包含 trigger 体或带有分号的字符串字面量,会导致错误。
将语句升级至 user_version
该社区插件也将 schema 版本存储在 PRAGMA user_version 中。如果保持版本号一致,在完成步骤 3 的文件迁移后,现有用户的数据库将直接处于正确的版本。
import type { SQLConnection } from '@capgo/capacitor-fast-sql';
// Same content as your old upgrade statements, index 0 = version 1
const versions: string[][] = [
['CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL)'],
['ALTER TABLE notes ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0'],
];
export async function migrate(db: SQLConnection) {
const [{ user_version }] = await db.query('PRAGMA user_version');
for (let v = Number(user_version); v < versions.length; v++) {
await db.transaction(async (tx) => {
for (const statement of versions[v]) await tx.execute(statement);
await tx.execute(`PRAGMA user_version = ${v + 1}`);
});
}
}
在依赖此方案前,请先在现有安装版本上检查实际的 user_version 。如果不确定,可以在当前版本的旧插件中将其记录到 logs 中。
预置数据库
该社区插件的 copyFromAssets() 会在首次启动时复制 bundled 数据库。使用 Fast SQL 时,请在连接前将文件放入插件指定的路径。Fast SQL 将数据存储在 <name>.db 在 Documents iOS 的 files 以及 Android 的 app Directory.Data 在 @capacitor/filesystem:
import { Directory, Filesystem } from '@capacitor/filesystem';
import { FastSQL } from '@capgo/capacitor-fast-sql';
async function openCatalog() {
const exists = await Filesystem.stat({ path: 'catalog.db', directory: Directory.Data })
.then(() => true)
.catch(() => false);
if (!exists) {
const blob = await (await fetch('/assets/catalog.db')).blob();
const base64 = await new Promise<string>((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve((reader.result as string).split(',')[1]);
reader.readAsDataURL(blob);
});
await Filesystem.writeFile({ path: 'catalog.db', data: base64, directory: Directory.Data });
}
return FastSQL.connect({ database: 'catalog', readOnly: true });
}
目录中,两者均为。对于大文件,请分块 download,或者通过传输压缩文件并使用 file plugin 的 native copy 功能,避免在 JavaScript 中使用 base64。
步骤 3:迁移现有用户数据
更新应用的用户的数据库是由社区插件创建的。它位于不同的路径,且名称也不同:
| 社区插件 | 快速 SQL | |
|---|---|---|
| iOS | Documents/<name>SQLite.db (默认 iosDatabaseLocation) |
Documents/<name>.db |
| Android | databases/<name>SQLite.db |
files/<name>.db |
方案 A:复制文件(非加密数据库)
最快且能保留所有内容:索引、触发器、 user_version.
import { CapacitorSQLite, SQLiteConnection } from '@capacitor-community/sqlite';
import { Directory, Filesystem } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';
export async function moveCommunityDatabase(name: string): Promise<void> {
const done = await Preferences.get({ key: `sqlite-moved:${name}` });
if (done.value === '1') return;
const sqlite = new SQLiteConnection(CapacitorSQLite);
const exists = (await sqlite.isDatabase(name)).result;
if (exists) {
const conn = await sqlite.createConnection(name, false, 'no-encryption', 1, false);
await conn.open();
await conn.execute('PRAGMA wal_checkpoint(TRUNCATE)', false).catch(() => undefined);
const { url } = await conn.getUrl();
await sqlite.closeConnection(name, false);
if (url) {
await Filesystem.copy({ from: url, to: `${name}.db`, toDirectory: Directory.Data });
}
}
await Preferences.set({ key: `sqlite-moved:${name}`, value: '1' });
}
在首次调用前执行 FastSQL.connect({ database: name }). 注意:
getUrl()(iOS 和 Android) 返回数据库文件 URL。传入完整的file://不带 URL 的directory可以让 Filesystem 插件读取沙盒内的任何路径。- 先关闭连接并进行 checkpoint,可以确保没有最近的写入操作留在
-wal文件中。 - 保留旧文件一个版本,待确认无误后,再使用社区版连接的
delete()方法将其删除。随后移除该社区插件。
方案 B:在 JavaScript 中复制行(加密数据库或 Schema 变更)
当源文件使用社区插件存储的密钥加密时,最简单且安全的方法是让该插件解密,并将行复制到新的 Fast SQL 数据库中。这样你还可以使用来自安全存储的新数据库密钥进行加密。
import { CapacitorSQLite, SQLiteConnection } from '@capacitor-community/sqlite';
import { FastSQL } from '@capgo/capacitor-fast-sql';
import { Preferences } from '@capacitor/preferences';
export async function copyRows(name: string, target: { encryptionKey?: string }) {
// Record which destination mode was migrated, so a later call with a different target fails loudly
const mode = target.encryptionKey ? 'encrypted' : 'plain';
const done = await Preferences.get({ key: `sqlite-copied:${name}` });
if (done.value === mode) return;
if (done.value) throw new Error(`${name} was already copied as ${done.value}, not ${mode}`);
const sqlite = new SQLiteConnection(CapacitorSQLite);
const src = await sqlite.createConnection(name, true, 'secret', 1, false);
await src.open();
try {
const dst = await FastSQL.connect({
database: name,
encrypted: Boolean(target.encryptionKey),
encryptionKey: target.encryptionKey,
});
const objects = ((await src.query(
`SELECT type, name, sql FROM sqlite_master
WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%'`,
)).values ?? []) as { type: string; name: string; sql: string }[];
const tables = objects.filter((o) => o.type === 'table');
const others = objects.filter((o) => o.type !== 'table');
const version = (await src.query('PRAGMA user_version')).values?.[0]?.user_version ?? 0;
await dst.transaction(async (tx) => {
for (const t of tables) await tx.execute(t.sql);
for (const t of tables) {
const rows = (await src.query(`SELECT * FROM "${t.name}"`)).values ?? [];
if (!rows.length) continue;
const cols = Object.keys(rows[0]);
const statement = `INSERT INTO "${t.name}" (${cols.map((c) => `"${c}"`).join(', ')}) VALUES (${cols.map(() => '?').join(', ')})`;
await tx.executeBatch(rows.map((r) => ({ statement, params: cols.map((c) => r[c]) })));
}
for (const o of others) await tx.execute(o.sql);
await tx.execute(`PRAGMA user_version = ${Number(version)}`);
});
// Only mark the copy as done once the transaction has committed
await Preferences.set({ key: `sqlite-copied:${name}`, value: mode });
} finally {
await sqlite.closeConnection(name, false);
}
}
使用 'no-encryption' 和 false 在 createConnection 如果源文件未加密。社区插件需要 iosIsEncryption / androidIsEncryption 在 capacitor.config 利用其加密模式(如果你之前已使用过)。对于大表,请使用分页功能进行 WHERE rowid > ? ORDER BY rowid LIMIT 5000.
两个插件都使用 SQLCipher 4 的默认设置,因此使用相同的密码也可以直接用 Fast SQL 打开加密文件。不过,社区插件会在内部管理该密码,而行复制操作则避免了对这一细节的依赖。
第 4 步:移除旧插件
在完成一两次迁移发布后:
bun remove @capacitor-community/sqlite jeep-sqlite
bunx cap sync
移除 sql-wasm.wasm 从你的 assets 以及任何 jeep-sqlite 配置于 main.ts。移除插件也会移除其 SQLCipher 依赖,因此如果你没有选择 Fast SQL 的加密功能,iOS 和 Android 的二进制文件体积会更小。
数据迁移 code 是 JavaScript 编写的,因此可以随 Capgo Live Update一起发布,但添加 Fast SQL 的版本属于原生变更,需要进行 store build。请先发布 Native Build。 Capgo 构建 如果你手边没有 Mac,也可以在云端进行构建(自 2026 年 4 月起,App Store 上传需要 Xcode 26 或更高版本)。
故障排除
Fast SQL 连接很快,但在 iOS 上所有查询都会失败。 NSAllowsLocalNetworking 应用中缺少 Info.plist,或者在添加后未重新构建应用。
Android: CLEARTEXT communication to localhost not permitted. 将 localhost 添加到 domain-config 你的网络安全配置中。如果你已经有了 SSL pinning 配置,请将它们合并。
只有部分 schema 被创建了。 You passed a multi-statement string to execute()。请将其拆分为数组。
INSERT ... RETURNING 在 Android 上不返回任何内容。 在 Fast SQL 8.2 中,Android 仅对以以下内容开头的语句返回行: SELECT, PRAGMA 或 EXPLAIN。请使用 insertId from run().
Android 读取时的 Boolean 类型不匹配。 传递 1/0 而不是 true/false 在查询参数中。Android 将读取查询参数绑定为 text 类型。
复制的数据库为空。 该社区插件配置了自定义的 iosDatabaseLocation,或者源名称不一致。记录由以下内容返回的 URL: getUrl().
旧插件中的 Web 数据已丢失。 社区插件通过 IndexedDB 存储 Web 数据, jeep-sqlite而 Fast SQL 使用 OPFS。请从后端重新同步 Web 客户端,或使用旧插件 exportToJson 导出并将其行数据导入 Fast SQL。
其他方案
@capgo/capacitor-data-storage-sqlite如果你真正需要的是基于 SQLite 的字符串键值存储。请参阅 Capacitor 中的键值存储.- 在其之上构建的 ORM 或查询构建器: Kysely 可以通过一个简短的自定义驱动程序与 Fast SQL 配合使用。 Drizzle 目前更适合社区插件。