iOS FMDB迁移到WCDB

移动端的数据库,除了使用"SQLite"这个共识,基本各自为政。git

iOS这边以前使用的是基于SQLite封装的FMDB。一开始使用并没有问题。但在长期的使用中反映出,有性能瓶颈,好比说某个用户长期未登陆,在登陆时收到大量消息,因为FMDB不支持多线程的写操做,会致使写入很慢。github

遇到性能瓶颈后咱们开始寻找FMDB的替代品,就是WCDB,微信开源官方移动端数据库组件。进入咱们的实现。依托微信的用户量和对数据库的依赖,WCDB已经处理了不少坑点和瓶颈,开源1年多,不断地迭代功能,完善文档。同时WCDB在Github的wiki上提供了专门的教程,帮助使用FMDB的开发者进行迁移。sql

性能对比

对于已经上线运行的项目,解决性能瓶颈会是一个常见的迁移理由。相较于FMDB直白的封装,WCDB上到OC层的ORM,下到SQLite源码,都作了各种性能优化。 为了验证优化效果,微信提供benchmark,并将性能测试结果和测试代码上传到了Github。同时,benchmark中也加入了FMDB的测试代码,用于横向比较。 如下性能测试均为WAL模式、缓存大小2000字节、页大小4 kb:数据库

PRAGMA cache_size=-2000
PRAGMA page_size=4096
PRAGMA journal_mode=WAL
复制代码

测试数据均为含有一个整型和一个二进制数据的表:CREATE TABLE benchmark(key INTEGER, value BLOB),二进制数据长度为100字节。缓存

  • 读操做性能测试
  • 写操做性能测试
  • 批量写操做性能测试 (事务)
    对于读操做,SQLite速度很快,所以封装层的消耗占比较多。FMDB只作了最简单的封装, 而WCDB还包括ORM、WINQ等操做,所以执行的指令会比FMDB多,从而致使性能劣于FMDB 5%。 而写操做一般是性能的瓶颈,WCDB对其作了许多针对性的优化,使得写操做和批量写操做的性能分别优于FMDB 28% 和 180%。
  • 多线程读并发性能测试
  • 多线程读写并发性能测试
  • 多线程写并发性能测试
    在多线程读操做的测试中,WCDB多线程并发的优点,将读操做的性能劣势拉了回来,使得最终结果与FMDB基本持平,而多线程读写操做性能则优于FMDB 62% 。 在多线程写操做的测试中,FMDB直接返回错误SQLITE_BUSY,没法完成。
  • 初始化性能测试
    SQLite链接的初始化速度会随着数据库内表的数量增长而逐渐上升,WCDB也针对这个场景作了优化。相较于没有优化的FMDB,WCDB 有107% 的性能优点。

平滑迁移

文件格式

因为FMDB和WCDB都基于SQLite,所以二者在数据库的文件格式上一致。用FMDB建立、操做的数据库,能够直接经过WCDB打开、使用。所以开发者无需作额外的数据迁移。性能优化

表结构

WCDB提供了ORM的功能,将类的属性绑定到数据库表的字段。在平常实践中,类的属性名和表的字段名一般不一致。所以,WCDB提供了WCDB_SYNTHESIZE_COLUMN(className, propertyName, columnName)宏,用于映射属性名。 对于 表:CREATE TABLE message (db_id INTEGER, db_content TEXT) 类:bash

//Message.h
@interface Message : NSObject

@property int localID;

@property(retain) NSString *content;

@end

//Message.mm
@implementation Message

@end
复制代码

这里表字段都加了"db_"的前缀,而且使用了不同的字段名。经过WCDB的ORM,能够映射为微信

//Message.h
@interface Message : NSObject <WCTTableCoding>

@property int localID;
@property(retain) NSString *content;
WCDB_PROPERTY(localID)
WCDB_PROPERTY(content)

@end
//Message.mm
@implementation Message

WCDB_IMPLEMENTATION(Message)
WCDB_SYNTHESIZE_COLUMN(Message, localID, "db_id")
WCDB_SYNTHESIZE_COLUMN(Message, content, "db_content")

@end
复制代码

经过WCDB_SYNTHESIZE_COLUMN宏映射后,WCDB一样能兼容FMDB的表结构,开发者也不须要作数据迁移。因为WCDB较之FMDB性能上有着较大提高,迁移起来因为都是基于SQLite封装,基本上都是兼容的,因此咱们决定使用WCDB。多线程

替换前代码并发

