SQLite创建的数据库有一种模式IN-MEMORY,但是它并不表示SQLite就成了一个内存数据库。IN-MEMORY模式可以简单地理解为,本来创建的数据库文件是基于磁盘的,现在整个文件使用内存空间来代替磁盘空间,其它操作保持一致。也就是数据库的设计没有根本改变。

提到内存,许多人就会简单地理解为,内存比磁盘速度快很多,所以内存模式比磁盘模式的数据库速度也快很多,甚至有人望文生意就把它变成等同于内存数据库。

它并不是为内存数据库应用而设计的,本质还是文件数据库。它的数据库存储文件有将近一半的空间是空置的,这是它的B树存储决定的,请参看上一篇SQLite存储格式。内存模式只是将数据库存储文件放入内存空间,但并不考虑最有效管理你的内存空间,其它临时文件也要使用内存,事务回滚日志一样要生成,只是使用了内存空间。它的作用应该偏向于临时性的用途。

我们先来看一下下面的测试结果,分别往memory和disk模式的sqlite数据库进行1w, 10w以及100w条数据的插入,采用一次性提交事务。另外使用commit_hook捕捉事务提交次数。

(注:测试场景为在新建的数据库做插入操作,所以回滚日志是很小的,并且无需要在插入过程中查找而从数据库加载页面,因此测试也并不全面)

内存模式

磁盘模式

在事务提交前的耗时 (事务提交后的总耗时):

  1w 10w 100w
内存模式 0.04s 0.35s 3.60s
磁盘模式 0.06s (0.27s) 0.47s (0.72s) 3.95s (4.62s)

可以看到当操作的数据越少时,内存模式的性能提高得越明显,事务IO的同步时间消耗越显注。

上图还有一组数据比较,就是在单次事务提交中,如果要为每条插入语句准备的话

  1w 10w 100w
内存模式 0.19s 1.92s 19.46s
磁盘模式 0.21s (0.35s) 2.06s (2.26s) 19.88s (20.41s)

我们从SQLite的设计来分析,一次插入操作,SQLite到底做了些什么。首先SQLite的数据库操作是以页面大小为单位的。在单条记录插入的事务中,回滚日志文件被创建。在B树中查找目标页面,要读入一些页面,然后将目标页面以及要修改的父级页面写出到回滚日志。操作目标页面的内存映像,插入一条记录,并在页面内重排序(索引排序,无索引做自增计数排序,参看上一篇《SQLite数据库存储格式》)。最后事务提交将修改的页面写出到数据库文件,成功后再删除日志文件。在这过程中显式进行了2次写磁盘(1次写日志文件,1次同步写数据库),还有2次隐式写磁盘(日志文件的创建和删除),这是在操作目录节点。以及为查找加载的页面读操作。更加详细可以参看官方文档的讨论章节《Atomic Commit In SQLite》

如果假设插入100条记录,每条记录都要提交一次事务就很不划算,所以需要批量操作来减少事务提交次数。假设页面大小为4KB,记录长度在20字节内,每页可放多于200条记录,一次事务提交插入100条记录,假设这100条记录正好能放入到同一页面又没有产生页面分裂,这样就可以在单条记录插入事务的IO开销耗损代价中完成100条记录插入。

当我们的事务中,插入的数据越多,事务的IO代价就会摊得越薄,所以在插入100w条记录的测试结果中,内存模式和磁盘模式的耗时都十分接近。实际应用场合中也很少会需要一次插入100w的数据。有这样的需要就不要考虑SQLite。

(补充说明一下,事务IO指代同步数据库的IO,以及回滚日志的IO,只在本文使用)

除了IO外,还有没有其它地方也影响着性能。那就是语句执行。其实反观一切,都是在对循环进行优化。

for (i = ; i < repeat; ++i)
{
exec("BEGIN TRANS");
exec("INSERT INTO ...");
exec("END TRANS");
}

批量插入:

exec("BEGIN TRANS");
for (i = ; i < repeat; ++i)
{
exec("INSERT INTO ...");
}
exec("END TRANS");

当我们展开插入语句的执行

exec("BEGIN TRANS");
for (i = ; i < repeat; ++i)
{
// unwind exec("INSERT INTO ...");
prepare("INSERT INTO ...");
bind();
step();
finalize();
}
exec("END TRANS");

