sqliteiOS – Sqlite 数据库服务层优化过程

场景

发生个SDK的数据库读写很慢的,写副 1000 漫漫数耗时 18.5
秒,虽然在了异步线程做,但感觉还慢了接触。另外手机的硬件配备于低,考虑到电池续航,大小等等因素不容许做得老快。硬件改不了,考虑于代码层面优化。

一个题材,假要写副 1000 长长的数据常常,又触及了读数据,要达传播服务器,还得等
18.5 秒,这个进程中,APP进程可能为 kiil 掉,用户手动关闭 APP 后,其经过
5 秒钟后终止。导致数据没有写到库里就给 kill 了,数据丢失。

优化过程

用 FMDB,基于 sqlite 的数据库。

1.改配置

用 WAL 模式

PRAGMA journal_mode = WAL
PRAGMA journal_mode = DELETE

WAL 模式:事务写的当儿,若提交,则写及 WAL
文件被,若回滚就直不写文件了,sqlite 按时将 WAL
的情节统一及数据库中。
DELETE 模式:事务写的时刻,先备份一卖数据到 old 文件,若提交,则去
old 文件,若回滚,则拿 old 内容还原到原来的岗位。
明白 WAL 模式对写的性质更快。读之言辞得读多独公文。
WAL模式测试结果

故而多线程模式

PRAGMA SQLITE_THREADSAFE = 0   // 单线程
PRAGMA SQLITE_THREADSAFE = 1   // 多线程串行
PRAGMA SQLITE_THREADSAFE = 2   // 多线程并发

SQLITE_THREADSAFE = 2
后,FMDB不加锁,性能快了,但要自己保证数据库访问的线程安全。

2.错过丢业务层 SQL 注入检查

为防 SQL 注入,旧代码检查用户字段和数据库字段是否匹配,用了单 O(n^2)
复杂度的方法,看正在就是当徐。
后来改成为了哈希查找的主意,快了一些。
新兴发现 FMDB 已经做了 SQL 防注入了。
运用 ? 通配符,让 FMDB 去匹配,如果 name 是单 SQL
语句,它将让当做字符串插入数据库被,不会见受实施及。

[db executeUpdate:@"insert into student(name) values(?)", @"hehe"];

并非自己去拼接 SQL 语句,除非自己开了防 SQL 注入检查。
若是是友善拼接的,别人传个 name = ” ‘a’) delete from student –‘ “

char *name = " 'a')  delete from student --' ";
"insert into student(name) values(%s)", name;
变成 
insert into student(name) values('a')  delete from student --')
整理一下
insert into student(name) values('a')  
delete from student 
--')

尽表都被清空了。

3.差不多久告句子合并成一个工作提交

习一下作业的季可怜特色,原子性,一致性,隔离性,永久性

启同结束工作是比较耗时的。在 WAL 模式下,事务要描写 WAL 文件,DELETE
模式下,事务要先行复制一卖 old
旧数据,然后交给或回滚了,还要删掉或者恢复就多少。

能集合成事务提交的言语就联合,性能会高有。因为各级条查询语句,数据库默认会开启一个隐式事务。像下这样

for (int i=0; i<10000; i++) 
{
    [db beginTransaction];    // 数据库会默认开启,你不写就默认开启
    insert into student(name) values('caokun')
    [db commit];              // 数据库会默认开启
}

友善被事务,即显式事务

[db beginTransaction];    // 自己手动写的
for (int i=0; i<10000; i++) 
{
    insert into student(name) values('caokun')
}
[db commit];              // 自己手动写的

联合成事务

万一调用层会不定时的犯来 SQL
查询语句,我把这些报告句加入到一个串行队列,串行的实行。
收起一模一样长查询请求,等待 25 ms,如果 25 ms 内并未请来,那么执行其。
只要 25 ms 内又来了外一个请,那么还等待 25 ms,直到超了 25
ms,然后合并成为一个事情执行。

假如直白来呼吁,不就是永远为推行不了了?
装极端要命条数,比如跨越 1000 单请求了,就将前 1000
独从包改成一个作业执行。
抑或过一定时间,比如 2 秒,就打包事务执行。

或者提供批量询问的接口。业务层有些累写库,但是还要没道用批量状库的。比如当用户迅速滑动屏幕,显示一个
cell,就栽入一条记下,没道用批量查询的接口。

4.读与读并发

第3步说了所以个串行队列去管理有请求。请求只有 select-读, update-写,
delete-写, insert-写,所以还是是朗诵,要么是形容。
之所以队列里是这般的:读读写写读写读写读读读读…

朗诵跟读之间都是串行的。但读不改动数据完整性,所以读跟读是得出现的。进一步提高了读之频率。

鉴于作业需而保管一致性,即写了,要马上会读出来,不能够有推,所以并未道将读跟写做成并发的了,必须顶描绘了,才会念,不然可怜可能读不至刚刚写副的。

用只要做成这样的:写及写同步,写及读共,读跟读并发。

旋即即变成了大抵独读者写者的题目

许多单读者读,多独写者写,但是生写者在形容的上,是不克念之,有读者以朗诵之时候,是无能够写的。
读者写者已发生缓解的办法。

定义 sem_cache 是同步共享区访问的信号量,应用中共享区即是数据库
定义 count 读者的计数,记录有几个读者在读
定义 sem_count 是同步计数器的访问的信号量

// 多个写者线程
void writer() 
{
    while (1) 
    {
        P(sem_cache);    // 尝试进入共享区写
        write_data();    // 写共享区
        V(sem_cache);    // 释放信号
    }
}

// 多个读者线程
void reader() 
{
    while (1)
    {
        P(sem_count);  // 读者尝试修改 count 进入共享区
        if (count == 0) 
        {
            // count == 0 说明是第一个读者进来,那么把共享区锁住,不让其他人访问
            P(sem_cache);
        }
        count++;    // 加上自身
        V(sem_count);  // count 访问完了,释放

        read_data();  // 读共享区

        P(sem_count);  // 读者尝试修改 count 进入共享区       
        count--;  // 自身离开
        if (count == 0) 
        {
            // count == 0 说明是最后一个读者出去,那么把共享区释放,让其他人访问
            V(sem_cache);
        }
        V(sem_count);  // count 访问完了,释放 
    }
}

管数据库看成共享区,然后按读者写者方法管理线程访问数据库,即实现了读跟读并发,进一步提高速度。

可事情层有读写顺序的要求,就是迟早要遵循业务层的调用顺序进行读写,用一个GCD的串行队列,保证读写顺序,遇到读的时,开个新线程去并发执行,或者把读任务在到连作班中,遇到写的时节,就以串行队列中举行。

5.小细节

按,减少频繁创建同销毁对象。
反复查询的字段缓存在内存中,并瞩目内存中与数据库中之数的一致性问题
iOS 没有 Memcache
之类的框架,所以缓存状态得投机维护,还好活动端不是特别多设缓存的数据。

对比:

优化前:itouch 5, iOS 8 的条件,1000长达写多少用时 18.7 秒
优化后:itouch 5, iOS 8 的条件,1000漫长写多少用时 370 ms
笔记本 SSD 环境,iOS虚拟机中,1000修写多少用时 18 ms
笔记本没装 sqlite,在虚拟机中测的,直接作伪物理机上估计会重复快。

参考文献:
【Dev Club 分享第七盼望】微信iOS
SQLite源码优化履
sqlite
数据库性能调优

网站地图xml地图