woaidongmao

文章均收录自他人博客,但不喜标题前加-[转贴],因其丑陋,见谅!~
随笔 - 1469, 文章 - 0, 评论 - 661, 引用 - 0
数据加载中……

SQLite虚拟机工作原理

SQLite中的虚拟数据库引擎
如果你想知道SQLite内部是如何运行的,你需要粗略了解一下VDBE的工作原理,从下图可以看出,VDBE处于系统运行的中部,所以它似乎与大部分的部件都有关系,即使部分代码不直接与它交互但还是起到了支持作用的,VDBE确实是SQLite的核心部分。
clip_image001

这部分将要介绍VDBE是如何工作的,特别是VDBE指令是如何在一起配合完成对数据库的操作的,下面先用一些简单的例子介绍,然后再解决更加复杂的问题,如果读完了这篇文章,你就会对SQLite如何工作有了很好的理解了。
前言
VDBE
是用虚拟机语言来操作虚拟机的,每个程序的目的都是要访问和更新数据库,那么VDBE要执行的虚拟机语言都是专门为查找、读取、和修改数据库而做的。
每个VDBE语言指令都包括一个操作符和三个操作数,分别为P1P2P3P1是一个任意的整数,P2是一个非负整数,P3是一个指向一个数据结构或者是一个字符串,也可能是NULL,只有少数VDBE指令用到所有的操作数,很多只用一两个而已,还有一大部分根本就不用而是把自己的数据存到执行栈中。
一个VDBE程序从第0条指令开始并执行后面的一连串指令直到遇到错误或者执行到一个HALT指令。当一个VDBE完成执行任务后,所有的数据游标都关闭了,所有的内存都释放,所有的数据都从栈中弹出,所以从来不用担心内存泄漏和没有释放资源的问题。

一、向数据库中插入数据
我们开始用只有很少指令的VDEB程序来解决一个问题,假设我们已经有一个数据表:
CREATE TABLE examp(one text, two int);
也就是说我们现在已经存在一个叫examp名字的表和两列分别叫onetwo,现在假设我们一插入下面的数据:INSERT INTO examp VALUES(‘Hello, World!’,99);
现在我们就可以看一下这些指令,我们可以先打开SQLite3命令窗口,并且建立好上面的表,并且插入数据,插入语句这样写:
EXPLAIN INSERT INTO examp VALUES('Hello, World!',99);
这时就会有下面的指令表产生:
clip_image002

从上面可以看出,对于一个简单的插入语句它执行了12条指令,前三条和后2条都是指令执行的开始和结尾,而真正执行的工作都是在中间7条完成的,这里没有跳转,程序从上面一直到下面执行,现在一条条的解释其含义:
0 Transaction 0 0
1 VerifyCookie 0 81
2 Transaction 1 0
指令Transaction是在开始一个事务,事务是如果遇到Commit或者Rollback操作符就结束。P1是指示这个事务所在的数据库文件的索引,0表示是主数据库文件,当一个事务开始时,在数据库文件上要加上写锁,当一个事务在运行的时候其它的进程就不能读或者写这个文件了,开始一下事务也会产生一个回滚日志,在数据库文件发生任何修改之前事务必须开始。
指令VerifyCookie是检查数据模式的版本来确保它与它在上次读数据库模式得到的信息是一致的。P1是数据库编号(0表示主数据库),这样是为了确保数据库模式没有被其它的线程修改。
第二个Transaction指令是在开始一个事务并且对数据库1产生一个日志文件,这个数据是用于临时表的。

3 Integer 0 0
4 OpenWrite 0 3 examp
指令Integer是将一个整型值P10)放到栈中,这里的0表示将要修改的数据库,如果P3不为NULL,那么它将是用一个字符串类型来表达同样的整数。现在栈的状态为:
(integer) 0

指令OpenWrite是在P10)数据库中,对表examp表打开一个读/写游标,它的根页面是P2(在这个数据库文件中是3),游标可以是任意一个非负整数,但是VDBE是在一个数组中分配游标的,这个数组的大小为最大游标数加一,所以为了节省内存,最好就是从0位置开始一直加上去。这里的P3是将要被打开的表名,但是这里并没有使用它,只是为了更好的读代码。这条指令会把数据库编号0从栈中弹出来,所以现在栈又变成空的了。

5 NewRecno 0 0
指令NewRecno是产生一个新的整数记录来让游标P1指向它,这个记录值现在还不被用作关键字,新的记录被存入栈中,现在栈的状态如下:
(integer) new record key


6 String 0 0 Hello, World!
指令String是将操作数P3放入栈中,现在栈的状态为:
(string) "Hello, World!"
(integer) new record key


7 Integer 99 0 99
Integer
指令是将操作数P1放入栈中,现在栈的状态为:
(integer) 99
(string) "Hello, World!"
(integer) new record key


8 MakeRecord 2 0
MakeRecord
指令是将P12个)栈顶数据弹出栈,并且将它们转换成二进制类型的数据来存入数据库方件中,被这条指令处理过的记录又一次压入栈中,现在栈的状态为:
(string) "Hello, World!"
99
(integer) new record key


