首页 > 数据库 > > SQLite 的文艺复兴
次被SQLite震撼到的一个是关于它的测试代码[1],其源码有15多万行,但测试代码和脚本竟有九千多万行。作者Dwayne Richard Hipp是一个完美主义者,为了能自由的开发SQLite,他编写了底层的存储引擎、Parser[2]、源码托管工具Fossil[3],除了C编译器和一些底层库如libc外,Richard几乎从零构建了SQLite所依赖的库或工具。
运行SQLite官网的Web服务器Althttpd[4]也是Richard开发的,所有代码仅在一个C文件中,除了标准C库之外没有依赖任何其他代码库。
当然SQLite官网[5]使用的数据库自然也是SQLite了,网页的动态数据[6]甚至是由200多条SQL语句查询后在0.01秒内渲染完成的。
很难想象这种开发模式能够成功,但Richard做到了,他把SQLite推向了数十亿的设备与终端。由于绝大部分代码都是Richard一个人完成的,SQLite并不是一个开源协作的项目。虽然代码是开放的,但却并没有开源社区的贡献,甚至有人为此专门fork出了SQLite的开源协作版本:libSQL[7]。
更多关于SQLite背后的故事可以听下这期Podcast:The Untold Story of SQLite[8]。
SQLite是一个数据库软件,但与绝大部分数据库系统拥有完全不同的运行方式。大部分数据库(MySQL、SQL Server、PostgreSQL或Oracle)系统是Client/Server的架构,客户端通过特定的协议比如JDBC/ODBC与数据库服务器通信,数据库服务器通过监听某个Socket端口去接收客户端的查询请求,之后将结果返回给客户端。
与其他数据库网络通信的方式相比,SQLite是一个库,它是通过In-Process的方式来与应用程序通信的。当应用程序发出查询请求时,这些请求是通过函数调用的方式在与应用程序相同的线程内执行的。SQLite的数据库也是存放在磁盘上的单个文件。
与其他数据库相比,SQLite的优势就是快,尤其是执行一些小的SQL查询语句,这也是为什么SQLite的官网动态数据能通过200多条SQL语句来查询获取的。SQLite也没有N+1查询性能的问题,原因就是没有其他数据库网络通信的开销:

SQLite的架构如下:

SQLite的架构主要包括三大部分:编译器、虚拟机与存储引擎。当应用程序发起查询请求时,SQL语句首先会被编译器解析,然后生成字节码,后通过虚拟机执行。虚拟机的执行过程中,会调用存储引擎的接口来读取或写入数据。
编译器的主要工作是将SQL语句解析成字节码,然后将字节码编译成可执行的机器码。编译器的主要组成部分是Parser与Code Generator,Parser负责将SQL语句解析成抽象语法树(AST),Code Generator负责将AST转换成字节码。查询计划的生成也是在Code Generator中完成的。
虚拟机[9]是一个寄存器式虚拟机(Register-Based VM),它的主要工作是执行字节码。另外查询优化(Query Optimization)也是在虚拟机中完成的。对此部分感兴趣的读者可阅读这篇文章:How the SQLite Virtual Machine Works[10]。
存储引擎的主要工作是读取或写入数据。存储引擎的主要组成部分是B-tree、Pager与OS Interface(也叫VFS):
以上就是SQLite架构的简单介绍,如果你还希望进一步了解内部实现细节,可以阅读这本开源电子书:SQLite Internals: How The World's Most Used Database Works[18]。
理解了这些,你就能够更好的了解下面这些把SQLite玩出花活的文艺复兴
类开源项目了。
SQLite是个已经超过20岁高龄的并不新的软件,很多人对它的认知还停留在一个玩具类的数据库,拿它用来做一些简单的本地存储或测试,很少用来在生产系统中使用
。但就是这样古老的软件,却长期在Hacker News[19]中被热议,因为一些有趣的项目,让SQLite老树逢春、文艺复兴。
一些你可能觉得SQLite做不到的事:
SQLite单机百万TPS并发压测:Scaling SQLite to 4M QPS on a Single Server (EC2 vs Bare Metal)[20]。
SQLite未来可支持多个并发写入: 官方出品的SQLite HC-tree[21],尝试替换老的B-tree[22]数据结构,能实现多写多读、分布式节点复制与更大的数据库大小限制,压测结果很不错[23]。
将静态页面发布到CDN中,然后使用API提供动态更新的能力,这种Jamstack[24]架构的Serverless[25]应用能给业务系统带来极高的伸缩性。这种架构的限制在于,数据需要存放在单独的托管数据库中,并且费用不便宜,而且数据库可能会成为一个性能瓶颈,因为当业务系统部署到多区域时,单节点的数据库的网络开销是个不容忽视的问题。
那如果数据库与业务系统实例在同一个服务器上运行呢?🤯

