你的位置:首页 > 数据库

[数据库]Sqlite学习笔记(二)性能测试

测试目标

获取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) 单行单事务

begininsert into user values(1,’xxx’);commit;begininsert into user values(1,’xxx’);commit;……

b)  多行单事务

begininsert into user values(1,’xxx’);insert into user values(2,’xxx’);……commit;

c)  prepare绑定

beginprepare 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,一定伴随着大量的随机写,导致更新性能比较差。