9 PutIntKey 0 1
指令PutIntKey是从栈弹出两个数据并且将这两个数据写入游标P1所指的表中,这个新的记录如果已经存在则被覆盖,如果不存在则新创建。这条记录的值是栈顶记录,而主键则是栈中第二条记录(注:也就是在SQLite每个表中的系统主键rowid)这条指令会使栈弹出两次,因为操作数P21,所以行的改变数为1并且rowid会存储到sqlite_last_insert_rowid()函数的返回值中,如果P20,那么行修改数就不会改变,这条指令就说明插入操作所做的工作。

10 Close 0 0
指令Close是关闭一个先前打开的游标P1,如果P1现在处于关闭状态则这条指令无操作。

11 Commit 0 0
指令Commit会使所有的改变都存储到数据库中,在下一个事务开始之前再不会产生任何的修改,这条指令也会删除日志文件并且释放了数据库锁,如果游标还是在打开状态的话,一个读锁可以继续持有。

12 Halt 0 0
指令Halt使得VDBE引擎立即退出,所有打开的游标等都自动关闭,操作数P1sqlite_exec()接口的返回值,对于一个正常的Halt,返回应该是SQLITE_OK (0).,如果是出现错误,就可能会得到其它的信息,P2操作数只有出现错误时候才会用到,也有一个隐含的Halt指令“Halt 0 0 0”在每个程序的结尾,这是VDBE在准备执行的时候附加上去的。

接着上一节的插入原理,现在来讲一下查询的执行原理: 

二、简单的查询
到现在为止,已经知道VDBE是如何将数据写入数据库中的了,现在下来看看查询是如何工作的,下面是我们用到的例子:SELECT * FROM examp;
下面就是对执行这条SQL语句产生的指令:
clip_image003

在看这个问题之前我们还是先看一下SQLite的查询是如何工作的,这样我们才知道我们需要完成什么工作,对于查询结果的每一行,SQLite都会调用一个Callback函数:
int Callback(void *pUserData, int nColumn, char *azData[], char *azColumnName[]);
SQLite
库会给VDBE提供一个指向回调函数的指针和一个pUserData指针(不管是回调函数指针还是pUserData指针,它都是由API函数sqlite_exec()传进来的)VDBE的工作就是为nColumnazData[]azColumnName[]取值,nColumn就是查询结果的列数,azColumnName[]数组中的每个字符串就是查询结果的每一列名,azData[]放的是实际的数据。


0 ColumnName 0 0 one
1 ColumnName 1 0 two
VDBE
的查询程序的前两条指令是为azColumn设置值的,ColumnName指令是告诉VDBE应该给azColumnName数组设置什么值的,每次查询都是以ColumnName指令开始的,对查询结果的每个列都会有这样的指令。并且在后来的查询中对于每一列都会有相应的列指令。

2 Integer 0 0
3 OpenRead 0 3 examp
4 VerifyCookie 0 81
指令23打开一个要访问的数据库表的游标,这人工作和在插入数据时候用到的OpenWrite指令是差不多的,除了这回打开是用来读的而那个是写的,指令4象在插入的例子中一样是用来验证数据库模式的

5 Rewind 0 10
指令Rewind是初始化一个对要查询表的循环的迭代器,它把游标定位到表的第一个数据上,这是Column指令和下一条指令所需要的,下一条指令将会用这个游标迭代整个表的,如果这个表是空的则跳转到P210),如果表不是空的,则就跳到下一条指令,从现在开始就到了循环体部分了。

6 Column 0 0
7 Column 0 1
8 Callback 2 0
上面的指令是整个循环体,它对表中的每一条记录都是只执行一次,上面67指令都是将P2操作数对应的各自的列的数据放到栈中,在这个例子中,第6条指令是将one列中的数据放到栈中,而第7条指令是将two列中的数据放到栈中,第8条指令是引发对回调函数的执行,这时它的P1操作数将会变成查询结果的列数,这条指令会将P1条记录出栈并放到azData[]数组中。

9 Next 0 6
这条指令是在执行一个循环的分叉部分,从第5条指令到现在是构成了整个循环的逻辑结构,这是个应该值得注意的关键概念,这个指令是将游标前进指向下一条记录,如果前进成功,那么立即跳转到这个循环开始,如果这个前进不成功,则继续执行下面的结束循环的指令,而不跳回。

10 Close 0 0
11 Halt 0 0
Close
指令是在程序的结尾将指向要查询表的游标关闭,其实没必要在这里执行这条指令,因为在程序结束后,VDBE都会把所有的游标自动的关闭,Halt指令是用来关闭VDBE程序的。
注意到查询记录时没有用到TransactionCommit指令,而在插入的时候用到了,因为SELECT是个读操作,而不会改变数据库,所以它不需要事务。

 

posted on 2009-06-20 03:16 肥仔 阅读(786) 评论(0)  编辑 收藏 引用 所属分类: 数据库


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理