SQLite本身就是一个Serverless的数据库,它与应用程序运行在同一进程中。相比其他数据库,同样运行在与应用程序同一个服务器中,SQLite都比那些C/S架构网络通信类的数据库要更快。的问题是,如何解决多实例读取与写入同一数据库?针对此问题有以下解决方案:
Litefs[26],此项目是Litestream[27]作者加入Fly.io后开发的新项目。
Litestream能通过接管SQLite检查点过程(checkpointing process)从而控制对wal日志文件的管理,在预写日志(Write-ahead Log)模式下,litestream能持续的复制wal日志文件到备份的位置如S3中,从而实现了在线流式备份SQLite数据库文件。具体可以查看此文档介绍:How it works - Litestream[28]。
Litefs相比Litestream要更进一步。Litefs通过提供一个基于FUSE的文件系统给SQLite做VFS层,当Leader节点要更新数据库时,该节点的SQLite的Pager模块要调用VFS层写入数据时,Litefs能够在页面级别复制与这个事务相关的页面集合,通过把这些页面集合打包成一个LTX数据格式的文件包,然后通过HTTP协议将这个文件包发送给只读节点,完成了数据的跨节点同步。
在Litefs的分布式集群中,只有主节点能够写入数据,只读节点可以通过将主节点地址转发给客户端,让客户端只在主节点写入数据。主节点的选举通过获取Consul的分布式租约(Distributed leases)来达成共识,也可以设置静态的主节点。
具体的架构参考这篇文章:How LiteFS Works · Fly Docs[29]。一个部署案例可以参考这篇文章:I Migrated from a Postgres Cluster to Distributed SQLite with LiteFS[30]。
另外Cloudflare也推出了类似的商业解决方案Cloudflare D1[31]。
SQLite可以通过WebAssembly(WASM)技术运行在浏览器中,以下两个项目能让前端代码在浏览器中发起SQL查询:
在浏览器中使用SQLite有什么好处?以我的这个Invest Alchemy[34]开源项目为例,它是一个ETF投资组合管理系统,它需要管理多个ETF投资组合,每个ETF投资组合的所有数据都存放在一个SQLite数据库中,而这个数据库文件存放的位置是在AWS S3中。每天都会有一个定时程序自动下载AWS S3桶中所有的SQLite数据库,之后更新这些组合的数据,后再上传至S3中。当用户浏览某个投资组合的页面,以这个投资组合[35]为例,该页面在初始化渲染时会先从S3中下载SQLite数据库到浏览器内存,之后使用sql.js初始化SQLite数据库,后发起多个SQL查询获取数据结果,然后渲染页面。
这种架构的好处在于,浏览器页面只需要发起一次查询请求,就可以获取整个组合的全部数据。如果使用传统的数据库,一方面是费用的问题,另外一方面每次查询都需要经过网络传输,这样会增加页面的加载时间。
后,SQLite这种单数据库文件的方式,带来了很好的隔离性。比如在Invest Alchemy中,一个数据库代表一个投资组合,当然也可以把某个用户的所有个人数据存放至一个数据库中,再把这些数据库存放在AWS S3中不同的目录中,这样就可以很好的隔离不同用户的数据。
SQLite也可以作为一个Client/Server架构的数据库。当然这种用法已经失去了SQLite的优势,增加了网络开销,但在某些场景可能有用,比如作为一个只读数据源,或者作为一个数据缓存,这种费用开销应该比较低。
HTTP range头即可获取指定范围的数据,在一个大的数据库文件中,这种优化可以节省大量的网络开销。SQLite典型的用法是作为一个OLTP数据库,很少用它做OLAP。因为它的表数据存储是行式而非列式。为此duckdb[39]填充了这个空白,和SQLite相似的架构,只不过是列式存储,非常适合OLAP的业务场景。
不过SQLite在3.38.0[40]版本中通过布隆过滤器(Bloom Filter)提升了大型分析查询语句的性能,这个特性也是为了支持OLAP的业务场景。当然更近一步的优化及与duckdb的对比可参考这篇论文:Introducing a Query Acceleration Path for Analytics in SQLite3[41]。
SQLite这种In-Process的架构看起来与分布式数据库完全不相关,而且分布式数据库本身就是一个非常复杂的领域,分布式带给数据库弹性扩容及高可用的能力,由于分布式事务的复杂性,常规的做法是尽可能通过单机垂直扩容的方式来解决扩容的问题,实在没办法再通过横向扩容(分片)的方式来解决。但依旧有以下令人惊艳的项目把SQLite带向了分布式的领域。
Leader-Replica的分布式架构风格。Leader-Replica,还实现了多节点写入的并行化,提高了写入的吞吐量[47]。由于其实现了MVCC,所以甚至能实现在数据库层面的时间旅行(Time travel)。FoundationDB集群与mvstore无状态实例,所以部署与运维成本更高。7.0.0之前SSD存储引擎使用的是SQLite,不过在这之后,FoundationDB实现了自己的存储引擎Redwood[50]。SQLite为什么能在如此多领域有创新的项目出现?一方面得益于SQLite高质量的代码,近亿行的测试代码保证了其坚若磐石的可靠性,另一方面又得益其简单的架构,总共的实现源码也才15万行。
对比其他数据库如MySQL有超过四百万行的源码,Oracle有超过上千万行的源码,就连Postgres的源码也超过百万行了。正如你很难让大象跳舞一样,个人开发者也很难单打独斗去在这种规模的代码上做创新。而SQLite的架构简单,代码量少,使得它成为了一个非常好的实验平台,让开发者可以在SQLite的基础上做出各种各样的创新。
随着Serverless及运行在CDN上的Edge Computing的流行,SQLite这种轻量级的关系型数据库将会有更多的应用场景,也会有更多的创新出现。SQLite也是一个非常适合学习的数据库,它的源码非常简单,可以很好的帮助你理解数据库的原理。
它的测试代码: https://www.sqlite.org/testing.html
[2]Parser: /dev/parser_black_magic/
[3]Fossil: https://www2.fossil-scm.org/home/doc/trunk/www/index.wiki
[4]Althttpd: https://sqlite.org/althttpd/doc/trunk/althttpd.md
[5]SQLite官网: https://www.sqlite.org/
[6]动态数据: https://www.sqlite.org/src/timeline
[7]libSQL: https://github.com/libsql/libsql
[8]The Untold Story of SQLite: https://corecursive.com/066-sqlite-with-richard-hipp/
[9]虚拟机: https://www.sqlite.org/opcode.html
[10]How the SQLite Virtual Machine Works: https://fly.io/blog/sqlite-virtual-machine/
[11]SQLite Internals: Pages & B-trees: https://fly.io/blog/sqlite-internals-btree/
[12]回滚日志(Rollback Journal): https://www.sqlite.org/atomiccommit.html
[13](Write-ahead Log): https://www.sqlite.org/wal.html
[14]busy_timeout: https://sqlite.org/c3ref/busy_timeout.html
How SQLite helps you do ACID: https://fly.io/blog/sqlite-internals-rollback-journal/
[16]How SQLite Scales Read Concurrency: https://fly.io/blog/sqlite-internals-wal/
[17]VFS: https://www.sqlite.org/vfs.html
[18]SQLite Internals: How The World's Most Used Database Works: https://www.compileralchemy.com/books/sqlite-internals/
[19]Hacker News: https://news.ycombinator.com/item?id=32478907
[20]Scaling SQLite to 4M QPS on a Single Server (EC2 vs Bare Metal): https://blog.expensify.com/2018/01/08/scaling-sqlite-to-4m-qps-on-a-single-server/
[21]SQLite HC-tree: https://sqlite.org/hctree/doc/hctree/doc/hctree/index.html
[22]B-tree: https://sqlite.org/src4/doc/trunk/www/bt.wiki
[23]压测结果很不错: https://sqlite.org/hctree/doc/hctree/doc/hctree/threadtest.wiki
[24]Jamstack: /dev/pulumi-aws-serverless-hugo-site-vists/
[25]Serverless: /dev/guide-to-serverless/
[26]Litefs: https://github.com/superfly/litefs
[27]Litestream: https://github.com/benbjohnson/litestream
[28]How it works - Litestream: https://litestream.io/how-it-works/
[29]How LiteFS Works · Fly Docs: https://fly.io/docs/litefs/how-it-works/
[30]I Migrated from a Postgres Cluster to Distributed SQLite with LiteFS: https://news.ycombinator.com/item?id=34267434
[31]Cloudflare D1: https://developers.cloudflare.com/d1/
[32]sql.js: https://github.com/sql-js/sql.js/
[33]absurd-sql: https://github.com/jlongster/absurd-sql
[34]Invest Alchemy: https://github.com/bmpi-dev/invest-alchemy
[35]投资组合: https://money.bmpi.dev/portfolio?t=robot_dma_v02&p=dma_11_22
[36]postlite: https://github.com/benbjohnson/postlite
[37]sqlite3vfshttp: https://github.com/psanford/sqlite3vfshttp
[38]Cloud Backed SQLite: https://sqlite.org/cloudsqlite/doc/trunk/www/index.wiki
[39]duckdb: https://github.com/duckdb/duckdb
[40]3.38.0: https://www.sqlite.org/releaselog/3_38_0.html
[41]Introducing a Query Acceleration Path for Analytics in SQLite3: https://www.cidrdb.org/cidr2022/papers/p56-prammer.pdf
[42]rqlite: https://github.com/rqlite/rqlite
[43]Raft: https://raft.github.io/
[44]Rqlite Design: https://rqlite.io/docs/design/
[45]mvsqlite: https://github.com/losfair/mvsqlite
[46]FoundationDB: https://github.com/apple/foundationdb
[47]提高了写入的吞吐量: https://su3.io/posts/mvsqlite-bench-20220930
[48]Turning SQLite into a distributed database: https://su3.io/posts/mvsqlite
[49]Storage and transaction in mvSQLite: https://su3.io/posts/mvsqlite-2
[50]Redwood: https://youtu.be/nlus1Z7TVTI
[51]cr-sqlite: https://github.com/vlcn-io/cr-sqlite
[52]CRDT: https://crdt.tech/
[53]运行时扩展: https://www.sqlite.org/loadext.html
[54]Why SQLite? Why Now?: https://tantaman.com/2022-08-23-why-sqlite-why-now.html
[55]Bedrock: https://github.com/Expensify/Bedrock
[56]同步引擎: https://bedrockdb.com/synchronization.html
[57]私有区块链: https://bedrockdb.com/blockchain.html
本文转载自:「improve365」,原文:https://url.hi-linux.com/AYs1x,版权归原作者所有。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69999831/viewspace-3141885/ ,如需转载,请注明出处,否则将追究法律责任。