又发现循环内可以移出部分语句

exec("BEGIN TRANS");
// unwind exec("INSERT INTO ...");
prepare("INSERT INTO ...");
for (i = ; i < repeat; ++i)
{
bind();
step();
}
finalize();
exec("END TRANS");

这样就得到了批量插入的最终优化模式。

所以对sql语句的分析,编译和释放是直接在损耗CPU,而同步IO则是在饥饿CPU。

请看下图

分别为内存模式1w和10w两组测试,每组测试包括4项测试

1.只编译一条语句,只提交一次事务

2.每次插入编译语句,只提交一次事务

3.只编译一条语句,但使用自动事务。

4.每次插入编译语句,并使用自动事务。

可以看到测试项目4基本上就是测试项目2和测试项目3的结果的和。

测试项目1就是批量插入优化的最终结果。

下面是探讨内存模式的使用:

经过上面的分析,内存模式在批量插入对比磁盘模式提升不是太显注的,请现在开始关注未批量插入的结果。

下面给出的是磁盘模式0.1w和0.2w两组测试,每组测试包括4项测试

可以看到在非批量插入情况,sqlite表现很差要100秒来完成1000次单条插入事务,但绝非sqlite很吃力,因为cpu在空载,IO阻塞了程序。

再来看内存模式20w测试

可以看到sqlite在内存模式,即使在20w次的单条插入事务,其耗时也不太逊于磁盘模式100w插入一次事务。

  0.1w 0.2w 20w
内存模式(非批量插入)     15.87s
磁盘模式(非批量插入) 97.4s 198.28s  
  编译1次插入语句 每次插入编译1次语句
内存模式(20w,20w次事务) 11.10s 15.87s
磁盘模式(100w,1次事务) 4.62s 20.41s

不能给出内存模式100w次事务的测试结果是因为程序运行出问题。

在100w的插入一次性事务测试结果,内存模式和磁盘模式相差不到1秒,这1秒就是最后大量数据库同步到数据库的IO时间。

再回到上面两图两表的测试结果,磁盘模式在执行多事务显得偏瘫,每秒不多于10个单条插入的事务。而内存模式下执行事务的能力仍然坚挺,每秒1w次单条插入的事务也不在话下。

在实际应用中,数据随机实时,你又不想做批量插入控制,就可以考虑用内存模式将现有的数据马上用事务提交,不管事务提交的数据是多是少。你只要定制计划,将内存模式的数据库同步到你的外部数据库。因为每个内存模式的数据库是独立的,你同步一个内存模式的数据库到外部的期间,就可以同时使用另一个内存模式的数据库缓冲数据。

(上面删除段落是根据MinGW系统的测试结果。在真机环境测试了win 7 32bit和win 7 64bit,以及在它们之上使用mingw系统,测试结果是sqlite处理1000个单条插入事务总耗时在100秒级别。而在vm环境,vm虚拟磁盘上测试了xp,linux和macosx,测试结果是sqlite处理1000个单条插入事务总耗时在10秒级别内。值得注意的是,vm虚拟磁盘不是直接操作磁盘,所以我还要另找磁盘,挂接真实的磁盘对虚拟机环境进行测试。)

更多磁盘模式的测试结果在下一篇《意想不到,但又情理之中的测试结果》。

在经过慎重考虑后,在linux和mac环境下进行了测试,验证了一句“数据库都构建在痛苦的操作系统之上”。上面的测试环境是MinGw,痛苦的不是windows而是在windows之上加上的一层MinGw系统,磁盘操作十分痛苦。根据在linux和mac环境的测试结果,内存模式和磁盘模式在单条插入自动事务的性能更加接近,相差只有10倍左右,由于不用在MinGW这样的适配系统痛苦地操作磁盘,所以在其它批量插入事务的测试项目中,两种模式的测试结果更加趋于接近。

至于你想用sqlite的内存模式作持久用途或者去媲美内存数据库,可能不是正确的选择。sqlite是一个体积轻巧,可以帮你管理关系型数据的嵌入式数据库。它适应嵌入式的空间小,耗电低和占用内存有限的特殊环境。它的高效是不因为它的简单,而在基本的数据库查询功能上有打折扣。它在设计上有针对性的取舍,使它更适合某些应用场合,也必然在舍的部分蹩足。

