SQLite 知识摘要 --- 存储

最近在做HttpURLCache 的缓存管理,每个response 对应一个网页Cache索引,由数据库管理网页索引,response 使用Protobuf 替换NSCoding 做序列化。发现在SQLite 方面的知识实在欠缺,虽然之前接触过,但是并没有深入的学习和研究,因此最近又翻阅了一些文档,温故而知新嘛,我也是初学者,欢迎大家踊跃吐槽。今天主要聊一聊SQLite的数据存储。

SQLite的储存

SQLite 的一个DB对应一个物理文件,文件中的数据被分成若干个大小相等的Page进行存储,这些Page之间的关系由树形结构来组织管理。

为了方便管理,每个Page的大小是相同的,默认为1024,也可在创建时设置,大小范围是512~32768。那么整个数据库文件在物理上我们其实可以理解为一个Page数组,下标从1开始上限是2^31-1(当然实际使用中还受到操作系统对文件大小的限制)。Page主要分为三类:

  • Btree页面:Btree页又包括叶子页内部页,叶子页是树形结构的叶子节点,实际存储数据的页面,内部页是树形结构的中间节点,并不存储实际的数据,存储了下一层Page的索引列表;
  • 溢出页面:当某一条记录数据特别大,单个Page无法容纳时,SQLite会选择在这个叶子页面存放一部分数据,然后新建一个Page来存放剩余的数据,这个新建的页面就是溢出页面;
  • 空闲页面:当某个Page上的数据被删除时,物理空间可能并没有删除,SQLite会将其添加到空闲页面列表中(该链表头在db文件头部),以便后续插入数据时可再次使用。

Btree页面的内部以单元为单位来组织数据,对于叶子页面而言,一个单元存储的就是一条数据记录,而对于内部页面而言,一个单元对应一个子页面,里面存储着该页面的页号以及该页面关键字的最大值。当单个页面无法存放一条记录的时候,则会将部分内容存放在本单元,然后将剩下的部分存放在新开辟的溢出页面,在本单元结尾会记录下对应溢出页的索引。一个溢出页不够还可以再开辟新的,依次类推。存储大数据的时候,最后都采用页链接链表来存储,注意了,这种方式是很低效率的。

Screen Shot 2017-03-28 at 10.27.53 PM.png

存储空间的优化点

  • 释放碎片空间。
  • 合理设置page大小
  • 调整缓存大小,提高事务效率

释放碎片空间

  • 在默认情况下,当SQLite执行一个数据删除的事务的时候,并不会将DB文件中对应的内容删除,然后调整位置释放那些Page的空间,而只是简单地将这些页置为空闲页,然后添加到空闲页列表,以此来换取极高的效率。这是一种牺牲空间换取效率的方法,尽管空闲页到后面也会被再次使用,但是会带来不少碎片,导致文件所占用的大小比时机储存数据大小要大。

    SQLite中的VACUUM命令可以达到释放空白空间的需求。它的执行原理是先将原数据库文件复制到临时数据库,再从临时数据库将数据重新载入原数据库,这中间会将那些空白页去除,使表数据彼此相邻排列,并整理数据库文件。这一番操作,对查询效率也会有所帮助。但是需要注意的是,VACUUM本身操作的效率非常低。

PRAGMA auto_vacuum = (0 | NONE) | (1 | FULL) | ( 2| INCREMENTAL);
(0 | NONE)表示关闭自动清理;
(1 | FULL)表示完全开启auto-vacuum,每次执行事务都会进行自动收缩操作,但是会影响事务处理性能;
(2 | INCREMENTAL)情况下,不会自动触发收缩操作,需要额外的incremental_vacuum语句来触发。
auto-vacuum的效果没有VACUUM命令彻底,而且这个模式只有在还没创建表的数据库或者执行了VACUUM命令的情况下设置才能生效。

这边需要找到一个时间和空间的平衡点,对于不在乎存储开销的情况(比如PC程序,硬盘根本不值钱...),完全不需要用这个清理功能。如果有清理空间的需求,建议采用VACUUM命令或者开INCREMENTAL,在需要清理的时机清理,不推荐开FULL模式。

合理设置page大小

前面讲到,表的记录数据最终是存储在Btree叶子页,每个页面容纳一条或者多条记录,庞大的记录会开辟溢出页来存储。

这里有两点需要关注:

  • 大数据字段,新增了溢出页,但是溢出页的使用率并不是非常高(同一个溢出页不会存储两条记录的溢出内容)
  • 查询使用概率很高的字段被放到了溢出页。

第一种情况造成了空间上的一点浪费,而第二种情况,则会影响常用字段的查询效率,因为需要额外加载溢出页才能拿到字段值,且溢出页并不一定就在下一个连续的页,这对查询的影响可想而知。

Screen Shot 2017-03-28 at 10.35.45 PM.png

因此pageSize 的大小设置至关重要,从SQLite 官网可知,关于大文件究竟是直接存储在db,还是将文件持久化到本地,然后将数据索引添加到数据库呢?

Screen Shot 2017-03-28 at 9.35.45 PM.png

官方给出的答案是:

  • pageSize 为8192 或者16384 ,对于大文件的存储能达到最好的性能
  • 大于100 kb 的文件建议用外部存储,小于100kb 的直接写db就好。

第三方库YYCache 就选择当单条数据大于20k 的时候,会直接写外部文件。小于则存db。
那么,如何选择适合自己的pageSize 呢?

假设有一个字段,存储数据平均大小为M,我们只要确保尽量不要出现溢出就好。也就是pageSize尽量比M 大一点,避免开辟溢出页。 当存在很大的数据,那么直接用外部存储吧,数据库用来存外部数据索引就好。对于第二个问题,很简单,把经常查询需要用到的字段安排在前面,避免存到溢出页。

pageSize默认值是1024,理论上的设置范围是512~65536(必须是2的幂),之所以称之为理论值,是因为它还受到SQLITE_MAX_DEFAULT_PAGE_SIZE的限制。需要注意的是,page_size的设置需要在数据库创建之前才能生效!

调整缓存大小,提高事务效率

当SQLite打开一个数据库连接执行事务的时候,会在内存中缓存部分页面数据以提高执行效率。如果Cache中缓存的页面多,命中概率就会增大,对事务的执行效率自然也会有很大的帮助。

对于经常会执行多行查询或者增删改操作的情况,我们可以适当增加缓存页数来提高事务执行效率。当然也要考虑到用户普遍的硬件条件,缓存页数乘以每页大小就是缓存总共占用的内存(page_size*cache_size)。设置缓存页数可以通过如下的命令实现

PRAGMA cache_size = pages | -kibibytes;

默认是缓存2000页,cache_size的值可以正也可以负,正数代表页数,负数代表大小(单位是KB)。例如1000代表缓存1000页,那么所占用的大小为1000乘以page_size,-1000代表缓存可占用大小为1000 * 1024 bytes,也可以倒过来计算出可缓存的页数。cache_size命令只对当前连接生效,下次连接需要重新设置。如果想设置所有连接都生效,可以使用default_cache_size命令。

建议:如果硬件条件允许SQLite使用更多的内存,可以适当将缓存页数调大,来换取大量或多行事务的效率。最好在开发前对用户群体的情况做一个大概的了解来估算这个值。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容