欢迎回来
登录你的知识库账户
忘记密码?
还没有账户?立即注册
创建账户
注册你的专属知识库
已有账户?去登录
找回密码
输入注册邮箱获取验证码
返回登录
请输入图片中的验证码以继续注册
加载中...
取消
新建收藏
手动添加你喜欢的内容
取消
编辑头像与昵称
上传新头像或修改你的显示昵称
支持 JPG/PNG,最大 2MB
取消

问题反馈

notebasewww.notebase.cn
控制台
内容库
动态
管理
账户
U
用户
--
在线
v0.8.7 · 知识库
笔记
KnowledgeBase
网络无边,知识有迹。
0笔记
0工具
30推荐

分类导航

按主题直达

编辑精选

站内用户贡献 · 真实笔记

最新收录

每日更新
继续浏览全部内容 →
>
笔记
0
加载中...
工具
0
此页用于记录用户反馈问题后的每一次改进
笔记用法

“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。

md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。

要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。

工具用法

在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。

要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。

工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。

隐藏笔记

写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。

适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。

数据安全

你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。

技术

全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。

理念

这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。

原则

不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。

更多

产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。

举报

如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。

趋势
// 点击导航加载发现
归档
// 归档为空
最近浏览
// 暂无浏览记录
发布
// 加载中...
用户发布
// 加载中...
用户管理
// 加载中...
访问统计
// 加载中...
内容审核
// 加载中...
个人信息
// 加载中...
返回首页

在 GitHub Pages 上托管 SQLite 数据库:让静态网站拥有真正的查询能力

1970/1/1数据库

通过将 SQLite 编译为 WebAssembly 并利用 HTTP Range 请求实现虚拟文件系统,让静态网站也能承载并高效查询超大数据库,无需后端服务器。

引言:一个反复出现的痛点

作者在开发一个小工具网站时,发现了一个自己反复遇到的模式:想写一个查询数据库并以图表或表格形式展示结果的网页小工具。但在这个需求面前,传统方案总有令人遗憾的缺陷:

  • 写一个后端服务器:需要持续的托管和维护。麻烦之处在于,这些小型副项目往往会上线后就被遗忘。某天外部 API 挂掉、密钥过期,或者因为不再关注而停止了 VPS 的续费。几年后回访时,发现服务早已消失,悔恨自己当初依赖了外部服务,也高估了自己长期维护的意愿。

  • 把整个数据集下载到浏览器:对于超过 10MB 的数据集,这变得不可接受——加载缓慢、内存占用巨大,且无法利用索引进行高效检索。

而维护一个静态网站要简单得多:GitHub Pages、GitLab Pages、Netlify 等提供了大量且免费可靠的选择,流量几乎无限扩展而无需任何运维成本。那么,有没有可能把“真正的 SQL 数据库”塞进静态托管里?

答案是肯定的。作者开发了一个名为 sql.js-httpvfs 的工具,实现了这个想法。

核心理念:SQLite 编译为 WebAssembly + HTTP Range 请求

技术方案的核心思路并不复杂,由两部分组成:

  1. SQLite → WebAssembly:SQLite 本身是用 C 语言编写的,它可以无修改地通过 Emscripten 编译为 WebAssembly 代码。sql.js 库就是封装这套 wasm 代码的轻量 JS 层。但 sql.js 只允许在内存中完全创建和读取数据库——这显然不够。

  2. 虚拟文件系统——按需获取数据库分块:作者实现了一个自定义的虚拟文件系统层。从 SQLite 的角度看,它感觉自己运行在一台正常的电脑上,文件系统里除了一个它只能读取、不能写入的文件外空空如也。但底层原理是:当 SQLite 尝试从文件系统读取某个偏移量的数据时,这个虚拟文件系统会发起一个带 Range 头的 HTTP 请求,从服务器上获取对应字节范围的数据块。

用 SQL 术语来说:数据库文件永远不用被完整下载到浏览器,你只下载查询所必需的那几个页面。

分块策略:寻找请求数与带宽之间的平衡

HTTP 请求本身的往返开销相当大(握手、头信息等),所以必须批量获取数据。十分幸运的是,SQLite 本身就把数据组织成固定大小的“页”(用户可自定义页大小,默认为 4 KiB)。

为了在单次请求中能传输更多有用数据,作者将演示数据库(World Development Indicators,共 6 张表,超过 800 万行,总大小 670 MiB)的页大小压缩到了 1 KiB。这样每次请求获取的数据更精确,避免浪费带宽。

来看看实际运行效果。查询 wdi_country 表的前 3 行,只需获取 1KB 数据:

sql
select country_code, long_name from wdi_country limit 3;

这是一个完整的 SQLite 查询引擎。这意味着 SQLite 的所有原生能力都能用,包括 JSON 处理:

sql
select json_extract(arr.value, '$.foo.bar') as bar from json_each('[{"foo": {"bar": 123}}, {"foo": {"bar": "baz"}}]') as arr;

甚至可以注册 JavaScript 函数让 SQLite 在查询时调用。比如将国家代码转换为对应的旗帜 emoji:

js
function getFlag(country_code) {
// 只需一些 Unicode 魔法
return String.fromCodePoint(...Array.from(country_code || "").map(c => 127397 + c.codePointAt()));
}
await db.create_function("get_flag", getFlag);
return await db.query( select long_name, get_flag("2-alpha_code") as flag from wdi_country where region is not null and currency_unit = 'Euro';);

