在浏览器端实现本地结构化数据存储,开发者往往会在SQLite与IndexedDB之间权衡。前者通过WebAssembly把成熟的嵌入式关系型数据库搬进网页,后者则是规范原生提供的非关系型方案。理解它们各自的底层机制和适用边界,是做技术选型的第一步。

运行架构与集成方式差异
SQLite在浏览器中通常以WASM编译版本存在,例如官方sqlite-wasm或第三方sql.js。它需要在页面加载时拉取一个wasm文件以及可选的数据库文件,之后在内存或File System Access API管理的虚拟文件系统中打开数据库。因为引擎本身是C语言编译而来,所以它能够完整支持SQL-92语法、多表联查、外键约束与事务回滚。这种方案对从服务端SQLite迁移逻辑的前端项目十分友好,你几乎可以复用同一套建表语句与查询代码。
IndexedDB则是浏览器自带的底层存储接口,不需要下载任何外部引擎。它以数据库、对象仓库(object store)、索引三层结构组织数据,记录的值可以是结构化克隆支持的任何JS对象。写入和读取都是异步事件模型,不会阻塞UI线程。由于它没有查询语言,所谓的查询其实是通过键范围(key range)或索引游标来遍历,因此复杂关联分析在前端做起来比较笨重。对于只做简单缓存、离线表单暂存的应用,直接调用IndexedDB或用封装库如Dexie就能满足。
从集成成本看,SQLite多了网络传输与初始化编译的开销,首次打开可能比IndexedDB慢几百毫秒;但一旦就绪,它的执行效率与表达能力远胜手写游标。IndexedDB虽零依赖,却要求开发者自己设计索引,否则全表遍历性能极差。下面是一段使用Dexie操作IndexedDB的示例,对比之后你会更清楚二者代码风格的不同。
// 使用Dexie封装IndexedDB
import Dexie from 'dexie';
const db = new Dexie('myApp');
db.version(1).stores({
users: '++id, name, age' // id自增,name和age建索引
});
async function addUser() {
await db.users.add({ name: '张三', age: 28 });
const list = await db.users.where('age').above(20).toArray();
console.log(list);
}
数据模型与查询能力对比
SQLite本质是关系模型,表与表之间通过外键联系,数据一致性由引擎保障。你可以写JOIN、GROUP BY、窗口函数,也能借助CREATE INDEX优化慢查询。对于报表型前端、本地笔记软件、需要全文检索的场景,SQLite的SQL能力可以大幅减少应用层代码。它还支持附加数据库、触发器等高级特性,在纯前端实现复杂业务逻辑时优势明显。
IndexedDB的数据模型是面向对象的,一个对象仓库类似一张表,但每行就是一个完整对象,不强制 schema。这种灵活性适合结构易变的数据,比如异步落地的日志、不同版本的配置项。不过当你需要跨仓库关联时,只能先取出再在JS里手动组合,无法像SQL那样一句搞定。它的索引仅支持单字段或复合字段的前缀匹配,不支持表达式索引,查询计划也不透明。
为了直观比较,我们用表格列出核心差异:
| 维度 | SQLite(WASM) | IndexedDB |
|---|---|---|
| 查询语言 | 标准SQL | 键/索引遍历 |
| 事务隔离 | 支持,可回滚 | 支持,但仅限同源事务 |
| 存储后端 | 虚拟文件或OPFS | 浏览器私有存储 |
| 复杂关联 | 原生JOIN | 应用层处理 |
如果业务只是缓存接口响应,IndexedDB配合TTL清理就够了;若要在浏览器里跑一个小型进销存,SQLite几乎是唯一省心的选择。下面给出一段sqlite-wasm执行联表查询的代码,体会其表达力。
// 使用sqlite-wasm执行SQL
import sqlite3InitModule from './sqlite3.mjs';
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.DB(':memory:');
db.exec('CREATE TABLE orders(id INTEGER PRIMARY KEY, user_id INT, amount REAL);');
db.exec('CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT);');
db.exec("INSERT INTO users VALUES(1,'李四');");
db.exec('INSERT INTO orders VALUES(1,1,99.5);');
const rows = db.exec("SELECT u.name, o.amount FROM orders o JOIN users u ON o.user_id=u.id;");
console.log(rows);
性能特征与容量限制实践
在写入吞吐量上,SQLite的WASM引擎跑在单个Worker或主线程,批量插入通过事务包裹可达每秒数万行;IndexedDB的异步事务受浏览器事件循环制约,单条put很快,但大量循环写入容易触发事务超时被中止。移动端Safari对IndexedDB有约500MB的可用空间估算,而SQLite依托OPFS同样受配额限制,但文件可以分片,更便于做容量监控。
读取性能方面,IndexedDB通过索引取单条记录延迟极低,适合高频点查;SQLite做多表聚合时虽然CPU占用高,但结果准确且代码短。需要留意的是,WASM版SQLite若放在主线程会卡顿,应配合Web Worker或同源OPFS的同步访问接口。IndexedDB则天然异步,不会阻塞渲染,但在Electron等混合容器中,其底层可能是Node文件,行为略有差异。
容量规划上,两者都依赖浏览器存储配额,用户清缓存会一并删除。若做PWA离线应用,建议用SQLite管理业务库,用IndexedDB存元数据,这样既能享受SQL能力,又避免大对象塞进关系表导致膨胀。以下代码展示在Worker中初始化SQLite以避免主线程卡顿的思路。
// worker.js 中初始化SQLite
import sqlite3InitModule from './sqlite3.mjs';
self.onmessage = async (e) => {
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.DB('/data.db');
db.exec(e.data.sql);
self.postMessage(db.exec('SELECT * FROM t;'));
};
综合来看,SQLite与IndexedDB并非互斥。轻量缓存用IndexedDB,复杂本地查询用SQLite,混合架构在大型前端系统中越来越常见。选型时重点评估团队SQL熟悉度、数据关联复杂度与设备存储警戒线,才能少走弯路。