缘起
sqlite写入500条不大的记录居然要花费20多秒的时间,太慢了!!!
分析
sqlite是一个非常优秀的嵌入式数据库,读取性能非常好,写入性能就比较差一些,为什么写入性能差呢?下面做了一个测试。
下面是对500+条记录些操作的系统调用的观察,发现时间基本花费在了fdatasync系统调用上,调用2064次;write系统调用虽然8049次,但是write并不保证逻辑,所以速度很快。
$ strace -c php test.php
27.688107013702 (耗时27.6s)
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
88.73 0.073919 36 2064 fdatasync
3.88 0.003231 0 8049 write
3.77 0.003144 6 516 unlink
1.12 0.000929 0 6730 fcntl64
0.90 0.000751 0 9118 3 _llseek
0.86 0.000720 1 1218 82 open
0.24 0.000198 0 1143 close
0.20 0.000167 0 1182 read
0.16 0.000131 0 520 518 access
0.05 0.000039 1 42 1 lstat64
0.04 0.000033 0 194 mmap2
0.03 0.000027 0 92 munmap
0.03 0.000021 0 58 49 stat64
0.00 0.000000 0 1 execve
0.00 0.000000 0 20 time
0.00 0.000000 0 21 brk
0.00 0.000000 0 3 2 ioctl
0.00 0.000000 0 3 gettimeofday
0.00 0.000000 0 1 readlink
0.00 0.000000 0 2 uname
0.00 0.000000 0 28 mprotect
0.00 0.000000 0 4 poll
0.00 0.000000 0 5 rt_sigaction
0.00 0.000000 0 2 rt_sigprocmask
0.00 0.000000 0 2 getcwd
0.00 0.000000 0 1 getrlimit
0.00 0.000000 0 648 fstat64
0.00 0.000000 0 4 futex
0.00 0.000000 0 1 sched_setaffinity
0.00 0.000000 0 2 sched_getaffinity
0.00 0.000000 0 1 set_thread_area
0.00 0.000000 0 1 set_tid_address
0.00 0.000000 0 1 set_robust_list
0.00 0.000000 0 4 socket
0.00 0.000000 0 4 2 connect
0.00 0.000000 0 1 send
0.00 0.000000 0 1 recvfrom
0.00 0.000000 0 1 shutdown
0.00 0.000000 0 5 setsockopt
------ ----------- ----------- --------- --------- ----------------
100.00 0.083310 31693 657 total查资料
http://www.cnblogs.com/KimSky/archive/2011/05/31/2064028.html
发现: 如果使用事务的方式批量插入数据,效果会有明显改善,因为默认情况下每次写入操作都会落地才返回的(更加安全靠谱),如果使用事务,则批量数据一次性落地。
修改代码,分析系统调用如下:(发现fdatasync调用12次)
$ strace -c php test.php
0.20949816703796 (耗时 209ms)
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
71.64 0.000998 83 12 fdatasync
7.61 0.000106 1 155 read
5.24 0.000073 0 194 mmap2
4.16 0.000058 0 195 write
2.58 0.000036 0 135 fstat64
1.65 0.000023 0 92 munmap
1.58 0.000022 1 28 mprotect
1.51 0.000021 1 42 1 lstat64
1.44 0.000020 20 1 readlink
1.29 0.000018 0 238 3 _llseek
1.29 0.000018 0 58 49 stat64
0.00 0.000000 0 192 82 open
0.00 0.000000 0 117 close
0.00 0.000000 0 3 unlink
0.00 0.000000 0 1 execve
0.00 0.000000 0 20 time
0.00 0.000000 0 7 5 access
0.00 0.000000 0 21 brk
0.00 0.000000 0 3 2 ioctl
0.00 0.000000 0 3 gettimeofday
0.00 0.000000 0 2 uname
0.00 0.000000 0 4 poll
0.00 0.000000 0 5 rt_sigaction
0.00 0.000000 0 2 rt_sigprocmask
0.00 0.000000 0 2 getcwd
0.00 0.000000 0 1 getrlimit
0.00 0.000000 0 61 fcntl64
0.00 0.000000 0 4 futex
0.00 0.000000 0 1 sched_setaffinity
0.00 0.000000 0 2 sched_getaffinity
0.00 0.000000 0 1 set_thread_area
0.00 0.000000 0 1 set_tid_address
0.00 0.000000 0 1 set_robust_list
0.00 0.000000 0 4 socket
0.00 0.000000 0 4 2 connect
0.00 0.000000 0 1 send
0.00 0.000000 0 1 recvfrom
0.00 0.000000 0 1 shutdown
0.00 0.000000 0 5 setsockopt
------ ----------- ----------- --------- --------- ----------------
100.00 0.001393 1620 144 total结论:
1. 借用事务采用批量写入的方式来加速写操作
2. 如果业务上不能批量操作呢?似乎有一个nosync的sqlite版本(不知道为什么不是一个配置选项)
参考资料: http://www.sqlite.org/speed.html
3. 如果数据量太大,可以分多批提交事务,因为事务是需要内存的。(不过,sqlite一般不会存放N个G的数据的,几百MB已经算是比较大的了,这样的数据量内存还是吃的消的)
参考资料:
http://www.phpchina.com/archives/view-33876-1.html
关于sqlite的系列分析文章(可以看看)
http://www.cnblogs.com/hustcat/archive/2009/02/12/1389448.html
SQLite的原子提交原理
http://www.cnblogs.com/vagerent/archive/2008/11/05/1327247.html