索引查询的底层逻辑:B-Tree 的逐页读取

深入观察一个简单的通过索引进行的查找查询:

sql
select indicator_code, long_definition
from wdi_series
where indicator_name = 'Literacy rate, youth total (% of people ages 15-24)';

通过页面读取日志,可以看到 SQLite 为此进行了 7 次页面读取:

  • 3 次页码读取:仅用于获取 Schema 元信息(这些通常会被缓存)。
  • 2 次索引查找:在 wdi_series(indicator_name) 索引的 B-Tree 中进行查找。
  • 2 次表数据读取:第一次根据主键找到行值,第二次从溢出页获取长文本数据。

索引和表读取本质上都是在 B-Tree 中逐层下降。由于每层的 B-Tree 页面的位置未必相邻,如果按 1 KiB 一页去请求,那确实需要多次随机 HTTP 请求。

更复杂的查询示例:自 2010 年后,基于最新数据,女性青年识字率最低的国家是哪些?

sql
with newest_datapoints as (
select country_code, indicator_code, max(year) as year
from wdi_data
join wdi_series using (indicator_code)
where indicator_name = 'Literacy rate, youth total (% of people ages 15-24)'
and year > 2010
group by country_code
)
select c.short_name as country, printf('%.1f %%', value) as "Youth Literacy Rate"
from wdi_data
join wdi_country c using (country_code)
join newest_datapoints using (indicator_code, country_code, year)
order by value asc
limit 10;

此查询预计会发送 1020 个 GET 请求,下载约 130270 KiB。为什么不会达到 270 次请求(如果 1 KiB 分批请求的理论最大值)?

预取系统:识别访问模式,指数化扩大请求

这便是精妙之处——作者实现了一个 预取系统,它会检测访问模式。系统使用了三个独立的虚拟“读头”,对于顺序读取(例如 B-Tree 叶节点的扫描),会指数级扩大请求大小。

也就是说:索引扫描或表扫描读取超过几 KiB 的数据时,发出的请求数量会与扫描总字节数成对数级别。第一次读 1 KiB,第二次读 2 KiB,然后 4 KiB、8 KiB,以此类推,这样可以以极少数请求拉取大段连续数据。这极大地缓和了网络延迟对性能的影响。

索引设计至关重要

这个方法只在一个前提下才高效:数据库中拥有匹配查询模式的正确索引。

作为反例,如果一个查询需要对大量数据点进行单独取值,而索引不包含该value列,SQLite 就必须为每个匹配行再做一次“随机访问”读盘——本地还能接受,但在 HTTP 环境下这将带来一场灾难性的大量请求。

此外,索引中列的顺序也非常讲究。例如:

  • INDEX ON wdi_data (indicator_code, country_code, year, value) 能使“查询某个指标所有国家的数据”过快;
  • 但如果索引顺序改为 (country_code, indicator_code, ...),就可以快速拿到一个国家的所有指标,却无法高效地查询某个指标的值。

写这篇博客时所用的 World Development Indicators 数据库,正是仔细考虑了索引设计(wdi_data 表上的索引为 (indicator_code, country_code, year, value)),才能在页面如此高效的运作。

文本搜索:基于 FTS5 模块

这个方案与 SQLite 的全文搜索(FTS5)模块结合同样出色。在演示数据库中,有超过 1000 个文本量较大的人类发展指标描述,indicator_search 表利用了 FTS5 提供模糊且快速的全文检索:

sql
select * from indicator_search
where indicator_search match 'educatio* femal*'
order by rank
limit 10;

该 FTS 表的总数据量大约 8 MB,而上面这条查询只需要获取约 70 KiB。FTS5 内部的索引结构同样利用了 B-Tree,所以这里的预取逻辑同样有效。

实际效果:一个交互式图表案例

为了让读者认识到实用性,作者展示了完整的图表交互功能:选择国家(如美国、德国、印度、中国、韩国)和任意指标(如“使用互联网的个人人口百分比”),可以渲染随时间变化的曲线图。相关指标的描述(Indicator Code、Long definition 等)也预先存放在数据库中,可以即时查询。重要的是,这一切都发生在静态托管中,没有后端请求。

扩展阅读和演示源码可在仓库中找到。

总结:为什么值得尝试

  • 零维护:静态文件托管是完全免费的,并且有可靠的服务提供商(如 GitHub Pages,它为此项目提供 100% 的静态托管服务)。
  • 高可扩展性:不需要动一根手指,你的数据库就能服务无限量的访问者。
  • 真正的 SQL 能力:不受限于像 localStorage 这样的键值存储,它可以拥有索引、事务、视图、触发器、FTS5 全文检索、JSON 处理等全部 SQLite 特性。
  • 远超 10MB 的数据:通过智能分块,几百兆的数据库也能被浏览器轻松地在快速查询下提供流畅体验,初始加载只有几十 KB。

如果你也曾在静态网站中苦于无法进行复杂数据筛选,这个方案值得一试。当你需要存储不断增长的真实数据并希望用户随时可查时,静态托管 + SQLite WASM + HTTP Range 是一套极为优雅的组合。

原文链接:https://phiresky.github.io/blog/2021/hosting-sqlite-databases-on-github-pages/

编写使用方法
Markdown 格式 · Ctrl+Enter 确定
新建笔记
预览
数据表格
点击单元格编辑 · Tab 移动
A1fx
Sheet1
BIH1H2≡🔗</>
隐私提醒

取消
编辑工具
取消