本篇至此结束,谢谢观看。

后续会有":memory:","file:whereIsDb?mode=memory"以及"disk.db"这三种模式的对比。

测试代码在https://github.com/bbqz007/xw/test.sqlite.in-memory.zip。(不支持VC,需要自行下载编译sqlite。支持VC编译的测试代码未上传。)

mingw测试插入1000条数据使用自动事务,即一共提交1000次事务:

运行在      总耗时

xp (vm11)            9s

win 7 64bit        200s

win 7 32bit        100s

最后补上Linux (vm11)和MacOSX (vm11)的测试结果:

Linux 2.6.-.el6.x86_64
cpu MHz : 3591.699
cpu MHz : 3591.699
----- in memory ----
repeat insert times, in trans, with stmt prepared
.04s
.04s
commit:
repeat insert times, in trans, with each stmt prepared
.06s
.06s
commit:
repeat insert times, in auto trans(s), with stmt prepared
.02s
.02s
commit:
repeat insert times, in auto trans(s), with each stmt prepared
.06s
.06s
commit:
---- in memory ----
repeat insert times, in trans, with stmt prepared
.11s
.11s
commit:
repeat insert times, in trans, with each stmt prepared
.40s
.40s
commit:
repeat insert times, in auto trans(s), with stmt prepared
.28s
.28s
commit:
repeat insert times, in auto trans(s), with each stmt prepared
.76s
.76s
commit:
---- in memory ----
repeat insert times, in trans, with stmt prepared
.23s
.23s
commit:
repeat insert times, in trans, with each stmt prepared
.87s
.87s
commit:
repeat insert times, in auto trans(s), with stmt prepared
.35s
.35s
commit:
repeat insert times, in auto trans(s), with each stmt prepared
.10s
.10s
commit:
--- in memory ----
repeat insert times, in trans, with stmt prepared
.23s
.23s
commit:
repeat insert times, in trans, with each stmt prepared
.39s
.39s
commit: ------ in disk ----
rm: 无法删除"test.db": 没有那个文件或目录
repeat insert times, in trans, with stmt prepared
.00s
.00s
commit:
repeat insert times, in trans, with each stmt prepared
.00s
.00s
commit:
repeat insert times, in auto trans(s), with stmt prepared
.80s
.80s
commit:
repeat insert times, in auto trans(s), with each stmt prepared
.87s
.87s
commit:
------ in disk ----
repeat insert times, in trans, with stmt prepared
.00s
.00s
commit:
repeat insert times, in trans, with each stmt prepared
.01s
.02s
commit:
repeat insert times, in auto trans(s), with stmt prepared
.60s
.60s
commit:
repeat insert times, in auto trans(s), with each stmt prepared
.27s
.27s
commit:
----- in disk ----
repeat insert times, in trans, with stmt prepared
.01s
.02s
commit:
repeat insert times, in trans, with each stmt prepared
.04s
.04s
commit:
---- in disk ----
repeat insert times, in trans, with stmt prepared
.11s
.11s
commit:
repeat insert times, in trans, with each stmt prepared
.45s
.45s
commit:
--- in disk ----
repeat insert times, in trans, with stmt prepared
.27s
.34s
commit:
repeat insert times, in trans, with each stmt prepared
.51s
.57s
commit:

Linux测试结果

MacOSX的测试结果:

