☰
在浏览器中使用SQLite(官方sqlite3.wasm)
2026/10/15 6:05:52 网站建设 项目流程

有人可能会问:既然浏览器里有内置得IndexedDB,而且在IndexedDB里存数据,关了浏览器数据也不会丢,为什么还要在浏览器里用SQLite?

实际上,当 IndexedDB 内的数据量增多,数据和数据之间的关系变得复杂,IndexedDB 的劣势就凸显出来了。我们在这方面踩过很多坑,这里就不细说了。

好了,言归正传:

我建议你不要使用这个工具:https://github.com/sqlite/sqlite-wasm,用起来麻烦的很,而且类型定义还有问题(看看 issue 就知道了)。

还是自己使用原始的、官方提供的 js 文件比较好。

首先到 SQLite Download Page 下载 WebAssembly & JavaScript 版本的SQLite。

下载解压后你将得到:

我们需要的文件都在 jswasm 目录中。

把 jswasm 目录中的如下文件拷贝到你的根目录下,(可以通过 https://domain.com/sqlite3.js 访问)

接着,在你的页面引入 sqlite3-worker1-promiser.js 脚本文件

<script src="sqlite3-worker1-promiser.js"></script>

现在我们封装一个 TypeScript 类

class Db { dbId: string; dbFunc: (cmd: string, param: object) => Promise<{ dbId: string; messageId: string; type: string; result: any }>; constructor() {} exec(sql: string): Promise<any[]> { return new Promise((resolve, reject) => { let rows = []; this.dbFunc("exec", { dbId: this.dbId, sql, callback: (result: { columnNames: string[]; row: any[]; rowNumber: number; type: string }) => { if (result.row) { let obj = {}; for (let i = 0; i < result.columnNames.length; i++) { obj[result.columnNames[i]] = result.row[i]; } rows.push(obj); } else { resolve(rows); } }, }); }); } async open() { const dbFactory = globalThis.sqlite3Worker1Promiser.v2; delete globalThis.sqlite3Worker1Promiser; const config = { debug: (...args) => console.debug("db worker debug", ...args), onunhandled: (ev) => console.error("Unhandled db worker message:", ev.data), onerror: (ev) => console.error("db worker error:", ev), }; this.dbFunc = await dbFactory(config); let { dbId } = await this.dbFunc("open", { filename: "file:db.sqlite3?vfs=opfs", simulateError: 0, }); this.dbId = dbId; let rows = await this.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name='Job';`); if (rows.length <= 0) { await this.exec(`CREATE TABLE Job(Id VARCHAR2(36) NOT NULL PRIMARY KEY, JobInfo TEXT, RepeatType INT, StartTime BIGINT, EndTime BIGINT, ColorIndex INT); CREATE INDEX JobInfo_Index ON Job(JobInfo); CREATE TABLE Setting(ViewDefault INT DEFAULT 0, ViewVal INT, LangDefault INT DEFAULT 0, SkinDefault INT DEFAULT 0, AlertBefore INT); INSERT INTO Setting (ViewDefault, ViewVal, LangDefault, SkinDefault, AlertBefore) VALUES (0, 0, 0, 0, 5);`); } let data = await this.exec(`select * from Setting;`); console.log(data); } async delDb() { const opfsRoot = await navigator.storage.getDirectory(); await opfsRoot.removeEntry("db.sqlite3"); } } export let db = new Db();

先来看打开数据库方法: open 。

我们使用 SQLite3 的 V2 版本的方法,其他老方法一股脑删掉。

const dbFactory = globalThis.sqlite3Worker1Promiser.v2; delete globalThis.sqlite3Worker1Promiser;

接着创建一个数据库访问方法:dbFunc,然后使用这个方法创建数据库:db.sqlite3

this.dbFunc = await dbFactory(config); let { dbId } = await this.dbFunc("open", { filename: "file:db.sqlite3?vfs=opfs", simulateError: 0, });

注意,filename的路径和参数,我们让数据库保存在浏览器的OPFS文件系统中。

OPFS 是浏览器提供的一种私有文件系统,属于 File System Access API 的一部分。它的特点包括:

私有性:每个网站拥有独立的文件系统,其他网站无法访问。

高性能:支持原地读写和同步访问,适合数据库等对性能要求高的场景。

持久化:数据不会因刷新或关闭浏览器而丢失。

数据文件创建成功后,将得到数据库id:dbId,这是个字符串。

接下来,我们检查数据库中是否存在指定的表,没有的话,就给数据库建表。

此时就用到了SQL指令执行方法:exec

执行 SQL 指令,也是用 dbFunc 方法完成的。

如果执行的 SQL 语句本身不返回数据(例如 INSERT, UPDATE, DELETE, 或查询结果为空的 SELECT),回调方法 callback 只会被执行一次。callback 的传入参数 result 没有 row 属性。

如果执行的 SQL 语句本身返回多行数据,那么 callback 方法会被执行多次,每次都可能返回多行查询结果,查询结果被存储在 result 的 row 属性里,最后一次 callback 方法被执行时, result 没有 row 属性,表示查询结束。

下面这段代码用来根据列名构造数据对象:

if (result.row) { let obj = {}; for (let i = 0; i < result.columnNames.length; i++) { obj[result.columnNames[i]] = result.row[i]; } rows.push(obj); } else { resolve(rows); }

如果你想删除数据库,可以使用 delDb 方法

const opfsRoot = await navigator.storage.getDirectory() 用于获取 OPFS 文件系统的根目录实例

opfsRoot.removeEntry(name) 会从当前目录中删除名为 "db.sqlite3" 的文件或子目录。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询