一、内核概述
为了能更好的理解 SQLite,我们先从总的结构上讨论了内核,从全局把握 SQLite 很重要。SQLite 的内核实现不是很难,但是也不是很简单。本次主要讨论虚拟机 (Virtual Machine)。
VDBE 是 SQLite 的核心,它的上层模块和下层模块都是本质上都是为它服务的。它的实现位于 vbde.c, vdbe.h, vdbeapi.c, vdbeInt.h, 和 vdbemem.c 几个文件中。
它通过底层的基础设施 B+Tree 执行由编译器(Compiler)生成的字节代码,这种字节代码程序语言(bytecode programming lauguage)是为了进行查询,读取和修改数据库而专门设计的。字节代码在内存中被封装成 sqlite3_stmt 对象(内部叫做 Vdbe,见 vdbeInt.h),Vdbe(或者说 statement)包含执行程序所需要的一切: a) a bytecode program b) names and data types for all result columns c) values bound to input parameters d) a program counter e) an execution stack of operands f) an arbitrary amount of “numbered” memory cells g) other run-time state information (such as open BTree objects, sorters, lists, sets)
字节代码和汇编程序十分类似,每一条指令由操作码和三个操作数构成:<opcode, P1, P2, P3>。 Opcode为一定功能的操作码,为了理解,可以看成一个函数。 P1 是 32 位的有符号整数, p2 是 31 位的无符号整数,它通常是导致跳转(jump)的指令的目标地址(destination),当然这了有其它用途; p3 为一个以 null 结尾的字符串或者其它结构体的指针。和 C API 不同的是,VDBE 操作码经常变化,所以不应该用字节码写程序。 下面的几个 C API 直接和 VDBE 交互: ? sqlite3_bind_xxx() functions ? sqlite3_step() ? sqlite3_reset() ? sqlite3_column_xxx() functions ? sqlite3_finalize()
二、VDBE与B-tree的关系
B-Tree 使得 VDBE 可以在 O(logN)下查询,插入和删除数据,以及 O(1)下双向遍历结果集。B-Tree 不会直接读写磁盘,它仅仅维护着页面(pages)之间的关系。
当 B-TREE 需要页面或者修改页面时,它就会调用Pager。当修改页面时,pager 保证原始页面首先写入日志文件,当它完成写操作时,pager 根据事务状态决定如何做。B-tree 不直接读写文件,而是通过 page cache 这个缓冲模块读写文件对于性能是有重要意义的,这和操作系统读写文件类似,在 Linux 中,操作系统的上层模块并不直接调用设备驱动读写设备, 而是通过一个高速缓冲模块调用设备驱动读写文件,并将结果存到高速缓冲区。
三、B-tree初步分析
SQLite中每个数据库完全存储在单个磁盘文件中,因为B树进行数据的查找、删除、添加速度快,所以这些数据以B树数据结构的形式存储在磁盘上(实现代码在btree.c源文件中)。
B树的典型结构如图2所示。B+树是应文件系统所需而出的一种B-树的变型树。B+树可以进行两种查找算法,第一种,从最小关键字起顺序查找;第二种,从根结点开始,进行随机查找。B-tree应用到数据库的每个表和索引中。所有B-tree存储在相同的磁盘文件中。
btreeInt.h 中 b树结构定义:
struct Btree {
sqlite3 *db;
BtShared *pBt;
u8 inTrans;
u8 sharable;
u8 locked;
u8 hasIncrblobCur;
int wantToLock;
int nBackup;
u32 iBDataVersion;
Btree *pNext;
Btree *pPrev;
#ifdef SQLITE_DEBUG
u64 nSeek;
#endif
#ifndef SQLITE_OMIT_SHARED_CACHE
BtLock lock;
#endif
};
B-tree为SQLiteVDBE提供O(㏒N)级时间复杂度的查询和插入,通过遍历记录实现O(1)级时间复杂度的删除。B-tree是自平衡的,并能够对碎片清理和内存再分配进行自动管理。B-tree对如何读写磁盘没有限定,只是关注页之间的关系。
B-tree的职责就是排序,它维护着多个页之间错综复杂的关系,这些关系能够保证快速定位并找到一切有联系的数据。B-tree将页面组织成树状结构,这些组织结构很适合搜索,页面就是树的叶子。Pager帮助B-tree管理页面,它负责传输。 B-tree是为查询而高度优化了的。它使用特殊的算法来预测将来要使用哪些页,从而让B-tree保存该页面以便尽可能快地工作。 数据库中所有的页都是以1开始顺序编号的。一个数据库是由多个B-tree组成的——每张表以及每个索引各对应一个B-tree。数据库中每张表或索引都以根页面作为第一页。所有的索引和表的根页面都存储在sqlite_master表中。 B-tree中的页由一系列B-tree记录组成,这些记录也称为有效载荷。这些记录不是传统的数据库记录(表中有多个列的那种格式),而是更为原始的格式。一个B-tree记录(有效载荷)仅由两个域组成:键值域和数据域。键值域是每个数据库表中所包含的ROWID值或主键值;在B-tree中,数据域可以包含任意类型的内容。最终,数据库的记录信息存储在数据域中。B-tree用来保持记录有序并方便记录的查询,同时,键值域能够完成B-tree的主要工作。此外,记录(有效载荷)的大小是可变的,这取决于内部键值域和数据域的大小。一般而言,每个页拥有多个有效载荷,但如果一个页的有效载荷超出了一个页的大小,将会出现跨越多个页的情况(包括blob类型的记录)。
|