sqlite3存储格式
本篇介绍sqlite3数据库文件的存储格式。
通过阅读源读源代码可以知道sqlite的设计思想。
一个sqlite数据库文件对应着一个数据库。sqlite将数据库文件划分大小一致的存储(以区分内存)页面,并通过一系列数据结构将它们组织起来。
sqlite组织页面的数据结构主要有B树和二维链表。每一个页面要么是B树的叶子或结点,要么是二维链表的一个节点。用作B树的页面都有8或12字节的页面信息头,特别地第一个页面除了是表的根结点是数据库文件的根,所以它首先包含100字节的是库信息,然后才是作为一个是B树的根结点。另一方面闲置的页面没有页面头,它的链头是在第一个页面的库信息头。二维链表的第一维由单向链表实现,第二维由数组实现。用作第一维节点的页面也是一个空闲页面,被统计在库信息头。
数据库层面的表和索引,在底层存储格式中用B树结构组织,第一张表或索引都对应着一棵B树。底层存储中所有空闲(闲置)页面,都由二维链表链接起来。
B树页面,有统一的存储格式。首先是页面信息头,然后紧跟着页内数据排序索引(请区分数据库的表索引,这里是数据结构中的外部排序索引)数组;而页内数据存储在页面的尾部。数据索引和数据单元分别从正反两个方向向页面中间伸展,页面中间部分形式空闲区域。
sqlite数据库文件使用自然字节序存储数据,这种字节序有利于它里面使用到可变长整形解释。
各种具体的数据结构和详细的格式编排请自行参阅源代码,不一一贴上划水。自行用任意一门编程语言写程序遍历解读sqlite数据库文件其中的内容。
在这里定义本篇使用的一些术语:
页面,数据库文件页面,请区分内存页面或虚地址空间页面,尽管sqlite默认页面大小也是4KB。
指针,请区分c语言指针,这里指文件内地址索引,或页面内地址偏移。
单元,cell,存储在B树页面的单位数据。
整型主键,sqlite表默认的自增计数。
变长整型,sqlite存储中使用到的一种基于自然字节序的变长整数存储方式。每字节低7位为有效数值位,高位标记下一字节是否继续为当前变长整数的一部分,高位为0表示为变长整数的最后一个字节。
叶子,B树的叶子结点。
结点,B树的非终端结点。
索引,当提及结构化查询时指索引表,而当提及低层存储格式时等同于编号,地址和指针。
前面总括了底层存储组织,现在结合上一层数据库层来看存储组织,库和表的存储。sqlite将一切表和索引视作表,并以B树的形式保存在数据库文件。sqlite数据库默认有一张管理表,名为sqlite_master。这张表记录着用户创建的所有表和索引的名字,左联合表名,表的根页面索引号以及创建sql语句。也就是数据库文件的第一页面一棵入口B树的根,通过这棵树可以索引到其它B树(数据库层面表或索引)的根。当sqlite打开某一张表时,必须遍历管理表找出与表名匹配的记录,从而找出记录中根页面索引号,才能定位到表的入口位置。索引和表是分开存储的,表默认使用是自增计数,因为存储在B树中,所以每条记录都必须有一个唯一键。索引就是一张索引键与自增计数的映射表。当使用索引查询记录时,就相当于索引左联合到数据记录的表,条件为自增计数。总结来说,就是sqlite数据库文件里保存了多于一棵的B树,第一棵B树入口位于第一页面,其它B树的根必须通过第一棵B树才能索引出来。
首先我们来看sqlite数据库的总起信息,也就是入口,位于第一页面的开头100字节。
<img dbheader/>
可看到数据库文件开头作了文件类型标记“SQLite format 3\0”。当前数据库以4KB尺寸来划分页面。数据库文件迄今被修改过461次,并且没有空闲页面。当前默认编码方式为UTF-8。
接着就是第一个页面作为B树根的页面信息。
页面信息首先定义了当前页面的类型,包含自增计数的非叶子结点并且有左孩子记录。跟着就是当前页面的状态信息,有1条记录(单元),下一条插入的记录(单元)的位置,结点的右孩子位于索引号为141的页面(也就是第141个页面)。当“number of cells on this page”和“first byte of the cell content area”越靠近,表示页面内空闲空间越小。
下面是对第一个页面的入口B树跟踪。
第一行是当前页面号为1, 类型是结点,右孩子在141号页面,左孩子分别是139号页面以23为key,只有一个左孩子。
第二行是当前页面号为139,类型是叶子,存放23条记录。根据上下文知,是1号页面的左孩子,并且存放小于等于key23的记录,一共有23条记录。
第三行是当前页面号为141,类型是叶子,存放23条记录。根据上下文知,是1号页面的右孩子,是刚刚分裂成139和141两个叶子,各存放一半数量的记录。
下面是第139号页面,作为左孩子叶子页面的跟踪。
绿色框框是我们关心的信息,描述了记录在当前页面的指针,记录长度以及唯一键值(自增计数)。根据上下文139号页面,这个B树叶子存放自增计数小于等于23的记录一共23条。
蓝色框框是记录单元里的数据,是表的根页面指针,在当前情景中,2,4,5,11和12都是某张表的根页面索引号。
下面我们对2号页面为根的表进行跟踪。
分别对2,4以及12号页面为根三张表的B树存储组织,蓝色框框叶子记录总数与使用sqlite查询的结果一致。
最后我们跟踪一下空闲页面,在本篇中使用的数据库不包含空闲页面,现在我们truncate一个表以产生一些空闲页面,就拿上面举例的12号页面为根的表,这张表包含了1个根结点和6个叶子,其中5个叶子是左孩子,1个叶子是右孩子。
留意绿色框框,文件修改次数被加1,刚刚有一个表被清空了。
蓝色框框显示了本次清空动作,产生了8个空闲页面,而链接这些页面的第一节点在第60号页面。
红色框框显示了放在60号页面节点的页面索引号,对照上面12号页面为根的表跟踪,可以看到被清空的表的存储B树所使用到的页面都被链接到空闲链表。
介绍完以页面为单位存储大框架后,我们再来看页面内的存储。
页面分为三部分,页面信息头,单元(cell)指针(索引)数组,以及数据(记录)单元。索引和数据分别放于页面的一头一尾,中间为未使用空间,各自向中间获取分配空间来应对增长。当某一单元被删除,它的空间会被回收链入到页面内空闲块链表。当空闲块再次被使用时可能规格不一致,从而会产生碎片(零碎字节),这些碎片将不能被重用,因而必须记录下来,供往后页面零碎程序评级用。我猜过于零碎的页面可能会在合适的时机进行重整。也就是单元区域遍历所有单元,必须依赖页面内索引数组。只有索引数组内索引到的单元才是有效的。对于未曾发生过单元回收重用的页面,单元区域可以直接遍历,但发生过单元回收后则不可,必须依赖页面内索引。
虽然说管理表是以B树存储,但是却不是以表名为键而只是单纯的自增计数,所以当表(包括索引,视图等)的数量多的时候,打开其中之一也没有得益于B树结构。
sqlite数据库文件使用的B树是一种B-树。参考我们曾经的教材严女士的《数据结构》对B-树的定义。
一棵m阶的B-树,或为空树,或为满足下列特性的m叉树:
(1)树中每个结点至多有m棵子树;
注:这是由sqlite数据库页面大小限制,对于Key为变长时,并不是固定m阶,而是一个泛数多阶。
(2)若根结点不是叶子,则至少有两棵子树;
注:当表的首个页面空间可以容纳所有数据时为叶子,当不满足时将分裂出两个叶子,开始变成根结点。
(3)除根之外的所有非终端结点至少有m/2的上限棵子树;
注:当结点容量不足时结点会分裂出两个结点,各迁移一半key。
(4)所有的非终端结点中包售下列信息数据(n,(A1,K1),(A2,K2),...(An,Kn), An+1),其中n为K(ey)的个数,A(ddr)是子树地址。
注:n个Key意味有n个左孩子,A1至An是左孩子页面号,An+1是右孩子页面号。n和An+1保存在页面头,(Ax,Kx)|x<n则是结点页面的数据单元。
(5)所有的叶子都出现在同一层次上,并且不带信息。
注:不带信息也就是页面类型是叶子时,数据单元忽略左孩子域。但sqlite有没有维护树高度未能证明。
通过本篇,相信大家已经对sqlite数据库有了很具体的了解了。有了上面的知识大家就能对sqlite的存储性能进行一定的分析,比如数据库页面的大小对增删改查的影响。
sqlite3存储格式的更多相关文章
- iOS 数据存储之SQLite3的使用
SQLite3是iOS内嵌的数据库,SQLite3在存储和检索大量数据方面非常有效,它使得不必将每个对象都加到内存中.还能够对数据进行负责的聚合,与使用对象执行这些操作相比,获得结果的速度更快. SQ ...
- Android中如何使用命令行查看内嵌数据库SQLite3
转载博客:http://www.linuxidc.com/Linux/2011-06/37135.htm 在上图中,除了最后一个红色的方框,其它方框都是adb shell下的命令. [1]在Andro ...
- flask+sqlite3+echarts2+ajax数据可视化
前提: 准备Python + Flask+Sqlite3的平台环境(windows系统) 前面一节介绍flask怎么安装了,剩下sqlite3下载后解压,然后环境变量添加解压路径就行了 附加下载地址: ...
- flask+sqlite3+echarts2+ajax数据可视化报错:UnicodeDecodeError: 'utf8' codec can't decode byte解决方法
flask+sqlite3+echarts2+ajax数据可视化报错: UnicodeDecodeError: 'utf8' codec can't decode byte 解决方法: 将 py文件和 ...
- 安卓使用SQlite3数据库无法id主键无法自动增加?不是的。
安卓使用SQlite3数据库无法id主键无法自动增加?不是的. 要这样写:id integer primary key ,要写integer而不是int所以会报错! http://blog.csdn. ...
- 安卓虚拟机adb shell sqlite3数据库
adb shell 连接: //http://www.cnblogs.com/xiaobo-Linux/ Android把数据都存放在data/data目录下. 我们使用cd命令转到data/data ...
- SQLite源程序分析之sqlite3.c
/****************************************************************************** ** This file is an a ...
- SQLite3源程序分析之查询处理及优化
前言 查询处理及优化是关系数据库得以流行的根本原因,也是关系数据库系统最核心的技术之一.SQLite的查询处理模块很精致,而且很容易移植到不支持SQL的存储引擎(Berkeley DB最新的版本已经将 ...
- SQLite3源程序分析之虚拟机
前言 最早的虚拟机可追溯到IBM的VM/370,到上个世纪90年代,在计算机程序设计语言领域又出现一件革命性的事情——Java语言的出现,它与c++最大的不同在于它必须在Java虚拟机上运行.Java ...
随机推荐
- Requirejs加载超时问题的一个解决方法:设置waitSeconds=0
有时Requirejs会遇到加载js超时问题 除了排查js脚本问题,网络问题以外的一个解决方法是加大Require的等待时间waitSeconds,或者直接设置为0,这个参数的意义是:The numb ...
- IDE、SATA、SCSI、SAS、FC、SSD硬盘类型介绍[zz]
目前所能见到的硬盘接口类型主要有IDE.SATA.SCSI.SAS.FC等等. IDE是俗称的并口,SATA是俗称的串口,这两种硬盘是个人电脑和低端服务器常见的硬盘.SCSI是"小型计算机系 ...
- 搭建mongodb集群(副本集+分片)
搭建mongodb集群(副本集+分片) 转载自:http://blog.csdn.net/bluejoe2000/article/details/41323051 完整的搭建mongodb集群(副本集 ...
- HTTP2 学习
一.HTTP1.x存在的问题 Http1.0时Connection无法复用,同一时间一个Connection只能处理一个request.Http1.1引入了Request pipelining来解决这 ...
- VC++ 学习笔记(三):摩登之路——C++/CLI简介
在Windows上,除非我们必须得用C++来写界面,否则我会选择避免,避免学习和使用MFC.替代的方案是用C#来做界面,然后用C++/CLI来连接C#和Native C++.那么问题来了,C++/CL ...
- Floyd-Warshall算法
Floyd也是采用动态规划的方案来解决在一个有向图G=(V,E)上每对顶点间的最短路径问题.运行时间为Θ(V3). 算法分析: 用邻接矩阵map[][]存储有向图,用dist[i][j]表示i到j的最 ...
- openssl 学习之从证书中提取RSA公钥N 和 E
原文链接: http://blog.csdn.net/kkxgx/article/details/19850509 通常数字证书包含很多信息,其中N和E值即我们称为的公钥.如何从PEM 或者DER格式 ...
- 使用B或BL跳转时,下一条指令的地址是这样计算的
B跳转指令:它是个相对跳转指令,其机器码格式如下: [31:28]位是条件码:[27:24]位为“1010”(0xeaffffff)时,表示B跳转指令,为“1011”时,表示BL跳转指令:[23:0] ...
- u3d 性能优化
http://blog.csdn.net/candycat1992/article/details/42127811 写在前面 这一篇是在Digital Tutors的一个系列教程的基础上总结扩展而得 ...
- Python初学者需要注意的问题
一.注意你的Python版本 Python官方网站为http://www.python.org/,当前最新版本为3.4.0 alpha,稳定版本为3.3.2,在3.0版本时,Python的语法改动较大 ...