IndexedDB 的优点是通用性能良好,缺点是速度太慢了。
数据库结构化查询语言 SQL,是绝大多数程序员都会经常用到的工具。不过这个东西入门容易精通难,有很多技巧和特性是一直被人忽略的。
最近,程序员 James Long 提出了 absurd-sql,它是 Web 上 SQLite 的持久化后端,不会将整个数据库加载到内存中,并且可以持续写入。
James Long 撰写了一篇博客来讲解 Web 存储 API(主要是 IndexedDB)的缺点,并介绍了 SQLite 在 absurd-sql 的加持下如何提供 10 倍的性能改进及其技巧。
项目地址:https://github.com/jlongster/absurd-sql
如果要编写一个 web 应用程序,你可能会选择使用 IndexedDB 来存储数据,这是能在所有浏览器上运行数据库的唯一选项。
当试图构建一个本地应用时,你会发现围绕这个数据库构建整个应用并不理想,当然,其中的少量功能还是很有用的。但如果想构建更好的网络应用程序,我们需要一种更强大的方式来处理数据。
IndexedDB 有一个缺点是速度缓慢。一般来说,数据库的简单操作大约需要 10ms 的时间,profile SQLite 通常需要 0.01 ms 的时间,这些时间长短和具体编写什么应用程序有关。
在 IndexedDB 中查询数据,需要用户手动操作。IndexedDB 唯一提供的函数是 count,其余的 API 只返回一系列 item。用户必须以特定的方式连接索引和构造数据,才能构建查询函数。
甚至添加一个新的对象存储都很困难,如果用户想打开数据库添加对象存储,需要强制终止其他所有 tab 与数据库的连接。
IDB 不是完美甚至是有些低级的,因此作者提出了 absurd-sql。
SQL 是构建应用程序的好方法。尤其是小型本地网络应用程序。键 / 值存储可能在大型分布式系统中占有一席之地,但是如果我们可以在 Web 上使用 SQLite 会不会很棒?
absurd-sql 使这成为了可能。absurd-sql 是 sql.js 的文件系统后端,它允许 SQLite 从 IndexedDB 读 / 写一小部分数据,就像磁盘一样。
值得注意的是,absurd-sql 的项目作者受到了 SQLite 的启发。
sql.js 是 SQLite 到 Webassembly 的一个端口,通过使用 Emscripten 编译 SQLite C 代码,它是 SQLite 在 Web 上应用的一个良好例子。它使用存储在内存中的虚拟数据库文件,因此不会保留对数据库所做的更改。sql.js 将 SQLite 编译为 WebAssembly,并允许读取数据库和运行查询。这里就存在一个重要的问题——用户不能持续进行任何写操作。它将整个数据库加载到内存中,并且只更改内存中的数据。刷新页面后,所有更改都将丢失。
虽然内存数据库有其用途,但这会让 SQLite 的用途变得很受限。开发者要使用它构建任何类型的应用程序,都需要具备数据持久化的能力。
absurd-sql 解决了这个问题,它通过拦截来自 SQLite 的读 / 写请求,将它们获取并持久化到 IndexedDB(或任何其他持久后端)来工作。项目作者编写了一个完整的文件系统层,该文件系统层知道 SQLite 如何读取和写入数据块,并能够有效地正确执行操作。这意味着它永远不会将数据库加载到内存中,而是只加载 SQLite 要求的任何内容,并且写入始终保持不变。
项目作者表示,这里使用 sql.js 是因为它已经拥有一个庞大的社区,并且是迄今为止在 Web 上使用 SQL 的最常见方式。
import initSqlJs from '@jlongster/sql.js';
import { SQLiteFS } from 'absurd-sql';
import IndexedDBBackend from 'absurd-sql/dist/indexeddb-backend';
SQL = await initSqlJs();
sqlFS = new SQLiteFS(SQL.FS, new IndexedDBBackend());
// This is temporary for now
SQL.register_for_idb(sqlFS);
SQL.FS.mkdir('/sql');
SQL.FS.mount(sqlFS, {}, '/sql');
let db =new SQL.Database('/sql/db.sqlite', { filename: true });
数据库 db 的使用也与常规使用方法没有什么区别,并且还做到了有效地读取和持久写入 IndexedDB:
let stmt = db.prepare('INSERT INTO kv (key, value) VALUES (?, ?)');
stmt.run(['item-id-00001', 35725.29]);
let stmt = db.prepare('SELECT SUM(value) FROM kv');
stmt.step();
console.log(stmt.getAsObject());
这样一操作,Web SQL 仿佛回来了!项目作者表示:「虽然目前还不推荐将 absurd-sql 用于生产,但我移植了我的应用程序 Actual 来使用 absurd-sql,结果表明还能够良好工作。」
IndexedDB 是当前唯一适用于所有浏览器的持久存储,它将数据库和持久存储两种需求合二为一。
作者使用 sql.js + absurd-sql 抽象出存储层。并讨论了 absurd-sql 能让 IndexedDB 变快多少。不过速度仍然比原生 SQLite 慢 50-100 倍,这是因为 IndexedDB 的写入速度很慢,无法进行批量读写。
IndexedDB 只是 absurd-sql 的一个后端,作者还尝试过使用 webkitFileSystem 做后端,结果发现其无法匹敌 IDB 后端的性能。
实际上,SQLite 在各项性能指标上都能轻松击败 IndexedDB。该项目作者编写了一个基准测试应用程序来测试不同的场景,运行几个不同的查询,并允许用户以多种方式配置 SQLite 和基于原始 IndexedDB 实现运行测试。
测试结果:https://docs.google.com/spreadsheets/d/1Cpb9r3cZlbZgp1RoSTmh22wOPCRMqINzMUelUTIDo8Y/edit#gid=0
基准测试代码:https://github.com/jlongster/absurd-sql/tree/master/src/examples/bench
以写入为例,IDB 的速度比 SQLite 慢了数十倍:
批处理为 SQLite 提供了如此巨大的性能优势,任何 CPU 处理都无法弥补差距。
而 absurd-sql 通过避免 IndexedDB 读 / 写节省了大量时间,以至于 CPU 命中的影响可以忽略不计。
实验表明,absurd-sql 能够很好地处理 1000000 item 的情况。从速度上看,absurd-sql 写入需要 4-6s,读取需要 2-3 秒;而 IndexedDB 完成这一过程,写入大约是 2 或 3 分钟,读取大概在 1 分钟左右。
https://jlongster.com/future-sql-web
机器之心招人啦!
为进一步生产更多的高质量内容,提供更好数据产品及产业服务,机器之心需要更多的小伙伴加入进来,共同努力打造专业的人工智能信息服务平台。
工作城市:北京市朝阳区酒仙桥 / 上海张江人工智能岛
© THE END
投稿或寻求报道:[email protected]