3、编译器(Compiler)
3.1、分词器(Tokenizer)
接口把要执行的SQL语句传递给Tokenizer,Tokenizer按照SQL的词法定义把它切分一个一个的词,并传递给分析器(Parser)进行语法分析。分词器是手工写的,主要在Tokenizer.c中实现。
3.2、分析器(Parser)
SQLite的语法分析器是用Lemon——一个开源的LALR(1)语法分析器的生成器,生成的文件为parser.c。
一个简单的语法树: SELECT rowid, name, season FROM episodes WHERE rowid=1 LIMIT 1
3.3、代码生成器(Code Generator)
代码生成器是SQLite中取庞大,最复杂的部分。它与Parser关系紧密,根据语法分析树生成VDBE程序执行SQL语句的功能。由诸多文件构成:select.c,update.c,insert.c,delete.c,trigger.c,where.c等文件。这些文件生成相应的VDBE 程序指令,比如SELECT语句就由select.c生成。下面是一个读操作中打开表的代码的生成实现: /* Generate code that will open a table for reading. */
void sqlite3OpenTableForReading(
Vdbe *v, /* Generate code into this VDBE */ int iCur, /* The cursor number of the table */ Table *pTab /* The table to be opened */ ){
sqlite3VdbeAddOp(v, OP_Integer, pTab->iDb, 0);
sqlite3VdbeAddOp(v, OP_OpenRead, iCur, pTab->tnum); VdbeComment((v, \
sqlite3VdbeAddOp(v, OP_SetNumColumns, iCur, pTab->nCol); }
Sqlite3vdbeAddOp函数有三个参数: (1)VDBE实例(它将添加指令),(2)操作码(一条指令),(3)两个操作数。
3.4、查询优化
代码生成器不仅负责生成代码,也负责进行查询优化。主要的实现位于where.c中,生成的WHERE语句块通常被其它模块共享,比如 select.c,update.c以及delete.c。这些模块调用sqlite3WhereBegin()开始WHERE语句块的指令生成,然后加入它们自己的VDBE代码返回,最后调用sqlite3WhereEnd()结束指令生成,如下:
(四)Page Cache之事务处理(1)
写在前面:从本章开始,将对SQLite的每个模块进行讨论。讨论的顺序按照我阅读SQLite的顺序来进行,由于项目的需要,以及时间关系,不能给出一个完整的计划,但是我会先讨论我认为比较重要的内容。本节讨论SQLite的事务处理技术,事务处理是DBMS中最关键的技术,对SQLite也一样,它涉及到并发控制,以及故障恢复,由于内容较多,分为两节。好了,下面进入正题。
本节通过一个具体的例子来分析SQLite原子提交的实现(基于Version 3.3.6的代码)。 CREATE TABLE episodes( id integer primary key,name text, cid int) ; 插入一条记录:insert into episodes(name,cid) values(\它经过编译器处理后生成的虚拟机代码如下:
sqlite> explain insert into episodes(name,cid) values(\
0|Trace|0|0|0|explain insert into episodes(name,cid) values(\1|Goto|0|12|0||00|
2|SetNumColumns|0|3|0||00| 3|OpenWrite|0|2|0||00| 4|NewRowid|0|2|0||00| 5|Null|0|3|0||00|
6|String8|0|4|0|cat|00| 7|Integer|1|5|0||00|
8|MakeRecord|3|3|6|dad|00| 9|Insert|0|6|2|episodes|0b| 10|Close|0|0|0||00| 11|Halt|0|0|0||00|
12|Transaction|0|1|0||00| 13|VerifyCookie|0|1|0||00| 14|Transaction|1|1|0||00| 15|VerifyCookie|1|0|0||00|
16|TableLock|0|2|1|episodes|00|
17|Goto|0|2|0||00|
4.1、初始状态(Initial State)
当一个数据库连接第一次打开时,状态如图所示。图中最右边(“Disk”标注)表示保存在存储设备中的内容。每个方框代表一个扇区。蓝色的块表示这个扇区保存了原始数据。图中中间区域是操作系统的磁盘缓冲区。开始的时候,这些缓存是还没有被使用,因此这些方框是空白的。图中左边区域显示SQLite用户进程的内存。因为这个数据库连接刚刚打开,所以还没有任何数据记录被读入,所以这些内存也是空的。
4.2、获取读锁(Acquiring A Read Lock)
在SQLite写数据库之前,它必须先从数据库中读取相关信息。比如,在插入新的数据时,SQLite会先从sqlite_master表中读取数据库模式(相当于数据字典),以便编译器对INSERT语句进行分析,确定数据插入的位置。
在进行读操作之前,必须先获取数据库的共享锁(shared lock),共享锁允许两个或更多的连接在同一时刻读取数据库。但是共享锁不允许其它连接对数据库进行写操作。
shared lock存在于操作系统磁盘缓存,而不是磁盘本身。文件锁的本质只是操作系统的内核数据结构,当操作系统崩溃或掉电时,这些内核数据也会随之消失。
4.3、读取数据
一旦得到shared lock,就可以进行读操作。如图所示,数据先由OS从磁盘读取到OS缓存,然后再由OS移到用户进程空间。一般来说,数据库文件分为很多页,而一次读操作只读取一小部分页面。如图,从8个页面读取3个页面。
4.4、获取Reserved Lock
在对数据进行修改操作之前,先要获取数据库文件的Reserved Lock,Reserved Lock和shared lock的相似之处在于,它们都允许其它进程对数据库文件进行读操作。Reserved Lock和Shared Lock可以共存,但是只能是一个Reserved Lock和多个Shared Lock——多个Reserved Lock不能共存。所以,在同一时刻,只能进行一个写操作。
Reserved Lock意味着当前进程(连接)想修改数据库文件,但是还没开始修改操作,所以其它的进程可以读数据库,但不能写数据库。
4.5、创建恢复日志(Creating A Rollback Journal File)
在对数据库进行写操作之前,SQLite先要创建一个单独的日志文件,然后把要修改的页面的原始数据写入日志。回滚日志包含一个日志头(图中的绿色)——记录数据库文件的原始大小。所以即使数据库文件大小改变了,我们仍知道数据库的原始大小。