做Web应用时,数据存储几乎是绕不开的话题。用户的偏好设置、离线缓存的数据、表单草稿、本地日志,这些信息都需要一个落脚点。提到浏览器本地存储,大多数人第一反应是LocalStorage,它简单到只需要两个API:setItem和getItem。但当数据量上来之后,问题就暴露了:5MB的容量上限、只支持字符串、没有任何查询能力。这时候SQLite就进入了视野。SQLite是一个轻量级的嵌入式关系型数据库,通过WebAssembly编译后可以直接跑在浏览器里,功能几乎和原生版没有区别。这篇文章就来详细对比这两套方案,并通过一个实战项目演示如何在浏览器中使用SQLite。

一、LocalStorage的能力与局限
LocalStorage是浏览器提供的键值存储接口,属于同步API,读写操作直接在主线程执行。它的优点非常明显:接口简单、兼容性好、不需要任何第三方库。存一个字符串、取一个字符串,一行代码就能搞定:
// 写入
localStorage.setItem('username', '张三');
// 读取
const name = localStorage.getItem('username');
// 删除
localStorage.removeItem('username');
但LocalStorage的局限同样突出。首先是容量限制,大多数浏览器给每个源分配的配额在5MB左右,超过这个限制,写入操作会直接抛出QuotaExceededError异常。其次,它只能存储字符串,如果要把一个对象存进去,必须先调用JSON.stringify序列化,取出时再JSON.parse反序列化,数据量大时这个转换开销不小。
更致命的是查询能力。LocalStorage本质上是一个扁平的键值仓库,你没办法写类似WHERE的条件过滤,只能把所有数据取出来,在JavaScript里遍历筛选。假设本地存了一万条订单记录,想找出某个月份、金额大于500的订单,LocalStorage只能全量读出、全量解析、逐条判断,而SQL一句WHERE子句就能解决。另外,LocalStorage是同步阻塞的,大量读写会卡住主线程,影响页面响应。
二、SQLite在浏览器中的运行方式
SQLite本身是为嵌入式环境设计的C语言数据库,整个数据库就是单个文件。要在浏览器里使用它,需要借助WebAssembly。目前主流的方案有两个:一个是sql.js,它把SQLite编译成WASM,提供了完整的SQL执行能力;另一个是wa-sqlite,它不仅能在浏览器中运行SQL,还能结合OPFS(Origin Private File System)实现真正的持久化,性能表现更好。
sql.js的使用方式很直接:引入库文件后初始化数据库实例,之后就可以执行任意SQL语句。下面是一个完整的示例,包含建库、建表、插入数据和查询:
import initSqlJs from 'sql.js';
async function main() {
// 初始化 SQL.js,需要指定 wasm 文件路径
const SQL = await initSqlJs({
locateFile: file => `https://cdn.example-static.com/sql.js/${file}`
});
// 创建内存数据库
const db = new SQL.Database();
// 建表
db.run(`
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_name TEXT NOT NULL,
amount REAL,
created_at TEXT
)
`);
// 插入数据(参数化写法,防止 SQL 注入)
const insert = db.prepare(
'INSERT INTO orders (user_name, amount, created_at) VALUES (?, ?, ?)'
);
insert.run(['张三', 520.5, '2024-06-01']);
insert.free();
// 条件查询
const rows = [];
const stmt = db.prepare(
'SELECT * FROM orders WHERE amount > ? ORDER BY created_at DESC'
);
stmt.bind([500]);
while (stmt.step()) {
rows.push(stmt.getAsObject());
}
stmt.free();
console.log(rows);
}
main();
注意一点:sql.js默认创建的是内存数据库,页面刷新后数据就没了。要实现持久化,需要在合适的时机把数据库导出为二进制数据,存到IndexedDB或者OPFS里,下次启动时再加载回来。导出可以用db.export()方法,它返回一个Uint8Array。wa-sqlite则把这一步做得更彻底,它可以直接把OPFS当作存储后端,写操作自动落盘,不需要手动导出导入,适合数据量较大、需要频繁写入的场景。
三、核心维度逐项对比
把两个方案放在一起,差异一目了然。下面从几个关键维度逐一分析:
| 对比维度 | LocalStorage | SQLite(浏览器版) |
|---|---|---|
| 存储容量 | 约5MB | 受浏览器存储配额限制,通常数百MB甚至更多 |
| 数据类型 | 仅字符串 | 支持INTEGER、REAL、TEXT、BLOB等 |
| 查询能力 | 无,只能全量遍历 | 完整SQL,支持索引、JOIN、聚合 |
| 事务支持 | 无 | 支持ACID事务 |
| API模型 | 同步,阻塞主线程 | 异步为主,可放入Web Worker |
| 学习与接入成本 | 极低,零依赖 | 需要加载WASM,需掌握SQL |
| 适用数据规模 | 小型配置、简单状态 | 中大型结构化数据 |
从容量的角度看,LocalStorage的5MB是硬性天花板,而且这个配额是按字符串的UTF-16编码计算的,中文实际占用的空间比想象中大。SQLite配合OPFS或IndexedDB,能用的空间取决于浏览器的整体存储配额,Chrome通常能给到磁盘剩余空间的一定比例,实际可用空间大几个数量级。
从事务的角度看,这是SQLite的杀手锏。LocalStorage的多个写入操作之间没有原子性保证,如果中途页面崩溃,可能留下半成品数据。而SQLite的事务要么全部成功,要么全部回滚,这对订单、账目这类强一致性数据至关重要。另外SQLite支持建索引,对于经常按某个字段查询的表,加上索引后查询速度可以提升数十倍,这是LocalStorage完全做不到的。
从接入成本的角度看,LocalStorage确实更省事,没有额外体积,没有异步初始化。SQLite需要下载几百KB的WASM文件,首次加载有延迟,还要处理初始化时序问题。如果是简单的主题偏好、登录令牌这类数据,用SQLite属于杀鸡用牛刀。
四、选型建议与实战经验
经过上面的对比,选型思路其实很清晰。满足以下条件时,LocalStorage是合理选择:数据量小(几十KB以内)、结构简单、不需要查询、主要是键值对形式的配置项。比如主题色、语言设置、用户上次浏览位置,这些用LocalStorage就够了。
以下情况建议直接上SQLite:数据是结构化的表格数据,记录数量可能达到数千条以上;需要多条件组合查询、排序、分页;需要事务保证数据一致性;数据包含二进制内容;或者项目有离线优先的需求,需要在本地复刻一套服务端的查询逻辑。典型场景包括离线CRM、本地笔记应用、数据分析看板的离线缓存等。
实战中还有几条经验值得注意。第一,把SQLite操作放进Web Worker里执行,避免SQL执行时间过长阻塞UI渲染,wa-sqlite对Worker环境支持很好。第二,做持久化时优先考虑OPFS,它的写入性能比IndexedDB稳定;如果目标浏览器不支持OPFS,再退回IndexedDB存储导出的二进制文件。第三,记得监听存储配额事件,并在用户授权后调用navigator.storage.persist(),这样浏览器在存储压力下不会轻易清除你的数据。第四,无论用哪种方案,都不要把敏感信息明文存在本地,浏览器本地存储对同源下的任何脚本都是开放的,一旦页面被注入恶意脚本,数据就可能被读取。
总结一下:LocalStorage和小数据量的配置项是绝配,简单直接零成本;SQLite则是浏览器端的重型武器,用空间复杂度换取了完整的数据库能力。两者并不冲突,很多成熟项目同时使用两者——配置走LocalStorage,业务数据走SQLite,各司其职。理解各自的边界,才能在合适的场景做出合适的选择。
SQLiteLocalStorageWeb存储修改时间:2026-09-09 02:10:47