+ (BOOL)insertGroupInfoData:(KitGroupInfoData *)infoData{
    BOOL result;
    //UPDATE
    KitGroupInfoData *groupInfoExit = [KitGroupInfoData getGroupInfoWithGroupId:infoData.groupId];
    if(groupInfoExit){//存在 updateHIYUNTON Group
        NSString *sql = [NSString stringWithFormat:@"UPDATE %@ SET groupName = ?, declared = ?, memberCount = ?,type = ?,isAnonymity = ?,isDiscuss = ? WHERE groupId = '%@' ",DATA_GROUPINFO_DBTABLE,infoData.groupId];
        result = [self updateTable:sql,!KCNSSTRING_ISEMPTY(infoData.groupName)? infoData.groupName:@"",!KCNSSTRING_ISEMPTY(infoData.declared)?infoData.declared:@"",[NSNumber numberWithInteger:infoData.memberCount],[NSNumber numberWithInteger:infoData.type],infoData.isAnonymity?@"1":@"0",infoData.isDiscuss?@"1":@"0"];
        return result;
    }else{//不存在insert
        NSString *sql = [NSString stringWithFormat:@"INSERT INTO %@ %@", DATA_GROUPINFO_DBTABLE, @"(groupId, groupName ,declared, createTime,owner,memberCount,type,isAnonymity,isDiscuss) VALUES (?, ?, ? , ?, ?, ?, ?,?,?)"];
        result = [self updateTable:sql,infoData.groupId, infoData.groupName,infoData.declared,infoData.createTime,infoData.owner,[NSNumber numberWithInteger:infoData.memberCount],[NSNumber numberWithInteger:infoData.type],infoData.isAnonymity?@"1":@"0",infoData.isDiscuss?@"1":@"0"];
        return result;
    }
    return YES;
}

+ (BOOL)upDateGroupInfo:(KitGroupInfoData *)groupInfo{
    __block BOOL result;//UPDATE
    [[[KitDataBaseManager sharedInstance] userDB_Queue] inDatabase:^(FMDatabase *db) {
        [db open];
        NSString *sql = [NSString stringWithFormat:@"UPDATE %@ SET groupName = ?, declared = ?,owner = ? WHERE groupId = '%@' ",DATA_GROUPINFO_DBTABLE,groupInfo.groupId];
        result = [db executeUpdate:sql,!KCNSSTRING_ISEMPTY(groupInfo.groupName)? groupInfo.groupName:@"",!KCNSSTRING_ISEMPTY(groupInfo.declared)?groupInfo.declared:@"",groupInfo.owner];
        [db close];
    }];
    return result;
}
复制代码

替换后代码

+ (BOOL)insertGroupInfoData:(KitGroupInfoData*)infoData{
    WCTDatabase *dataBase = [DataBaseManager sharedInstance].dataBase;
    return [dataBase insertOrReplaceObject:infoData into:DATA_GROUPINFO_DBTABLE];
}

+ (BOOL)upDateGroupInfo:(KitGroupInfoData *)groupInfo{
    WCTDatabase *dataBase = [DataBaseManager sharedInstance].dataBase;
    return [dataBase updateRowsInTable:DATA_GROUPINFO_DBTABLE onProperties:{KitGroupInfoData.groupName,KitGroupInfoData.declared,KitGroupInfoData.owner} withObject:groupInfo where:KitGroupInfoData.groupId == groupInfo.groupId];
}
复制代码

总结

在使用了WCDB以后,代码变得更加简洁的同时,性能还获得了提升,也不须要额外关注数据库升级和多线程操做的问题。WCDB还提供了加密、统计、修复等功能供咱们使用。在解决性能瓶颈的同时,也解决以前使用FMDB的以下问题:

  1. 胶水代码的问题 过去一个几十行的函数,绝大部分都是拼接SQL、处理SQLite返回的空数据和错误码之类的“裹脚布”代码。并且这种代码四处分布,字里行间都写着"Copy & Paste"。 而如今ORM取出即为对象无需拼接SQL。
  2. 效率问题 SQL基于字符串,命令行爱好者甚喜之。但对于基于现代IDE的移动开发者,倒是一大痛。字符串得不到任何编译器的检查,业务开发每每心中一团热火,奋笔疾书下几百行代码,满心欢喜点下Run后才发现:出错了!静心下来逐步看log、断点后才发现,噢,SELECT敲成SLEECT了。改正,再等待编译完成,此时已过去十几分钟,还谈何效率?而如今经过ORM式可以经过IDE来检测是否错误。
  3. SQL注入问题 SQL注入一般是利用SQL字符串拼接的特色,用一些特殊符号提早截断SQL,达到执行其余SQL的目的。试想这么一段代码
    这段封装很简单,就是将消息内容插入到数据库中。假设对方发来这么一条消息:"');DELETE FROM message;--",那么这条SQL就会被截断成三部分:
    它会在插入一条消息后,将表内的全部消息删除。假若存在这样的漏洞,后果将不堪设想。 其实反注入并不难,经过绑定参数或替换单引号为双单引号便可解决。但要在业务开发的过程时时刻刻警戒这样的风险,并不现实,毕竟人总会犯错的。
  4. 多端同步问题 因为以前没有安卓和iOS端通用的三方库,你们也是各自为政的,使用不一样天然会出现各类各样的不一样步问题。在接入了WCDB后,咱们的数据库方面便会统一

迁移过程当中遇到的问题

  1. 工程中有的类是SDK提供的仅有头文件。因为WCDB是基于对象绑定的,因此最后经过建立子类对象绑定WCDB作中间转换实现。
  2. 项目中存了一个用于查询运营商的db文件,只是单纯的从db里查询,一样因为WCDB须要绑定没法实现,因为只是一个小查找,使用位置很少,最后使用了sqlite3原生方法。

参考资料

为何要从FMDB迁移到WCDB?

github官方wiki

相关文章
相关标签/搜索