跳转至正文

2026 年 @capacitor-community/sqlite 的替代方案

正在寻找 @capacitor-community/sqlite 的替代方案?将其与 Capgo Fast SQL 进行对比,并逐步迁移您的 Capacitor SQLite code 和用户数据。

Capgo 标志,用于介绍 Capacitor 社区 SQLite 插件替代方案的指南

主要替代方案 @capacitor-community/sqlite 在 Capacitor 8 上 @capgo/capacitor-fast-sql:一款适用于 iOS、Android 和 Web 的原生 SQLite 插件,SQLCipher 保持可选,查询数据通过本地 HTTP 通道传输,而不是走 Capacitor bridge。本指南会客观对比两者,说明何时该继续用社区插件,并带你完成 code 和用户现有数据库的迁移。

为什么团队在寻找替代方案

@capacitor-community/sqlite 多年来一直是 Capacitor 默认的 SQLite 插件,而且仍在维护(8.1.1 于 2026 年 8 月发布)。人们迁移的原因很具体:

  1. Bridge 序列化问题。 每个查询结果都要以 JSON 形式经过 Capacitor bridge。小查询没问题,但在同步或搜索时加载数千行数据,损耗就非常明显。
  2. SQLCipher 始终包含在内。 即使你从不加密,该插件在 iOS 和 Android 上仍依赖 SQLCipher。这会增加原生包体积,也意味着你的二进制文件里包含加密 code,在填写出口合规申报时必须考虑这一点。
  3. Web 存储模型。 在 Web 端,它使用 jeep-sqlite 组件:sql.js 在内存中运行,只有当你调用 saveToStore()。你必须记得保存,而且整个数据库都驻留在内存中。
  4. 庞大的 API 攻击面。 连接封装、一致性检查、只读及非规范连接、同步表、JSON 导入导出。功能强大,但对于“执行 SQL”来说学习成本较高。
  5. 每个应用一个密码短语。 加密使用由插件存储的单一密钥(setEncryptionSecret),供应用内所有加密数据库共享。

横向对比

@capacitor-community/sqlite 8.1 @capgo/capacitor-fast-sql 8.2
平台 iOS、Android、Web、Electron iOS、Android、Web
数据路径 Capacitor 桥接 本地 HTTP 服务器,每个连接使用独立 token(原生)
Web 引擎 jeep-sqlite(sql.js + IndexedDB,手动 saveToStore) 官方 @sqlite.org/sqlite-wasm 使用 OPFS,写入文件
加密 始终捆绑 SQLCipher,由插件管理单一密码 可选 SQLCipher,在...上按数据库传递密钥 connect()
Swift Package Manager 可以(Package.swift) 可以(Package.swift)
模式版本控制 upgradeStatements / addUpgradeStatement 通常由您自行管理 PRAGMA user_version
JSON 导入/导出,同步表 是 否
从资源预填充数据库 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 所需的原生配置:

  • iOS: NSAppTransportSecurity > NSAllowsLocalNetworking = true 中的 Info.plist.
  • Android: network_security_config.xml 允许明文传输到 localhost 以及 127.0.0.1,在清单文件中被引用。
  • 加密: net.zetetic:sqlcipher-android 在 Android 应用模块和 CapgoCapacitorFastSql/SQLCipher iOS 上的 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(';') 拆分,如果任何语句包含触发器主体或带有分号的字符串字面量。

将升级语句改为 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 。如果不确定,请在当前版本中从旧插件记录日志。

预填充数据库

社区插件的 copyFromAssets() 会在首次启动时复制捆绑的数据库。使用 Fast SQL 时,请在连接前将文件放在插件查找的位置。Fast SQL 存储 <name>.db 中的 Documents 在 iOS 上以及应用的 files Android 目录中,两者都是 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 });
}

对于大文件,请分块下载或发送压缩文件,并使用文件插件的原生复制功能,而不是在 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 从你的资源文件和任何 jeep-sqlite 设置仅需 main.ts。移除该插件也会移除其对 SQLCipher 的依赖,因此如果你不选择 Fast SQL 的加密功能,iOS 和 Android 的二进制文件体积会更小。

数据迁移 code 是 JavaScript,因此可以随 Capgo 实时更新一起发布,但添加 Fast SQL 的版本涉及原生变更,需要商店构建。请先发布原生构建。 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 只创建了一部分。 你向以下参数传入了包含多条语句的字符串: execute()。请将其拆分为数组。

INSERT ... RETURNING 在 Android 上不返回任何内容。 在 Fast SQL 8.2 中,Android 仅对以以下内容开头的语句返回行: SELECT, PRAGMA 或 EXPLAIN. 用于 insertId 来自 run().

在 Android 上读取时,Boolean 值不匹配。 传入 1/0 而不是 true/false 在查询参数中。Android 会将读取查询参数绑定为文本。

复制的数据库为空。 该社区插件配置了自定义 iosDatabaseLocation,或者源名称不一致。记录由以下内容返回的 URL getUrl().

旧插件中的 Web 数据已丢失。 社区插件将 Web 数据存储在 IndexedDB 中, jeep-sqlite而 Fast SQL 使用 OPFS。请从后端重新同步 Web 客户端,或使用旧插件的导出功能, exportToJson 再将数据行导入 Fast SQL。

其他方案

  • @capgo/capacitor-data-storage-sqlite 如果你真正需要的是基于 SQLite 的字符串键值存储。请参阅 Capacitor 中的键值存储.
  • 在其上使用 ORM 或查询构建器: Kysely 可通过一个简短的自定义驱动与 Fast SQL 配合使用。 Drizzle 目前更适合社区插件。

完整的 Fast SQL API 位于 插件文档 以及在 plugin 页面.

为 Capacitor 应用提供 Live Updates

当 Web 层出现线上 Bug 时,通过 Capgo 发布修复,而不用等 App Store 审核好几天。用户会在后台收到更新,原生变更则仍走正常审核流程。

博客最新动态

Capgo 为你提供打造真正专业的移动应用所需的最有价值的洞察。