浅谈SQLite——实现与应用

1、前言

有一些日子没有仔细关注SQLite了,今天打开其主页,发现其最新的版本已经是3.6.22了,更让我惊喜的是它的用户越来越多,而且邮件列表的关注者也越来越多,突然觉得自己已经太old了。惊喜的同时,不得不聊上几句了。

首先,来看看都有哪些人在使用SQLite,主页上列举一长串NB的用户,其中不乏像Adobe,Apple,Firefox,甚至连google,Microsoft,SUN这样的用户。

Firefox:这是我的机器上V3.5.7安装目录下的文件:

 sqlite_10_1

可以发现用的SQLite 3.6.16.1。

据说,Google在它的Desktop for Mac,Google Gears,以及Android,甚至Chrome中都用到SQLite,而且,Google的工程师对SQLite的全文检索功能作了很大的贡献(contribution)。还有Apple,Micorsoft,SUN等等,这里就不列举了。详细见http://www.sqlite.org/famous.html。有这些公司的参与,对SQLite的发展应该有很大的帮助,尤其是像Google这样的用户。

 

2、实现与应用

下面从实现及应用的角度来谈谈SQLite,先看看SQLite的特点(功能)吧。

特点

简单(simple):SQLite是一个非常轻量级自包含(lightweight and self-contained)的DBMS:一个头文件,一个动态库文件,你就拥有了关系数据库的所有功能了。简单,是SQLite最明显的哲学。它提供的API少而简单。只需要一个DLL文件,你的程序马上就拥有了一个功能强大的数据库引擎,这是一件很美妙的事。

小巧(small):我用VS 2005在Windows下编译的3.6.11,Release版为368K,用时不到20秒——而编译MySQL时,要花上几分钟。而当我插入10000条int数据时,内存开销660K,磁盘开销92K。

事务(transaction):事务是现代商业数据处理系统最基本的要求,而Access,不论是在可执行文件大小(看了一下Access2003的可执行文件大小为6.32M,两者不是一个量级),还是事务特性,都是不能和SQLite 相比的。

并发性(Concurrency):由于SQLite通过OS的文件锁来实现库级锁,粒度很大,但是,它通过一些复杂特殊的处理(具体可以参见分析系列),尽量的提升了读写的并发度。如果你还有担心,你可以看看这篇文章:http://www.dbanotes.net/database/sqlite_cms.html

SQL92:SQLite支持绝大部分的标准SQL语句,你只需要几百K的空间,就可以换来需要上百兆的通用DBMS几乎所有操作了。

方便(Convenience):如果你的程序要使用SQLite,只需要将拷贝你的程序目录即可。

开源(Opensource):这是它最强大的地方。开源,意味着你可以品读它的源码,你可以随时修改它,加入你自己的特性,而这一切完全免费的。开源,是一种精神。

 

实现部分

好了,现在从实现的角度来谈谈个人体会,这也是我比较关注的。

SQLite是一款优秀的嵌入式数据库管理系统,这里有两层含义:一是它经常作为动态库嵌入到应用程序;另外一方面它通常用于嵌入式设备或其它要求较低的桌面应用。如果把它作为内存数据库,个人觉得不是很适合。毕竟,它的写并发性不是很好,此时, TimesTen也许会更好,Berkey DB也许是一个不错的选择。SQLite这样的嵌入式数据库与主存数据库的应用场景、实现以及对资源的需求都是不一样的。

(1)事务处理

事务的核心问题有两个:并发控制和恢复。解决了并发控制和恢复问题的系统,就能允许它的用户假设程序是原子的(atomically)执行的——好像没有其它的程序同时执行;而且是可靠的(reliably)——不会产生失败。原子性和可靠性的抽象,则称为事务(transaction)。其实,事务并不是DBMS的专利,任何分布式系统,都面对并发和恢复问题,而解决的方法就是事务,只不过,我们更常听到DBMS中的事务。

并发控制保证事务的原子执行,它使得交错执行的事务看起来是一个接一个的顺序执行的,完全没有交错执行。如果交错执行的结果与顺序执行的结果一致,则称为串行化(serializable)。

