嵌入式数据库典型技术―sqlite和Berkeley DB的研究
摘要:与常见的数据库相比,嵌入式数据库具有体积小、功能齐备、可移植性、健壮性等特点,本文分析和比较了典型的嵌入式数据库sqlite和Berkeley DB。首先从体系结构、子系统间调用关系、任务执行过程等角度对sqlite和Berkeley DB进行了详细分析,然后重点从数据类型、存储方式、模式、数据库引擎和错误处理及加密功能等方面讨论了sqlite和Berkeley DB的异同点,最后列举了一个基于ARM—Linux的sqlite应用实例。
关键词: sqlite、Berkeley DB、sql、虚拟数据库引擎(VDBE)
引言
随着计算机技术与其它学科间的不断交融、渗透,数据库应用的范围更加深入和具体。那些仅适用于PC机,体积庞大、延时较长的数据库技术已不能满足针对性较强的嵌入式系统开发的需求。SOLite和Berkeley DB是目前应用较广泛、技术较稳定的两种嵌入式数据库。然而,国内对嵌入式数据库的研究起步较晚,还没能引起更多人的关注。更多人熟悉那些基于C/S或B/S结构的关系型数据库来实现数据的存储、检索等功能。然而,在嵌入式系统中,由于软硬件资源有限,不可能安装庞大的数据库服务器,而用户的需求可能由一个简单的基于磁盘文件的数据库系统就能实现,这仅仅是利用了那些数据库的基本特性。此时,对嵌入式数据库的研究就显得尤为重要了。
一、嵌入式数据库
嵌入式数据库通常是与嵌入式操作系统及具体的应用集成在一起,无需独立运行数据库引擎,由程序直接调用相应的API就可实现对数据的存取操作。嵌入式系统的开发环境决定了其数据库的特点:
1、体积适当
由于嵌入式系统自身的特点,对数据的存储和程序的运行都有较强的空间限制,所以嵌入式数据库首先应该保障的就是适当的体积。进一步来说就是占用尽量少的ROM、RAM及cpu的资源。
2、功能齐备
嵌入式系统开发中,用户需求决定了需要一个大小适中、功能齐备的数据库来实现数据管理,这就使得开发人员要采用一个能够提供完备开发文档且易于开发的数据库技术。此外,国家863项目2002AA714023,研究生精品课程资助05531451在嵌入式设备中,数据库的管理对用户来说是透明的,这就要求此数据库能够自动完成启动初始化、日志管理、数据压缩、备份、数据恢复等功能;而且嵌入式设备经常有不可预料的硬复位,这就需要此数据库有高度的健壮性。
3、可移植性
嵌入式系统的平台种类繁多,因此嵌入式数据库应有一定的可移植性,以适用于不同的软硬件平台。
4、代码开源
开源的代码在产品的开发过程中不仅可以减少开发成本,更重要的是为后期的维护完善和稳定运行都提供了最为彻底的解决方法。
二、sqlite
sqlite是D.理查德.希普用一个小型的C库开发的一种强有力的嵌入式关系数据库管理体制。虽然功能较Berkeley DB稍显逊色,但它简单易学、速度较快,同时提供了丰富的数据库接口,提供了对sql92的大多数支持:支持多表和索引、事务、视图、触发和一系列的用户接口及驱动。
图1 sqlite的体系结构
sqlite的体系结构大体上可以分成八个主要的子系统,如图1所示。对数据库进行的各种操作都是按照此顺序,逐一执行的。顶层是标记处理器(tokenize)和分析器(parser)。sqlite有自己高度优化的代码生成器,可以快速、高效地生产出代码。底部是经过优化的B树,这样有助于运行在可调整的页面缓冲上时,对磁盘的查找降低到最小。再往下是页面高速缓存,它作用在OS的抽象层之上,这样的体系结构使数据库的可移植性变为可能。
该体系结构的核心是虚拟数据库引擎(VDBE)。VDBE完成与数据操作相关的全部任务,并且是客户和存储之间信息交换的中间单元。从各个角度分析,它都是sqlite的核心。当sql语句被分析后,VDBE便开始工作。代码生成器将分析树翻译成一个袖珍程序,随后这些袖珍程序又被组合成VDBE的虚拟机器语言表示的一系列指令。如此反复,VDBE执行每条指令,最终完成sql语句指定的查询要求。
sqlite有以下特性:支持ACID事务、零配置―无需安装和管理配置、存储在单一磁盘文件中的一个完整的数据库、数据库文件可以在不同字节顺序的机器间自由共享、支持数据库大小至2TB、足够小、全部源码大致3万行C代码,250KB、比目前流行的大多数据库运行速度快,提供了对事务功能和并发处理的支持、应用Transaction既保证了数据的完整性,也会提高运行速度,因为多条语句一起提交给数据库的速度会比逐一提交的方式更快、独立、没有额外依赖。
三、Berkeley DB
Berkeley DB是由sleepycat software开发的轻量级嵌入式数据库,它不仅适用于嵌入式系统,而且可以直接连接到应用程序内部,和应用程序运行在同一地址空间。传统的数据库一般作为独立服务器工作,而Berkeley DB是软件开发库,开发者将它嵌入到应用程序中,应用程序本身就是一个服务器,而只是利用嵌入式数据库开发来实现定制的数据库逻辑,避免了与应用服务器进程间通信的开销,因此Berkeley DB具有较高的运行效率,适用于资源受限的嵌入式系统。
一般而言,Berkeley DB数据库系统可以大致分为五个子系统,如图2所示。
图2 Berkeley DB 子系统图
1、存取管理子系统(Access Methods)
该子系统为创建和访问数据库文件提供基本的支持。在没有事务管理的情况下,该子系统中的模块可单独使用,为应用程序提供快速高效的数据存取服务。
2、内存池管理子系统(Memory Pool)
该子系统就是Berkeley DB所使用的通用共享内存缓冲区,该子系统可以被应用程序单独使用。
3、事务子系统(Transaction)
该子系统为Berkekey DB提供事务管理功能,保证操作的原则性、一致性和孤立性。事务子系统适用于对需要事务保证的数据进行修改的场合。
4、锁子系统(Locking)
该子系统提供进程之间以及进程内部的并发管理机制,为系统提供多用户读取和单用户修改同一对象的共享控制。该子系统可以被应用程序单独使用。
5、日志子系统(Logging)
该子系统采用的是先写日志的策略,支持事务子系统进行数据恢复,保证数据一致性。
四、sqlite与Berkeley DB的异同
通过上面的一些介绍,也许会对sqlite和Berkeley DB有了一定的了解。从目前的趋势看,这两款嵌入式数据库有着旺盛的生命力,较好的应用领域及发展空间。笔者翻阅了大量的资料,从各个角度,对它们的异同进行了多方面,多层次的比较,如表1所示。
表1 sqlite与Berkeley DB的异同
特性 |
sqlite |
Berkeley DB |
是否为关系数据库 |
是 |
否 |
是 |
否 |
|
开发语言 |
C语言 |
C、Java语言 |
数据类型 |
无 |
无 |
存储方式 |
转换成ASCII码 |
原样存储 |
存储模式 |
Btree |
Btree、Hash、Queue和Recno |
数据库引擎 |
虚拟 |
无 |
适用系统 |
从ARM/Linux到SPARC/Solaris多种硬件平台 |
UNIX/POSIX systems、Win32及嵌入式系统WinCE、VxWorks等 |
错误处理 |
较少 |
较详细 |
加密功能 |
弱 |
强 |
是否免费 |
全部 |
部分 |
难易程度 |
较易 |
较难 |
通过此表我们可以较为直观地看到,sqlite和Berkeley DB在数据库类型、开发语言、存储方式、模式等方面有着较大的差异。下面笔者就对其中某些重要方面进行相对详细的论述:
1、数据库类型
sqlite基于关系数据库模式,支持绝大多数标准的sql92语句,在很大程度上实现了ANSI sql92标准,特别是支持视图、触发器、事务,支持嵌套sql。它通过sql编译器(sql Complier)来实现sql语言对数据库进行操作,采用单文件存放数据库。在操作语句上更类似关系型数据库的产品使用,非常方便。这也就使得那些曾经有过PC机数据库经验的人,对sqlite的学习变得易如反掌。
此外,sqlite也有API的概念,而且极其易于使用,只需要三个用来执行sql和获得数据的函数。它还是可以扩展的,允许程序员自定义函数,然后以callback的形式集合进去。C语言API是脚本接口的基础,如已经发布的(Tcl接口)。开放源码团体已经扩展了众多的客户接口、适配器、驱动等,这就使得其他语言对sqlite的使用也成为可能。
Berkeley DB不是关系型的数据库,不能应用标准的sql语句对数据库操作,对它的操作要调用专用的API实现。这些API提供了查询、插入、删除等功能。使用Berkeley DB提供的函数来进行数据库的访问和管理并不复杂。在大多数场合下,只需按照统一的接口标准进行调用就可以完成最基本的操作。
2、存储方式及模式
sqlite只提供了Btree存储数据的模式。对二进制数据,sqlite不能直接保存;但可以先将二进制的数据转换成ASCII编码,然后再保存。Base64编码机制是最常见的把二进制数据转换成ASCII编码的手段。在sqlite的C语言代码encode.c中,提供了Base64编码的功能。
Berkeley DB对任何存入的数据都是按原样直接存储到数据文件中去,无论其是二进制数据还是ASCII或Unicode等编码的文本。Berkeley DB提供了四种存储数据的模式:Btree、Hash、Queue和Recno。在打开数据库的时候,要指定一种存储模式。
对于以上各种存储模式的具体定义、优缺点、及适用范围,由于篇幅有限,在此就不过多叙述,如有需要可参阅相关资料。
3、数据类型
sqlite最大的特点在于其数据类型为无数据类型(typelessness)。这意味着可以保存任何类型的数据到所想要保存的任何表的任何列中,无论这列声明的数据类型是什么。虽然在生成表结构的时候,要声明每个域的数据类型,但sqlite并不做任何检查。开发人员要靠自己的程序来控制输入与读出数据的类型。这里有一个例外,就是当主键为整型值时,如果要插入一个非整型值时会产生异常。
虽然,sqlite允许忽略数据类型,但是,仍然建议在Create Table语句中指定数据类型,因为数据类型有利于增强程序的可读性。另外,虽然在插入或读出数据的时候是不区分类型的,但在比较的时候,不同数据类型是有区别的。
在Berkeley DB中关键字(key)和数据(data)是用来进行数据库管理的基础,由这两者构成的key/data对,组成了数据库中的一个基本结构单元。通过使用这种方式,用API函数访问数据库时,只需提供关键字就能够访问到相应的数据。关键字和数据在Berkeley DB中都是用一个名为DBT的简单结构来表示的,它的作用主要是保存相应的内存地址及其长度。
五、 应用
sqlite嵌入式数据库提供了以源码发布的方式,要在众多的硬件平台进行移植,可以根据不同平台对源码进行交叉编译来实现。编译主要有以下几个步骤:
1、到http://www.sqlite.org/的cvs中下载最新的源代码包,解压后将生成sqlite目录,另外新建并转到一个与sqlite目录平行的同级目录,如make目录。
2、用“echo$PATH”命令查看PATH中是否已经包含交叉编译工具arm-linux-gcc。
3、为了在ARM-Linux下能正常运行sqlite,需要对sqlite/src/sqliteInt.h作一定的修改,以确保btree(B树)有正确的变量大小,如“ptr”和“char”。不同体系结构的Linux,如X86和ARM,会有些差别。对于ARM-Linux可以找到如下部分:
# ifndef INTPTR_TYPE
# if sqlITE_PTR_SZ==4
# define INTPTR_TYPE int
# else
# define INTPTR_TYPE long long
# endif
# define sqlITE_PTR_SZ 4
这样后面的“typedef INTPTR_TYPE ptr;”就是定义的“int”类型,而不是“long long”。
4、使用configure进行一些配置。修改sqlite目录下的configure,让configure不去检查交叉编译环境。由于篇幅有限不再详述。
5、修改Makefile文件。将代码行BCC=arm-linux-gcc-g-O2改成BCC=gcc-g-O2。另外,一般是以静态链接的形式将sqlite放到ARM-Linux的硬件板上运行的,所以继续修改Makefile,找到标记为sqlite:的代码段,将其中的libsqlite.la改成.libs/libsqlite.a。做完上述修改,用make生成sqlite、libsqlite.a、libsqlite.so。为了减小执行文件大小可以用strip处理,去掉其中的调试信息。
6、在ARM板上运行sqlite。将sqlite拷贝到ARM板上,方法很多,需要根据具体的情况来选择。如ftp、cm-dftp、wget等。将sqlite下载到ARM板的/tmp目录,因为此目录是可写的。修改权限并运行:
chmod+wx sqlite
会出现
sqlite>
如果一切正常,现在sqlite已经在ARM-Linux下跑了起来,然后就可以基于此进行下一步的应用开发了。
六、结语
嵌入式数据库sqlite和Berkeley DB,在体积上、功能上、运行速度及难易程度都存在着或多或少的异同。但它们都有能够充分适应硬件的能力,能很好地适应嵌入式系统的需要。就笔者来看,sqlite功能虽不及Berkeley DB强大,但它的设计思想是小型、快速和最小化的管理。这就使得sqlite在大小和功能之间找到了一个理想的平衡点,而且完全的开源代码使其可以称得上是理想的“嵌入式数据库”。当然在具体的嵌入式应用中可以根据具体情况选择应用。
参考文献:
1、http://www.sqlite.org , sqlite的官方主页
2、http://www.sleepycat.com ,Berkeley DB的官方主页
3、Michael Owens. Embedding an sql Database with sqlite. Linux Journal,2003 06 01
4、薛启康.Linux环境下的数据库.中国计算机报,2001总期号:1009
5、张孝.嵌入式移动数据库的现状及发展[J/OL]. http://www.basesoft.com
免费的实时数据库,我们该选谁?----BerkeleyDB与sqlite评测对比
http://blog.csdn.net/mynicedream
最近要做一个项目,需要用到实时数据库,PI太贵了,想找一个免费的,实在不行就只能自己编了。找了半天,找到了FastDB、BerkeleyDB和sqlite.
FastDB是内存型数据库,据说很快,但数据库大小不能大于物理内存,不然。。。反正我看到这就走了,我可是要一秒内处理几千个数据,还要保存8小时以上的啊!BerkeleyDB和sqlite倒没有数据库大小不能大于物理内存的限制,我对他们的性能进行了测试,结果如下:
插入10000条记录耗时 |
0.08秒 |
0.42秒 |
插入100000条记录耗时 |
2.31秒 |
3.81秒 |
插入7200000条记录耗时 |
1024.34秒 |
249秒 |
插入57600000条记录耗时 |
12860.78秒 |
2155.14秒 |
插入172800000条记录耗时 |
48039.64秒 |
6352.06秒 |
10000条记录数据库大小 |
0.628M |
0.527M |
100000条记录数据库大小 |
5.29M |
5.32M |
7200000条记录数据库大小 |
516M |
405M |
57600000条记录数据库大小 |
3087.13M |
3925.8M |
172800000条记录数据库大小 |
11890.7M |
10621.2M |
*机器配置:Core2 E4500cpu、2G内存
上表为两种数据库只建一个索引,Berkeley DB不支持事务、sqlite支持事务情况下的数据,如果Berkeley DB打开事务支持,速度会下降很大的数量级,根本不能满足需求。另外在程序崩溃后Berkeley DB数据库不可用,sqlite数据库仍可正常使用。
Berkeley DB与sqlite对比
嵌入式数据库无需安装,体积小巧,速度又很快,在很多场合可以替代目前流行的MysqL, sqlServer等大中型数据库。本文介绍两种嵌入式数据库产品:Berkeley DB和sqlite,并着重讨论它们与Java之间的接口。
通常我们采用各种数据库产品来实现对数据的存储、检索等功能,例如,Oracle,sql Server,MysqL等等。这些产品除提供基本的查询,删除,添加等功能外,也提供了很多高级特性,如触发器,存储过程,数据备份恢复,全文检索功能等。但实际上,很多的应用,仅仅利用到了这些数据库产品的基本特性而已。而且在一些小型应用上,或者某些特殊场合的应用,比如桌面程序,这些数据库产品就明显有一些臃肿。在这些情况下,嵌入式数据库的优势就特别明显了。
嵌入式数据库无须独立运行的数据库引擎,它是由程序直接调用相应的API去实现对数据的存取操作。更直白的讲,嵌入式数据库是一种具备了基本数据库特性的数据文件。嵌入式数据库与其它数据库产品的区别是,前者是程序驱动式,而后者是引擎响应式。嵌入式数据库的一个很重要的特点是它们的体积非常小,编译后的产品也不过几十K。这不但对桌面程序的数据存储方案是一个很好的选择,也使得它们可以应用到一些移动设备上。同时,很多嵌入式数据库在性能上也优于其它数据库,所以在高性能的应用上也常见嵌入式数据库的身影。
下面介绍的是两个开放源代码的嵌入式数据库,Berkeley DB和sqlite。同时侧重介绍如何应用Java连接这两种嵌入式数据库。
一. Berkeley DB
1. 简介
Berkeley DB是一款健壮的,高速的工业级嵌入式数据库产品,你可以在它的官方主页(见参考链接一)上发现很多知名的公司都采用了这款嵌入式数据库。 Berkeley DB的一个很重要的特点是就是高速存储。在高流量,高并发的情况下,Berkeley DB要比非嵌入式的数据库表现得更加出色。所以在一些技术实现上,Berkeley DB被作为大型关系数据库的中间数据缓冲层,用来快速的保存数据,可能会在适当的时刻再导入到大型数据库中,进而应用大型数据库所提供的更为高级的特性。
Berkeley DB虽然是开源的产品,但对某些条件下的商业性应用,却不是免费的,而且价格颇为昂贵。这些商业条件排除了开源的情况,不发放分布版本的情况,等等。比如,如果你的程序是开放源代码的或者仅仅应用到单一的网站上,在这种情况下,Berkeley DB是免费的。
2. 获得Java与Berkeley DB的接口
Berkeley DB目前的版本是4.1.25,自带了Java接口。下载的压缩包中包含C和Java语言的源代码和编译配置文件。在Windows平台,可以用MS Visual C++ 6.0或MS VC.Net编译。用VC6编译的操作如下介绍:在源代码的build_win32路径下打开VC的工程文件,之后在Build菜单中的Set Active Configuration选择db_java win32 release编译选项。在VC的Tools菜单Options选项中指定JNI.H等Java本地化接口编译时所需要头文件的位置。你会在JDK的 include路径下找到这些头文件, 例如加入的路径可能会是这样的:C:\jdk1.4\include和C:\jdk1.4\include\ win32。最后在Tools菜单中Options选项还要设置Javac.exe和Jar.exe的执行路径,这个设置会使VC开发环境也能调用 Java编译器,从而在VC环境下直接完成对Java接口类的编译和打包。在编译后,在release路径下的文件中找到db.jar,libdb41.dll,libdb_java41.dll,这三个文件组成了Berkeley DB的Java接口程序包。
3. 应用Java与Berkeley DB的接口
Berkeley DB并不是一个关系型的数据库。不能应用标准的sql语句对数据库操作,对它的操作要调用专用的API实现。这些API提供了查询、插入、删除等功能。比如com.sleepycat.db.Db类代表数据库对象。Db类的put()方法完成的是插入功能,get()方法完成的是读出数据的功能。 com.sleepycat.db.Dbc是Berkeley DB的游标类,提供了遍历数据库记录的功能。
Berkeley DB每一个记录都有一个键值和对应的数据值,而键值和数据必须是类com.sleepycat.db.Dbt的对象或其子类的对象。Dbt提供了一些方法可以将byte数组或Object对象保存到Dbt的对象中去。比如,Dbt类中的set_data(byte[])或set_object (Object)方法。注意到目前Berkeley DB中的Java API命名方法并不符合Java的命名规范,比如set_data()方法应该命名为setData()方法。Berkeley DB许诺在下一个版本中会提供符合命名规范的Java API。
Berkeley DB对任何存入的数据都是直接原样存储到数据文件中去,无论其是二进制数据还是ASCII或Unicode等编码的文本。通常可以利用这一特性和Java串行化的概念方便的进行数据的存取。例如声明一个类
public class AccountInfo implements Serializable{
//帐户信息
public String loginName;
public String password;
public boolean auotLogin;
}
在这个 AccountInfo类中仅仅包含了数据项的定义。我们完全可以将这个类看作数据库的表中字段定义。可以用Berkeley DB保存AccountInfo对象的串行化二进制数据,以此来保存这个对象中的变量值。在操作中,先对Dbt的对象调用set_object (AccountInfo)方法,而后把这个Dbt对象作为一条纪录保存到表中。当然,我们也可以应用继承Dbt类的方法来完成对数据的保存。
下面这段简单代码演示如何将数据存入到数据库中,然后再用游标对象浏览全部数据。
//注意,下面的程序的忽略了对异常处理,写入数据初始化等等一些代码,请在适当修改后再编
//译运行它
Db dbFile = null;
//生成Db对象
dbFile = new Db(null,0);
//用BTree方式打开数据库,库文件是在c:/temp下的mydata.db文件,表名是employee
dbFile.open(null,"c:\\temp\\mydata.db","employee",Db.DB_BTREE,
Db.DB_CREATE,0);
Dbt key = new Dbt();
Dbt data = new Dbt();
//向库文件中插入一条数据,如果已经存在,打印出错信息
if (dbFile.put(null,key,data,Db.DB_APPEND) == Db.DB_KEYEXIST) {
System.out.println("Key already exists.");
}
dbFile.close(0);
//重新打开数据文件
dbFile = new Db(null,0);
dbFile.open(null,Db.DB_UNKNOWN,
0,0644);
// 声明一个数据库游标Dbc对象iterator
Dbc iterator = dbFile.cursor(null,0);
// 遍历整个表
Dbt key = new Dbt();
while (iterator.get(key,Db.DB_NEXT) == 0)
{
System.out.println("reading");
}
iterator.close();
dbFile.close(0);
在运行Berkeley DB的程序时勿必在系统环境变量PATH中设置libdb41.dll和 libdb_java41.dll所在的路径。
4. Berkeley DB的存储模式
Berkeley DB提供了四种存储数据的模式:Btree,Hash,Queue和Recno。在打开数据库的时候要指定一种存储模式,比如上例中open()方法中的参数Db.DB_BTREE就是指定以Btree模式打开数据库。
Btree模式是以排序的二叉树的方式存储,Hash是以线性哈希表的方式存储。Queue用逻辑记录号做为键值,以定长的数据为记录值。Recno方式也以逻辑记录号做为键值,但可以保存定长或变长的记录值。这里提到的逻辑记录号有两种,可变的和固定的。可变逻辑记录号会根据数据记录的增加与删除做相应的变化。比如在数据库中共有100条记录,如果删除第80条记录,那么第81条记录的逻辑记录号会自动变成80,以此类推,第100条记录逻辑记录号会变成99。固定的逻辑记录号则无论数据库如何操作都不会有变化。Queue模式下,逻辑记录号只能是固定方式。 Recno模式则可通过配置来选择是采用那种类型的记录号作为键值。Btree模式也可以通过设置,将可变的逻辑记录号做为键值。
这几种存储模式各有优缺点,要根据具体的需求来选择。当键值不想用逻辑记录号时Btree或Hash是必须的选择。 Btree方式比较适合连续的顺序读取,比如,当键值是时间值,如果经常有从某一时间点开始连续读取后继的记录的操作,Btree是一种很好的选择。对随机的跳跃式读取,Hash模式则更为恰当。Queue和Recno都以记录号为键值,但前者适合先进先出的读取方式。Recno则通常是存取变长文本记录的理想存储模式。
5. Berkeley DB Environment的概念
Berkeley DB Environment为一组数据库同时提供参数设置。更为重要的是,如果要应用更高级的特性,必须要使用Environment功能,比如在想要对保存的数据进行加密存储时。
6. 更多特点
除了最基本的插入、查询、删除功能以外,Berkeley DB还提供了一些特性,比如Transaction,数据加密,同步加锁控制,错误日志等功能。下面的图片是Berkeley DB功能示意图。
二. sqlite
1. 简介
相信PHP的开发人员一定不会对sqlite感到陌生,因为在PHP5中已经集成了这个轻巧的内嵌式数据库产品。sqlite与Berkeley DB相比,在操作语句上更类似关系型数据库的产品。绝大多数标准的sql92语句sqlite都能支持。
sqlite的版权允许无任何限制的应用,包括商业性的产品。在参考链接二上提供的sqlite官方主站上可以下载到编译后的sqlite程序。但推荐应用CVS工具下载最新版本的sqlite源代码。如果在*nux平台下,可直接用make编译。如果在Windows 平台,常用的有两种方法,一是应用在Windows平台下的Linux仿真程序,如MingW或Cygwin提供的make来编译。二是应用MS VC平台编译。后者设置略有麻烦,但可仿照参考链接五上提供的MS VC6工程文件的样例。应用到这个样例的时候,要注意的是由于sqlite源代码在不断更新,如果直接应用样例所提供的VC6工程文件编译会出现一些问题,读者要根据具体的情况稍微调整一下编译的设置。
2. 编译第三方Java接口
sqlite 源代码是C,而且官方网站上只提供了C和Tcl语言的接口。为了应用Java接口,要采用第三方的接口驱程,可在参考链接三中找到这个Java接口程序。这个接口提供了两种连接sqlite的方式:一是直接用JNI技术调用sqlite的C语言接口,这种方式要求开发人员要对sqlite本身的API也有一定的了解。在第二种方式中,接口程序实现了Java标准规范的JDBC接口,这样开发人员只要对JDBC有了解就可以了。
下面介绍在Windows系统MS VC6环境中编译sqlite Java接口(同时包括JNI和JDBC两个接口)的过程。如果你对C语言编译的设置很熟悉,可以跳这这段介绍。
第一步先把sqlite源代码编译成Lib静态库文件。具体的步骤可以直接应用下面参考链接中提供的MS VC6工作区文件,其中有一个编译sqlite到静态库的设置。编译成功后得到sqlite.lib文件。第二步要建立一个新的VC DLL项目,然后和上面介绍的Berkeley DB在编译Java本地化接口的设置一样,在VC的Tools菜单Options选项中指定JNI.H等JNI编译所要的头文件位置。同时还要指定 sqlite.h头文件位置,这个文件是在生成sqlite静态库的时候自动生成的,可以在sqlite.lib文件所在的工作区目录下面找到它,例如加入的路径为C:\sqlite\msvc6。然后在Project菜单的setting选项设置Link到sqlite.lib库文件,并再次在 Tools菜单中Options指定sqlite.lib的查找路径。注意有些情况下可能要设置予编译选项HAVE_sqlITE_COMPILE以便使用sqlite中VM的一些功能。编译成功后可得到sqlite_jni.dll文件。
第三方接口库中的Java代码包含JNI接口和多个版本的JDBC接口程序,可根据你的JRE的版本选择相应的JDBC程序。编译这些Java代码的过程这里就不做叙述了。
编译后的Java类包加上前面得到的sqlite_jni.dll文件,组成了sqlite的Java接口库,在应用Java语言调用JDBC或JNI接口时,都是通过应用Java的本地化技术调用sqlite_jni.dll文件,完成对sqlite数据库的操作。
下面这段代码演示如何应用JNI接口操作sqlite。可以看到Database类的exec()方法是执行sql语句的关键:
Database db = new Database();
try {
//打开数据库
db.open("c:\\temp\\mydata.slt",0666);
db.interrupt();
db.busy_timeout(1000);
db.busy_handler(null);
db.exec("create table account (name varchar(10),gale boolean)"
,result);
db.exec("insert into account values('steve','m')",result);
db.exec("select * from account",result);
db.close();
} catch (Exception e) {
e.printStackTrace();
}
4. 应用JDBC连接sqlite
用"sqlite.JDBCDriver"作为JDBC的驱动程序类名。连接JDBC的URL格式为jdbc:sqlite:/path。这里的path为指定到sqlite数据库文件的路径,例如:
jdbc:sqlite://dirA/dirB/dbfile
jdbc:sqlite://DRIVE:/dirA/dirB/dbfile
jdbc:sqlite://COMPUTERNAME/shareA/dirB/dbfile
参考下面的应用JDBC连接sqlite的例程:
//声明JDBC驱动程序
Class clz = Class.forName("sqlite.JDBCDriver");
//连接数据库
Connection conn = DriverManager.getConnection("jdbc:sqlite:/c:/temp/e2.db");
Statement stmt = conn.createStatement();
//生成person表,包含名子和年龄字段
stmt.execute("create table person (name varchar(100),age int)");
//插入数据
stmt.execute("insert into person values('steve',25)");
//用sql语句读出数据
result = stmt.executeQuery("select * from person");
while(result.next()){
System.out.println(result.getString(1));
System.out.println(result.getInt(2));
}
运行程序时要在 Java.exe命令行加入选项java.library.path指定到sqlite_jni.dll所在的路径。例如,如果 sqlite_jni.dll放在c:\sqliteNative 路径下面,运行类com.e2one.MyClass 的命令行将会是这样:java -Djava.library.path=c:\sqliteNative com.e2one.MyClass。
5. sqlite的特点
sqlite 是无数据类型的数据库。虽然在生成表结构的时候,要声明每个域的数据类型,但sqlite并不做任何检查。开发人员要靠自己的程序控制输入与读出数据的类型是正确的。这里有一个例外,就是当主键为整型值时,如果要插入一个非整型值时会产生异常。另外,虽然在插入或读出数据的时候是不区分类型的,但在比较的时候,不同数据类型是有区别的。比如:
CREATE TABLE MyTable(a INTEGER,b TEXT);
INSERT INTO MyTable VALUES(0,0);
当执行下面的查询:
SELECT count(*) FROM MyTable WHERE a=='00';
会返回一条记录,因为字段a的类型是整型,而数字00与0是相等的。
而执行下面的查询则不会返回记录:
SELECT count(*) FROM MyTable WHERE b=='00';
因为字段b是字符类型,字符"00"与"0"是不相等的。
sqlite提供了对Transaction的支持。应用Transaction即保证了数据的完整性,也会提高运行速度,因为多条语句一起提交给数据库的速度会比一条一条的提交方式更快。
对二进制数据,sqlite不能直接保存。但可以先将二进制的数据转换成ASCII编码,然后再保存。Base64编码机制是最常见的把二进制数据转换成ASCII编码的手段。在sqlite的C语言代码encode.c中提供了Base64编码的功能。对Java而言,在参考链接六中提供的Apache的XML RPC项目中可以找到一个Base64编码的例子。
上面介绍了两个比较常见的嵌入式数据库,Berkeley DB速度极快,可靠性高,但学习起来有一定难度。sqlite则简单易用,速度也很快,又可以应用标准的JDBC连接,但它功能却照Berkeley略有逊色,比如加密功能、二进制数据的处理等。
开源嵌入式数据库:Berkeley DB和sqlite的比较
摘要深入分析、比较Berkeley DB和sqlite。Berkel ey DB和sqlite是源码开放的嵌入式数据库管理系统,无需安装,体积小巧,速度又很快;可以很方便地应用在掌上电脑、PDA、车载设备、移动电话等MysqL、sql Server这些大中型数据库不可实现的嵌入式设备上。
关键词Berkeley DB SOL,ite 嵌入式数据库
1、嵌入式数据库
通常,我们采用数据库来实现对数据的存储、检索等功能。像MysqL这类基于C/S结构的关系型数据库系统,虽然代表着目前数据库应用的主流,却并不能满足所有应用场合的需要。很多的应用,仅仅利用到了这些数据库产品的基本特性而已。有时我们需要的可能只是一个简单的基于磁盘文件的数据库系统,这样就不必安装庞大的数据库服务器,以简化数据库应用程序的设计。在某些特殊应用场合,比如在嵌入式系统中,由于系统的硬件软件资源都有限,这些数据库产品就明显有一些臃肿,甚至是不可实现的。在这些情况下,嵌入式数据库的优势就特别明显了。
嵌入式数据库通常与操作系统和具体应用集成在一起,无须独立运行的数据库引擎,由程序直接调用相应的API去实现对数据的存取操作。更直白地讲,嵌入式数据库是一种具备了基本数据库特性的数据文件。嵌入式数据库与其它数据库产品的区别是,前者是程序驱动式,而后者是引擎响应式。嵌入式数据库的一个很重要的特点是它们的体积非常小,编译后的产品也不过几十KB,在一些移动设备上极具竞争力。
从目前嵌入式应用的发展趋势来看,嵌入式数据库的实现必须充分体现系统的可定制性,即系统选择的技术路线要面向具体的行业应用,因而研究源码开放的嵌入式数据库具有特殊意义。
2、Berkeley DB和sqlite
DBkeley DB是一款健壮的、高速的工业级开放源代码的嵌入式数据库管理系统。应用它,程序员只需要调用一些简单的API就可以完成对数据的访问和管理。
Berkeley DB的源代码有C和Java两种,函数库本身只有300KB左右,但却能够用来管理多达256TB的数据。Berkeley DB作为一种嵌入式数据库系统在许多方面有着独特的优势。首先,由于其应用程序和数据库管理系统运行在相同的进程空间当中,进行数据操作时可以避免繁琐的进程间通信,因此耗费在通信上的开销自然也就降低到了极低程度。其次,Berkeley DB使用简单的函数调用接口来完成所有的数据库操作,而不是在数据库系统中经常用到的sql语言,避免了对结构化查询语言进行解析和处理所需的开销。
sqlite的源代码是C,其源代码完全开放。sqlite第一个Alpha版本诞生于2000年5月。今年5月,sqlite又迎来了一个新的里程一SOLite 3。
sqlite有以下特性:支持ACID事务;零配置一无需安装和管理配置;储存在单一磁盘文件中的一个完整的数据库;数据库文件可以在不同字节顺序的机器间自由共享;支持数据库大小至2TB;足够小,全部源码大致3万行c代码,250KB;比目前流行的大多数数据库对数据的操作要快;提供了对事务功能和并发处理的支持,应用Transaction既保证了数据的完整性,也会提高运行速度,因为多条语句一起提交给数据库的速度会比一条一条的提交方式更快;独立,没有额外依赖。
目前,对Berkeley DB的研究开发工作主要是美国的sleepycat公司在进行,在国内几乎没有关于这方面的研究;而sqlite在国内也是鲜有人问津。
2.1 Berkeley DB和SOLite的数据库操作
与常用的数据库管理系统(如MysqL和Oracle等)有所不同,在Berkeley DB中并没有数据库服务器的概念。应用程序不需要事先同数据库服务建立起网络连接,而是通过内嵌在程序中的Berkeley DB函数库来完成对数据的保存、查询、修改和删除等操作。所有与数据库相关的操作都由函数库负责统一完成,这样无论是系统中的多个进程,或者是相同进程中的多个线程,都可以在同一时间调用访问数据库的函数;而底层的数据加锁、事务日志和存储管理等都在Berkeley DB函数库中实现。它们对应用程序来讲是完全透明的。
Berkeley DB不是关系型的数据库,不能应用标准的sql语句对数据库操作,对它的操作要调用专用的API实现。这些API提供了查询、插入、删除等功能。比如com.sleepycat.db.Db类代表数据库对象。Db类的put( )方法完成的是插入功能;get( )方法完成的是读出数据的功能;com.sleepycat.db.Dbc是Berkeley DB的游标类,提供了遍历数据库记录的功能。
使用Berkeley DB提供的函数来进行数据库的访问和管理并不复杂。在大多数场合下,只需按照统一的接口标准进行调用就可以完成最基本的操作,Berkeley DBEnvironment为一组数据库同时提供参数设置。更为重要的是,如果要应用更高级的特性,必须要使用Environment功能,比如在要对保存的数据进行加密存储、利用其Transaction、数据加密、同步加锁控制、错误日志等功能的时候。
sqlite的sql语言很大程度上实现了ANSI sql92标准,特别是支持视图、触发器、事务,支持嵌套sql。它通过sql编译器(sql Complier)来实现sql语言对数据库进行操作,支持大部分的sql命令,如attach database、begin transaction、comment、commit transaction、copy、create index、create table、create trigger、create view、delete、detach database、drop index、drop table、drop trigger、drop view、end transaction、explain、expression、insert、On conflict clause、pragma、replace、rollback transaction、select、update。
当然,也有一部分sql命令sqlite并不支持。比如:不支持:Exists,虽然支持in(in是Exists的一种情况);不支持多数据库,如create table dbl.tablel as select*from db2.table 1;:不支持存储过程;不支持Alter View/Trigger/Table:不支持Truncate,在sqlite中Delete不带Where字句时和Truancate的效果是一样的;不支持Floor和Ceiling函数,还有其它许多的函数;没有Auto Increment(自增)字段,但其实sqlite是支持Auto Increment的,即在将该字段设置为“INTEGER PRIMARY KEY”的时候;不支持If Exists等。
2.2 Berkeley DB和S0Lite与普通数据库的差别
Berkeley DB引入了一些新的基本概念,使得数据库应用程序访问和管理数据库变得相对简单起来。
(1)关键字和数据
关键字(key)和数据(data)是。Berkeley.DB用来进行数据库管理的基础,由这两者构成的key/data对,组成了数据库中的一个基本结构单元。整个数据库实际上就是由许多这样的结构单元构成的。通过使用这种方式,在通过API函数访问数据库时,只需提供关键字就能够访问到相应的数据。
关键字和数据在Berkeley DB中都是用一个名为DBT的简单结构来表示的。实际上两者都可以是任意长度的二进制数据。DBT的作用主要是保存相应的内存地址及其长度,其结构如下所示:
typedef struct {
void*data;
u_int32_t size;
u_int32_t ulen;
u_int32_f dlen;
u_int32_f doff;
u_int32_f flags;
}DBT;
在使用Berkeley DB进行数据管理时,缺省情况下是一个关键字对应于一个数据;但也可以将数据库配置成一个关键字对应于多个数据,而键值和数据必须是类com.sleepycat.db.Dbt的对象或其子类的对象。
(2)对象句柄
在Berkeley DB函数库定义的大多数函数都遵循同样的调用原则:首先创建某个结构,然后再调用该结构中的某些方法。从程序设计的角度来讲,这一点同面向对象的设计原则是非常类似的,即先创建某个对象的一个实例,然后再调用该实例的某些方法。正因为如此,.Berkeley DB引入了对象句柄的概念来表示实例化后的结构,并且将结构中的成员函数称为该句柄的方法。对象句柄的引入使得程序员能够凭借面向对象的思想,来完成对Berkeley DB数据库的访问和操作。
(3)错误处理
对于任何一个函数库来说,如何对错误进行统一的处理都是需要考虑的问题。Berkeley DB提供的所有函数都遵循同样的错误处理原则,即函数成功执行后返回零,否则返回非零值。
对于系统错误(如磁盘空间不足),返回的是一个标准的值;而对于非系统错误,返回的则是一个特定的错误编码。例如,如果在数据库中没有与某个特定关键字所对应的数据,那么在通过该关键字检索数据时就会出现错误。此时函数的返回值将是DB—NOTF0uND,表示在数据库中并没有所请求的关键字。所有标准的e rrn0值都大于零,而由Berkeley DB定义的特殊错误编码则都小于零。
Berkeley。DB提供了相应的函数来获得错误代号所对应的错误描述。一旦有错误发生,只需首先调用db_strerror( )函数来获得错误描述信息,然后再调用DB一>err()或DB->errx()就可以很轻松地输出格式化后的错误信息。
而sqlite最大的特点在于其数据类型为无数据类型(typelessness)。这意味着可以保存任何类型的数据到所想要保存的任何表的任何列中,无论这列声明的数据类型是什么。虽然在生成表结构的时候,要声明每个域的数据类型,但sqlite并不做任何检查。开发人员要靠自己的程序控制输入与读出数据的类型。这里有一个例外,就是当主键为整型值时,如果要插入一个非整型值时会产生异常。
诚然,sqlite允许忽略数据类型,但是,仍然建议在Create Table语句中指定数据类型,因为数据类型有利于增强程序的可读性。sqlite支持常见的数据类型,如VARCHAR、NVARCHAR、TEXT、INTEGER、FLOAT、BOOLEAN、CLOB、BLOB、TIMESTAMP、NUMERIC、VARYING、CHARACTER、NATl0NAI,VARYINGCHARACTER。
另外,虽然在插入或读出数据的时候是不区分类型的,但在比较的时候,不同数据类型是有区别的。比如:CREATE TABLE MyTable (a INTEGER,b TEXT);
INSERT INT0 MyTable VALIUES(0,0);
当执行查询
SELECT count(*)FROM MyTable WHERE a==’00’;时,会返回一条记录。因为字段a的类型是整型,而数字00与0是相等的。而执行查询
SELECT count(*)FROM MyTable WHERE b==‘00’:时,则不会返回记录。因为字段b是字符类型,字符“00”与“0”是不相等的。
2.3 Betkeley DB和sqlite数据存储方式比较
Berkeley DB对任何存入的数据都是按原样直接存储到数据文件中去,无论其是二进制数据还是A S C I I或Unicode等编码的文本。Berkeley DB提供了四种存储数据的模式:Btree、Hash、Queue和Recno。在打开数据库的时候,要指定一种存储模式。比如,上例中open( )方法中的参数Db.DB_BTREE就是指定以Btree模式打开数据库。
sqlite只提供了Btree存储数据的模式。对二进制数据,sqlite不能直接保存,但可以先将二进制的数据转换成ASCII编码,然后再保存。Base64.编码机制是最常见的把二进制数据转换成ASCII编码的手段。在sqlite的C语言代码encode.c中,提供了Base64编码的功能。
Btree模式是以排序的二叉树的方式存储的,Hash是以线性哈希表的方式存储。Queue用逻辑记录号作为键值,以定长的数据为记录值。Recno方式也以逻辑记录号作为键值,但可以保存定长或变长的记录值。这里提到的逻辑记录号有两种,即可变的和固定的。可变逻辑记录号会根据数据记录的增加与删除作相应的变化。Queue模式下,逻辑记录号只能是固定方式。Recno模式则可通过配置来选择是采用哪种类型的记录号作为键值。Btree模式也可以通过设置,将可变的逻辑记录号作为键值。
这几种存储模式各有优缺点,当键值不想用逻辑记录号时,Btree或Hash是必须的选择。Btree方式比较适合连续的顺序读取。比如,当键值是时间值,如果经常有从某一时间点开始连续读取后继的记录的操作,Btree是一种很好的选择。对随机的跳跃式读取,Hash模式则更为恰当。Queue和Recno都以记录号为键值,但前者适合先进先出的读取方式。Recno则通常是存取变长文本记录的理想存储模式。
2.4 Berkeley DB和S0Lite适用的系统
Berkeley DB为许多编程语言提供了实用的API接口,包括C、C++、Java、:Perl、Tcl、Python和PHP等。它适用平台UNIX/POSIX systems、win32以及嵌入式操作系统WinCE、VxWorks等。
通过Wrapper,sqlite实现了与其它语言的连接。所谓Wrapper即对sqlite提供的接口进行封装,使其它语言可以访问,使用sqlite。sqlite本身提供C和Tcl的接口,世界各地的程序员还提供了各种语言的sqlite的接口封装,如Python、C++、Java、.Net等几乎所有流行的语言基本都有。sqlite提供一个抽象的操作系统接口层,来保证其在POSIX 与 Win32系统之间的兼容性。
2.5 其它方面
Berkeley DB没有数据库服务器的概念,使用简单的函数调用接口来完成所有的数据库操作,不使用sql语言;接口简明实用,避免了对结构化查询语言进行解析和处理所需的开销,提高了执行速度;速度极快,可靠性高,但学习起来有一定难度。sqlite则简单易用,速度也很快,但功能却较Berkeley略有逊色,比如加密功能、二进制数据的处理等。
Berkeley DB虽然是开源的产品,但对某些条件下的商业性应用,却不是免费的,而且价格颇为昂贵。这些商业条件包括排除了开源的情况,不发放分布版本的情况等。sqlite是源代码完全的开放,可以免费用于任何用途,包括商业目的。