标签:
测试目标
获取SQlite的常规性能指标
测试环境
CPU:8核,Intel(R) Xeon(R) CPU E5-2430 0 @ 2.20GHz
内存:16G
磁盘:SSD
Linux 2.6.32
SQlite最新版本3.8.11
测试场景
1) 主键查询测试
2) 主键更新测试
3) 批量导入测试
初始化
1) 测试表结构
CREATE TABLE user(
id integer primary key autoincrement,
c1 int,
c2 varchar(1000),
c3 varchar(1000));
CREATE TABLE orders(
id integer primary key autoincrement,
user_id int,
c1 varchar(1000),
c2 varchar(1000));
2) 初始化数据
通过程序往user表和orders表中导入10w条记录,整个db文件在400M左右。
3) 测试说明
sqlite本身通过PRAGMA命令可以设置程序缓存大小( cache_size),但同时sqlite的缓存策略中并没有忽略操作系统缓存的影响,因此本文的测试结果使用默认的cache_size(2000个page),通过多次测试取平均值,来得到一个大概的性能指标。此外,sqlite主要用于嵌入式设备,而本文的测试基于PC,因此测试数据仅作参考。
单表主键查询
1) 测试说明
该项测试主要测试主键查询的性能,测试语句形如:
“select * from user where id = xxx”,xxx通过随机函数生成,由于生成的测试数据id的范围是[1-100000],通过随机函数生成[1-1000000]的随机数,基本能保证1%的命中率(实际测试中得到印证)。Sqlite支持读并发,因此该项测试测试了多线程并发情况下的性能,测试结果的时间单位为毫秒(ms)。多线程测试模型很简单,每个线程执行同样的查询10w次,计算总耗时时间,然后根据平均值与时间的比值,计算出QPS和TPS,通过参数SQLITE_OPEN_SHAREDCACHE控制是否启用共享缓存模式。
2) 测试结果
a) 非共享缓存模式
| 
 线程数目  | 
 1  | 
 2  | 
 4  | 
 8  | 
| 
 第一轮  | 
 2886  | 
 3641  | 
 8392  | 
 19615  | 
| 
 第二轮  | 
 2867  | 
 3933  | 
 8088  | 
 21010  | 
| 
 第三轮  | 
 2821  | 
 4131  | 
 8077  | 
 21220  | 
| 
 第四轮  | 
 2941  | 
 4011  | 
 7787  | 
 20983  | 
| 
 第五轮  | 
 2896  | 
 3724  | 
 7881  | 
 21332  | 
| 
 平均值  | 
 2881  | 
 3949  | 
 7958  | 
 21136  | 
| 
 CPU%  | 
 80%  | 
 180%  | 
 320%  | 
 670%  | 
| 
 QPS  | 
 34w  | 
 50.6w  | 
 50.2w  | 
 37.85w  | 
表一
b) 共享缓存模式
| 
 线程数目  | 
 1  | 
 2  | 
 4  | 
| 
 第一轮  | 
 3050  | 
 12616  | 
 26554  | 
| 
 第二轮  | 
 3077  | 
 12331  | 
 26396  | 
| 
 第三轮  | 
 3131  | 
 12327  | 
 27070  | 
| 
 第四轮  | 
 3096  | 
 13014  | 
 27031  | 
| 
 第五轮  | 
 2972  | 
 12866  | 
 27778  | 
| 
 平均值  | 
 3065  | 
 12634  | 
 26965  | 
| 
 CPU%  | 
 80%  | 
 120%  | 
 120%  | 
| 
 QPS  | 
 32.6w  | 
 15.8w  | 
 14.8w  | 