恢复使得数据库仅仅包含那些正常完成的事务的结果。如果事务在执行的过程中发生错误,不能继续进行,恢复算法必须清除部分完成事务产生的影响。

  • 并发控制

SQLite只支持库级锁,库级锁意味着什么?——意味着同时只能允许一个写操作,也就是说,即事务T1在A表插入一条数据,事务T2在B表中插入一条数据,这两个操作不能同时进行,即使你的机器有100个CPU,也无法同时进行,而只能顺序进行。表级都不能并行,更别说元组级了——这就是库级锁。但是,SQLite尽量延迟申请X锁,直到数据块真正写盘时才申请X锁,这是非常巧妙而有效的。

  • 恢复

SQLite的恢复技术是影子分页技术(shadow paging)技术的典型代表。

DBMS的常用恢复技术有影子分页技术与基于日志的技术,前者在早其数据库管理系统中用到,比如System R,现代DBMS中已经很难见到它的身影了。

影子分页技术与基于日志技术相比,优点是实现简单,消除了写日志记录的开销,恢复的速度也快(不需要redo和undo)。影子分页的缺点就是事务提交时要输出多个块,这使得提交的开销很大,而且以块为单位,很难应用到允许多个事务并发执行的情况——这是它致命的缺点。

(2)查询处理

SQLite的查询处理本质上就是一个SQL编译器和一个虚拟机。而实现这些功能只用了十多个文件,整个实现实现简单而有效,但是也存在一些问题。首先,SQLite字典数据很简单,实际上它的字典就一个表sqlite_mater,所有的信息都是通过对sqlite_master中SQL语句进行解析获取的,而解析一个SQL语句,都需要进行词法分析、语法分析、甚至虚拟机代码的生成。而这一过程是很需要时间的,而且,查询计划也没有重用。其次,查询优化还比较简单,特别是连接操作,只通过循环来做(MySQL也一样)。但是,仅仅数万代码,我们不能对它要求太苛求。

(3)存储模型

SQLite的文件物理上被划分成相同大小的块;逻辑上划分成一些B-Tree——每个表对应一个B-Tree。而没有像Oracle,或者InnoDB对数据块进行复杂的逻辑组织,这种按需分配数据块的做法必然影响磁盘的读写性能。不过,归根到底,还是源于它的应用场景。

(4)缓冲区管理

Buffer的管理对于DBMS,无疑是非常重要的,SQLite在其它方面做得比较简单,但是在缓冲区管理这一块,它还是做足了功夫。SQLite采用DBMS常用的LRU算法。更值得一提的是,在较新的版本中,SQLite采用和虚拟文件系统的类似的方式,实现了让默认缓冲区管理子系统可以很容易的切换成其它的缓冲区管理算法,这是非常灵活的。

(5)I/O

SQLite采用简单的阻塞I/O,较新的版本将异步I/O作为可选的扩展,但是,由于SQLite没有日志,所以,事务中ACID中的D,就无法保证,所以,如果你的数据很关键,请不要用SQLite的异步I/O。另一方面,实际上,很多嵌入式操作系统,比如Windows CE并不支持异步I/O(不过,这可以通过多线程加以解决)。

但是,这些“缺点”并不是SQLite的缺点,相对于通过DBMS,恰恰是它的优点,这样的实现简单,而且对资源的需求较低。对嵌入式设备、或者那些要求较低的应用,已经足够,足够就好。

 

应用场景

嵌入式设备:这应该是SQLite应用的主要场景,很多公司都在他们的嵌入式应用程序中使用SQLite,其中不乏google的Android。

桌面应用:如果你已经厌恶了fopen,fread,fwrite这些函数,SQLite是你不错的选择,它接口简单,而且支持事务。前些天无意中下了一个cookie清除软件——CookieCrumbler,打开一看,发现里面竟然有一个sqlite3.dll,着实让我震惊了一把。

Websites:如果你的站点的访问量、数据量小的个人站点,SQLite可以代替开销较大的MySQL和繁杂的Access。

 