SQLite性能 - 它不是内存数据库,不要对IN-MEMORY望文生意。的更多相关文章

  1. SQLite剖析之临时文件、内存数据库

    一.7种临时文件    SQLite中,一个数据库由单个磁盘文件构成,简化了SQLite的使用,因为移动或备份数据库只要拷贝单个文件即可.这也使得SQLite适合用作应用程序文件格式.但是,当在单个文 ...

  2. Android 中 SQLite 性能优化

    数据库是应用开发中常用的技术,在Android应用中也不例外.Android默认使用了SQLite数据库,在应用程序开发中,我们使用最多的无外乎增删改查.纵使操作简单,也有可能出现查找数据缓慢,插入数 ...

  3. sqlite性能简单測试

    主要測试sqlite在大数据量下的插入及查询性能: 測试环境:Centos6.4  1G内存  单核 数据量 大小 索引字段检索(耗时) 非索引字段检索(耗时) 总插入时间 10W 19M 0.001 ...

  4. sqlite性能优化

    1.数据库性能上 1.1 批量事务插入,提升数据插入的性能 由于sqlite默认每次插入都是事务,需要对文件进行读写,那么减少事务次数就能简书磁盘读写次数从而获得性能提升. 1.2 单条sql优于多条 ...

  5. Android SQLite性能分析

    作为Android预置的数据库模块,对SQLite的深入理解是很有必要的,能够从中找到一些优化的方向. 这里对SQLite的性能和内存进行了一些測试分析.对照了不同操作的运行性能和内存占用的情况,粗略 ...

  6. SQLite性能 - 意想不到,但又情理之中的测试结果。

    win7(64) sata2 希捷 MINGW32_NT-(/) cat: /proc/cpuinfo: No such file or directory ------ in disk ---- r ...

  7. Android开源项目分类汇总-转载

    太长了,还是转载吧...今天在看博客的时候,无意中发现了@Trinea在GitHub上的一个项目Android开源项目分类汇总,由于类容太多了,我没有一个个完整地看完,但是里面介绍的开源项目都非常有参 ...

  8. SQLite内存数据库

    [转]SQLite内存数据库 http://www.cnblogs.com/liuyong/archive/2010/09/14/1826152.html SQLite 介绍 一. SQLite 是实 ...

  9. 【原创】System.Data.SQLite内存数据库模式

    对于很多嵌入式数据库来说都有对于的内存数据库模式,SQLite也不例外.内存数据库常常用于极速.实时的场景,一个很好的应用的场景是富客户端的缓存数据,一般富客户端的缓存常常需要分为落地和非落地两种,而 ...

随机推荐

  1. Vue项目多域名跨域

    在Vue项目中请求后台数据时,遇到的多域名跨域问题. 直接上代码: assetsSubDirectory: "static", assetsPublicPath: "/& ...

  2. 8.Nginx基本概述

    io网络模型介绍 1.介绍Nginx Nginx是一个高性能的HTTP和反向代理web服务器 2.常见的Web服务器 httpd Nginx Tengine OpenResty 3.介绍Nginx应用 ...

  3. 云计算 docker 容器使用命令

    docker 使用命令: docker version 查看docker版本号 vi /etc/docker/daemon.json { "registry-mirrors": [ ...

  4. vue引入css文件报错Unrecognised input

    一个vue项目中用到了swiper插件,引入swiper.css时报错 显示引入的css文件Unrecognised input ,在文件的line4,column12 . 其实是引入位置不对,样式文 ...

  5. Map文件从IDA到OD

    目录 什么是map文件 IDA与OD导出使用map文件 注意事项 使用OD载入导出的map文件 什么是map文件 什么是 MAP 文件? 简单地讲, MAP 文件是程序的全局符号.源文件和代码行号信息 ...

  6. centos 7 防火墙firewall 与iptables 的一些常用命令

    CentOS 7的防火墙配置跟以前版本有很大区别,CentOS7这个版本的防火墙默认使用的是firewall,与之前的版本使用iptables不一样. firewall常用命令 service fir ...

  7. SpringBoot集成JWT实现权限认证

    目录 一.JWT认证流程 二.SpringBoot整合JWT 三.测试 上一篇文章<一分钟带你了解JWT认证!>介绍了JWT的组成和认证原理,本文将介绍下SpringBoot整合JWT实现 ...

  8. Linux中vi命令的详细总结

    vi命令的使用 首先可以新建一个文件夹 touch 1.txt  之后通过vi命令进入其中  vi 1.txt 进入其中开始编辑,按下a键可以进行输入. 随便输入一些东西(暂时不支持中文) 非输入模式 ...

  9. CSPS模拟 67

    炸分炸的厉害.(当然这跟b哥定律无关 话说好久没人嘲笑我菜了,快飘的不知道到哪了. 谁能讽我两句我不要面子的. 另外在博客上写些没用的东西好浪费精力啊我又不想当网红 主要是考试的时候心态不稳. 以为T ...

  10. NOIP模拟 35

    int乘爆见祖宗 难得地T3暴力打满 T1T2思路也都正确 然而T2没看出一个int乘爆直接滚粗.. 这谁抗的住啊... 跟天皇说我差点就rank1了 换来的只有一张奸笑的大脸和一堆垃圾的嘲讽 真巨啊 ...