热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

iOS9sqlite3执行查询语句十分缓慢的解决方案

一、问题来源我所就职的公司是一家医疗互联网企业,作为一名iOS开发,目前我参与开发迭代的是一款面向医生用户的APP,这款APP的主要功能之一就是随手记病历,前不久我们的运营同学接到用户

一、问题来源

我所就职的公司是一家医疗互联网企业,作为一名iOS开发,目前我参与开发迭代的是一款面向医生用户的APP,这款APP的主要功能之一就是随手记病历,前不久我们的运营同学接到用户反馈,在病历数量较多(达到数千份),一些操作十分卡顿。
这个问题很容易就解决了(一些耗时操作卡住了主线程),不过在解决这个问题的过程中,反而发现了更大了一个问题,在sqlite中iOS8和iOS9执行一些相同的查询语句,效率相差十分巨大!

二、问题表现

我使用了4000左右的数据量进行了测试,发现执行同一条包含子查询共四个在表在内的查询语句:

  • 在iPhone6 Plus iOS8.0.2真机上面花费0.8秒左右的时间。
  • 在iPhone6 iOS9.1真机上面花费48秒左右的时间。

三、问题定位

作为一名开发者,应该都会对这类怪异的问题感兴趣(难道是恶趣味),可能是解决这类问题比较有成就感吧。
我也不例外,我就又花费了一些时间尝试去查一些解决方案,遗憾的是,百度、google之后并没有找到太有用的东西,只在stackoverflow上面找到一个相同的问题,然而并没有人回答,只是本人通过修改SQL查询语句解决了在iOS9查询速度十分慢的问题。

1.总体问题

没办法,看来不能走捷径了,于是做了进一步测试,发现分别在iOS8和iOS9的真机上面执行一些查询语句:

  • 单表查询数据量即使达到上万,执行相同的sqlite查询操作性能并没有明显的差别,耗时并不会很多(取决于查询条件和单表列数等)。
  • 大部分多表查询在数据量大时(数千),性能并不会有明显差异。
  • 部分多表查询数据量大时(数千),执行相同的sqlite查询操作性能上会有较大差别。

看来问题就出在多表查询上了。

2.具体问题

由于公司相关信息不便公开,这里以另外的类似场景举例做描述。

  1. 假如对全国的中学做了以下一些信息统计:学校信息、学校班级班级、班级学生信息,学生期末考试成绩信息。共使用4个数据表进行数据存储。

  2. 需要做的查询:列出所有——学校基本信息+学校平均成绩最好的一个班级+学校某年级总成绩最好的一名学生

  3. 发现的问题:原来的查询会在一条sql语句中完成,执行这条sql时在iOS8和iOS9中时间相差极大(如二、描述)。通过拆分发现,把查询“学校某年级总成绩最好的一名学生”的子查询拆分出去以后iOS8和iOS9的查询速度就接近了。

那么,看来查询“学校某年级总成绩最好的一名学生”的子查询就是问题的突破点了。

四、解决方案

我先后尝试了两种解决方案:

  1. 改变sql语句,做分表查询,内存组合数据;

  2. 不改变sql语句,添加索引,加快查询速度。

1.分表查询,内存组合数据(未选用)

在我们的APP中,在满足数据展示的前提下,我尝试将原来的一条查询拆分两个查询,分别对两个子查询的速度进行了测试,发现无论在iOS8或iOS9上面速度都是没问题的。
之所以放弃这个方案,是因为SQL执行速度的问题解决了,不过新的问题出现了,那就是内存组合数据的效率问题。
内存组合数据就是将两次的查询结果所得的两个model数组组合成一个新的model数组。
以id做关键字匹配,效率最低的情况是两次全量for循环嵌套即n²,实测两个4000的model数组组合花费了15s左右的时间,后来简单做了优化,降低到了10s左右。这也是不能接受的。

2.建立索引(选用)

作为一名客户端开发,我对数据库并没有深入的学习,对数据库的性能优化并不擅长,于是和一些对数据库知识有较深了解的同事讨论这个问题,一位同事提醒我,你可以试试建立索引。
于是,我试着给影响性能的那个表单个列建立了索引,实测,无论在iOS8还是iOS9执行速度上面都没有问题。