表二
3) 结果分析
从表一结果来,随着并发度提升,主机CPU利用率也随着上升;QPS由单线程34w,上升到4线程并发50w左右,但是到8线程又出现了一定的回落,这说明,在高并发情况下,QPS由于其它因素,比如磁盘IO,或者程序本身的并发问题,会达到一定的瓶颈。从绝对值来看每秒50w的查询性能,也确实很不错!
从表二结果来看,设置共享缓存模式后,并发性能有很大的下降,从CPU利用率就可见一斑,QPS由单线程32.6w降低到8线程14.8w左右。关于这一点我一直很疑惑,为啥开了共享缓存后,并发性能还下降了。通过在程序运行过程中抓取堆栈并结合源码找到了原因,并发查询时,大量的线程会堵塞在sqlite3BtreeEnter函数中的mutex里面。共享内存模式下,进程内的多个线程通过共享同一个B树对象,达到共享内存的目的,B树对象通过一个mutex保护,正是由于这个mutex的竞争,导致并发度严重下降。所以共享内存模式虽然能减少内存的使用,但是以牺牲并发性能为代价的。
批量载入测试
1) 测试说明
导入数据是db最常用的一个功能,该项测试主要测试了3种模式的导入性能,单行单事务,多行事务和prepare模式的多行事务。主要模型如下:
a) 单行单事务
begin
insert into user values(1,’xxx’);
commit;
begin
insert into user values(1,’xxx’);
commit;
……
b) 多行单事务
begin
insert into user values(1,’xxx’);insert into user values(2,’xxx’);……
commit;
c) prepare绑定
begin
prepare insert into user(id, c1) values(?,?);
bind (id,c1)
……
commit;
2) 测试结果
| 
 
  | 
 单行事务  | 
 10w行事务  | 
 10w行事务 (prepare)  | 
| 
 第一轮  | 
 1693533  | 
 11856  | 
 9079  | 
| 
 第二轮  | 
 1673983  | 
 11667  | 
 8375  | 
| 
 第三轮  | 
 略  | 
 12075  | 
 8566  | 
| 
 第四轮  | 
 略  | 
 11611  | 
 8773  | 
| 
 第五轮  | 
 略  | 
 11331  | 
 8660  | 
| 
 平均值  | 
 
  | 
 11671  | 
 8593  | 
| 
 TPS  | 
 60  | 
 8568  | 
 1.16w  | 
表三
3) 结果分析
从测试结果来看,单行事务和多行事务差别非常大,这也充分说明了,对于db而言,事务提交动作是非常耗时的。单行事务TPS只有60,而10w行事务TPS则达到了8500,有超过100倍的提升。与传统DBMS一样,sqlite提交事务时,也需要进行较慢的刷盘动作,因此刷1次盘与刷10w次盘,性能差别非常大。第三栏是prepare类型的事务,也是采用了10w行作为一个事务单位,但效果会更优。这主要原因是采用prepare模型事务,10w行记录只需要解析1次,而前者需要解析10w次,虽然解析时间不长,但积少成多,所以第三栏仅仅这一个优化点,就将TPS从8500提升到1.16w。
主键更新
1) 测试说明
本测试用例的语句也非常简单,就是简单的主键更新,将列值自增1。测试语句形如:update user set c1=c1+1 where id=xxx。SQLite不支持并发更新,因此测试写都是单线程。分别模拟单行事务,多行事务,观察SQLite的更新性能。
2) 测试结果
| 
 
  | 
 单行事务  | 
 1000行事务  | 
 1w行事务  | 
| 
 第一轮  | 
 164784  | 
 16623  | 
 16232  | 
| 
 第二轮  | 
 170256  | 
 16382  | 
 17514  | 
| 
 第三轮  | 
 166387  | 
 17099  | 
 17696  | 
| 
 第四轮  | 
 172987  | 
 17030  | 
 17753  | 
| 
 第五轮  | 
 166543  | 
 16386  | 
 17787  | 
| 
 平均值  | 
 169043  | 
 16724  | 
 17832  | 
| 
 TPS  | 
 59  | 
 598  | 
 560.7  | 
表四
3) 结果分析
关于多行事务这一块,基本与导入操作类似,多行事务可以显著提高性能。同时,也要看到更新的TPS相比插入的TPS要相差很多。个人推断这个现象与磁盘IO有莫大关系,因为插入时,由于主键自增,写都是顺序写;而本测例的更新都是随机更新,而且产生的脏页远远大于cache_size,一定伴随着大量的随机写,导致更新性能比较差。
标签:
原文地址:http://www.cnblogs.com/cchust/p/4738002.html