后记

SQLite正在不断发展,开源的力量是巨大的,相信它的应用会越来越广泛。Wish~

posted @ 2010-01-27 19:32 MrDB 阅读(4187) 评论(20) 编辑 收藏

 回复 引用 查看   
#1楼 2010-01-27 20:27 mapserver      
突然觉得自己已经太old了
-------------------
呵呵,old这个词在这里是亮点。

 回复 引用 查看   
#2楼[楼主] 2010-01-27 20:30 arrowcat      
@mapserver
呵呵,只是觉得SQLite的发展挺快的。

 回复 引用 查看   
#3楼 2010-01-27 20:48 Share赖      
我也很喜欢用sqlite,用到几个小项目中了,其中保存时间时,有点小麻烦。
 回复 引用 查看   
#4楼[楼主] 2010-01-27 20:54 arrowcat      
@Share赖
SQLite似乎没有显式的时间类型吧。
不过可以用TXT,或者INT来代替。。。

 回复 引用 查看   
#5楼 2010-01-27 21:19 ·风信子·      
嗯,sqlite不错。超迷你,当属开源项目中的上上品 :-)
 回复 引用 查看   
#6楼[楼主] 2010-01-27 21:38 arrowcat      
@·风信子·
呵呵,确实。
SQLite的影响力真的越来越大。。。

 回复 引用 查看   
#7楼 2010-01-27 22:24 49Degree      
SQLite加密数据有点麻烦
 回复 引用 查看   
#8楼[楼主] 2010-01-27 22:32 arrowcat      
@49Degree
对你的应用场景不是很了解。

 回复 引用 查看   
#9楼 2010-01-27 22:37 小多      
正在学习使用SQLite~~~
 回复 引用 查看   
#10楼[楼主] 2010-01-27 22:58 arrowcat      
@小多
-_-

 回复 引用 查看   
#11楼 2010-01-28 08:05 Share赖      
@arrowcat
sqlite有Datetiem和Date这两个类型的,用其它类型代替在查询时很麻烦,只是在插入数据要用datetime(now(),'localtion')转换一下,才能插入正确时间,不然,时间不准确的,好像是格林时间。

 回复 引用 查看   
#12楼 2010-01-28 08:58 Kevin Zou      
嗯,好像很不錯
 回复 引用 查看   
#13楼 2010-01-28 09:55 Jake Lin      
支持一下。
 回复 引用 查看   
#14楼 2010-01-28 13:48 49Degree      
@arrowcat
WinForm中保存一些用户相关的数据信息
考虑到安全方面,想把数据加密后保存起来
免得别人拿到文件就可以取到数据

 回复 引用 查看   
#15楼[楼主] 2010-01-28 19:15 arrowcat      
@Share赖
那建表的时候,应该指定什么类型呢?

 回复 引用 查看   
#16楼[楼主] 2010-01-28 19:16 arrowcat      
@Jake Lin
thanks

 回复 引用 查看   
#17楼 2010-01-29 07:44 Share赖      
@arrowcat
时间就是DateTime类型,日期就是Date

 回复 引用 查看   
#18楼[楼主] 2010-01-30 10:35 arrowcat      
@Share赖
嗯,SQL语句是支持DateTime这样的类型。
不过内部还是转化成字符串来做的,它也没有提供
sqlite3_column_datetime这样的接口。
关于时间问题,SQLite默认是GMT时间,它与本地时间计算的时区不同,所以,才会有这样的问题。所以,如果你要用本地时间,才会有那样的转换。不过,这并不是SQLite本身的问题。thanks.

 回复 引用 查看   
#19楼 2010-01-31 16:06 chinese_submarine      
这里提到数据库的锁,我想问一下对于数据库上锁,需要Coder自己去控制吗?还有就是Sqlite支持多个数据库连接吗?
 回复 引用 查看   
#20楼[楼主] 2010-01-31 19:41 arrowcat      
@chinese_submarine
这时提到的锁是SQLite自动处理,应用程序不需要关心。
SQLite支持多个连接,当然。