部分测试数据表:
Tip:首次查询需要建立数据库连接,耗时较多。

iPhone4s iOS 7.0.4 iPhone 6 Plus iOS 8.0.2 iPhone 6 iOS 9.2
第n次执行测试 添加索引前 添加索引后 添加索引前 添加索引后 添加索引前 添加索引后
1 8.077 7.824 2.216 2.049 49.183 1.969
2 3.256 2.730 0.798 0.768 48.019 0.503
3 3.230 2.629 0.793 0.650 48.443 0.501
4 3.317 2.628 0.823 0.658 47.888 0.564
5 3.249 2.627 0.842 0.646 48.530 0.460
6 3.262 2.629 0.812 0.649 48.479 0.504

部分测试代码:

NSString *sqlStr = @"SELECT ...";
sqlite3 *database = nil;
if (sqlite3_open([DatabasePath UTF8String], &database) == SQLITE_OK) {
sqlite3_stmt *statement = nil;
int status = sqlite3_prepare_v2(database, [sqlStr UTF8String], -1, &statement, NULL);
if (status == SQLITE_OK) {
NSInteger j = 0;
while (sqlite3_step(statement) == SQLITE_ROW) {
j++;
NSMutableArray *row = [[NSMutableArray alloc] init];
for(int i = 0; i <10; i++){
[row addObject:[NSString stringWithFormat:@"%s", sqlite3_column_text(statement, i)]];
}
}
} else {
NSLog(@"Error: failed to prepare");
}
sqlite3_finalize(statement);
} else {
sqlite3_close(database);
NSAssert1(0, @"Failed to open database with message '%s'.", sqlite3_errmsg(database));
}
sqlite3_close(database);

五、知识拓展

1.如何添加索引

给某个表某列添加索引

CREATE INDEX Index_Name ON Table_Name (Column_Name);

给某个表添加唯一索引(不能有重复的记录)

CREATE UNIQUE INDEX Index_Name on Table_Name (Column_Name);

给某个表添加组合索引

CREATE INDEX Index_Name on Table_Name (Column_Name1, Column_Name2, ...);

2.可以给任意表添加索引么

使用索引时,应遵守的准则:

  • 索引不应该使用在较小的表上。
  • 索引不应该使用在有频繁的大批量的更新或插入操作的表上。
  • 索引不应该使用在含有大量的NULL值的列上。
  • 索引不应该使用在频繁操作的列上。

3.怎么使用索引

sqlite中(其它的没有做了解)不需要显式的用

SELECT ... FROM Table_Name INDEX BY ...

的语法去使用索引,假如你添加了索引,执行sql查询语句时会自动使用。

4.怎么看是否使用了索引

你可以使用这种方法查看你的sql语句是否使用了索引进行查询:

EXPLAIN QUERY PLAN SelectQueryString;

结果表样式

selectid order form detail

如果你建立了索引,执行查询语句时使用到了索引时,detail列中结果样式

SEARCH TABLE Table_Name USING INDEX sqlite_autoindex_Table_Name_n (Column_Name=?) (~1 rows)

六、小结

iOS9中部分sql的执行速度异常慢的问题应该是apple的一个BUG,目前我暂未联系apple确定,可以考虑的解决方案就是在数据库搜索本身的性能上下功夫,假如你也遇到类似的问题,而且发现了更好的解决方案,希望可以评论告诉我。

参考:
  • http://stackoverflow.com/questions/32781913/sqlite-running-some-queries-under-ios9-views-a-lot-slower-than-ios8
  • http://blog.csdn.net/yanbober/article/details/45581751

推荐阅读
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • 本文介绍了一些好用的搜索引擎的替代品,包括网盘搜索工具、百度网盘搜索引擎等。同时还介绍了一些笑话大全、GIF笑话图片、动态图等资源的搜索引擎。此外,还推荐了一些迅雷快传搜索和360云盘资源搜索的网盘搜索引擎。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
author-avatar
我叫巴吉度
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有