SQLite性能 - 它不是内存数据库,不要对IN-MEMORY望文生意。
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望文生意。的更多相关文章
- SQLite剖析之临时文件、内存数据库
一.7种临时文件 SQLite中,一个数据库由单个磁盘文件构成,简化了SQLite的使用,因为移动或备份数据库只要拷贝单个文件即可.这也使得SQLite适合用作应用程序文件格式.但是,当在单个文 ...
- Android 中 SQLite 性能优化
数据库是应用开发中常用的技术,在Android应用中也不例外.Android默认使用了SQLite数据库,在应用程序开发中,我们使用最多的无外乎增删改查.纵使操作简单,也有可能出现查找数据缓慢,插入数 ...
- sqlite性能简单測试
主要測试sqlite在大数据量下的插入及查询性能: 測试环境:Centos6.4 1G内存 单核 数据量 大小 索引字段检索(耗时) 非索引字段检索(耗时) 总插入时间 10W 19M 0.001 ...
- sqlite性能优化
1.数据库性能上 1.1 批量事务插入,提升数据插入的性能 由于sqlite默认每次插入都是事务,需要对文件进行读写,那么减少事务次数就能简书磁盘读写次数从而获得性能提升. 1.2 单条sql优于多条 ...
- Android SQLite性能分析
作为Android预置的数据库模块,对SQLite的深入理解是很有必要的,能够从中找到一些优化的方向. 这里对SQLite的性能和内存进行了一些測试分析.对照了不同操作的运行性能和内存占用的情况,粗略 ...
- SQLite性能 - 意想不到,但又情理之中的测试结果。
win7(64) sata2 希捷 MINGW32_NT-(/) cat: /proc/cpuinfo: No such file or directory ------ in disk ---- r ...
- Android开源项目分类汇总-转载
太长了,还是转载吧...今天在看博客的时候,无意中发现了@Trinea在GitHub上的一个项目Android开源项目分类汇总,由于类容太多了,我没有一个个完整地看完,但是里面介绍的开源项目都非常有参 ...
- SQLite内存数据库
[转]SQLite内存数据库 http://www.cnblogs.com/liuyong/archive/2010/09/14/1826152.html SQLite 介绍 一. SQLite 是实 ...
- 【原创】System.Data.SQLite内存数据库模式
对于很多嵌入式数据库来说都有对于的内存数据库模式,SQLite也不例外.内存数据库常常用于极速.实时的场景,一个很好的应用的场景是富客户端的缓存数据,一般富客户端的缓存常常需要分为落地和非落地两种,而 ...
随机推荐
- python编程系列---tcp客户端的简单实现
实现流程如下: """ TCP客户端实现流程1. 创建一个tcp 客户端对象2. 与服务端建立连接3. 通过tcp socket 收发数据4. 关闭连接 关闭tcp &q ...
- 数据结构(四十二)散列表查找(Hash Table)
一.散列表查找的基础知识 1.散列表查找的定义 散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key).查找时,根据这个确定的对应关系找到 ...
- unity 开启外部摄像头
在unity中建立一个image作为摄像头显示画面,然后通过命令render到image上即可. public WebCamTexture webTex; public string deviceNa ...
- fenby C语言 P6
printf=格式输出函数; printf=("两个相加的数字是:%d,%d,他们的和是:%d\n",a,b,c); %d整数方式输出; \n=Enter; int a=1; fl ...
- RESTFul API最佳实践
RESTful API最佳实践 RESTful API 概述 基本概念 REST 英文全称:Representational State Transfer,直译为:表现层状态转移.首次是由Roy Th ...
- vue学习笔记-遗留问题记录
Node.js是什么?对node.js的理解 官网解释:Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时. 这是一种通过JavaScript语言开发web服务端的东 ...
- AtCoder Grand Contest 038
目录 \(\bf A - 01 \ Matrix\) \(\bf B- Sorting \ a \ Segment\) \(\bf C-LCMs\) \(\bf D-Unique \ Path\) 这 ...
- 欧拉路&&欧拉回路
T1是欧拉路板子,但我不会,直接爆炸.. 这玩意就是个dfs,但我以前一直以为欧拉路只能$O(nm)$求 今天才知道可以$O(n+m)$ 欧拉路判定: 无向:起点终点为奇度点,其余偶度 有向:起点终点 ...
- python爬虫-携程-eleven参数
携程-eleven分析 一.eleven的位置 通过对旁边栈的分析,它是在另一个js文件中调用的.那个js文件是一个自调用的函数,所以我们可以直接copy下来,用浏览器执行看看 执行运行是会报错的,u ...
- 原生JS封装_new函数,实现new关键字的功能
1.前言 众所周知:没有对象怎么办?那就new一个! 那么在JS中,当我们new一个对象的时候,这个new关键字内部都干了什么呢? 现在我们就来剖析一下原生JS中new关键字内部的工作原理. 2.原生 ...