草庐IT

ios - Sqlite 在 iOS 上的简单(但大)表上随机减慢速度

coder 2023-07-21 原文

我正在为 iPad 开发一个企业销售应用程序,它使用 Sqlite 作为其内部数据库,最近出现了一个奇怪的行为。

我有一个巨大的表格,里面装满了来自其他几个表格的信息(有点像“物化 View ”),它可以包含超过 200 万行,具体取决于用户的设置方式。当用户想要搜索一个项目时,应用程序会在这个具有索引列的巨大表和用作过滤器和/或元数据的其他列上执行查询。我将在下面发布查询和基本思想。无论如何,这个查询通常在第 4 代 iPad 上在 2~3 秒内返回,仅此而已,这很好。每次用户点击按钮以将其数据与我们的服务器同步时,该表都会被删除、重新创建和填充。

但是,最近在同一个表中的相同查询(根本没有相关更改),随机开始需要40~50秒。如果您稍后在同一台设备上使用相同的过滤器(甚至更改过滤器!)执行相同的操作,则同一表上的相同查询将再次花费 2 到 3 秒。我没有发现任何导致这种速度变慢的特定情况,该应用程序是当时唯一运行的应用程序。设备不是问题,我们已经看到这种情况发生在至少 5 款不同的 iPad 上,一个是 iPad 3,其他的是 iPad 4。

我不认为这是某种缓存,因为该应用程序不缓存任何内容,而且这些时间是相当随机的。有时他们连续 10 次需要 40 秒,然后突然又开始只需要 2 秒,反之亦然。我唯一清楚的是,这种减速只发生在密集使用后(使用该应用程序工作 1 - 2 天),所以我在我随身携带的 iPad 上调试时也遇到了导致这种行为的麻烦。

我试过的:

  • 将仪器附加到进程并检查在减速期间正在使用哪些资源。该应用程序在整个时间内密集使用 iPad 的“磁盘”(闪存)。我现在没有例子再分析它,但我认为CPU使用率在30%左右。 RAM使用量稳定在90~100MB,这对于我们的应用程序来说是正常的。
  • 在数据库上运行 VACCUM; - 在我作为示例的数据库上减少了约 50MB。从~600MB 到~550MB。
  • 在数据库上运行 ANALYZE; - 没有看到任何改进
  • 在数据库上运行 REINDEX; - 似乎有点帮助,但它并没有解决问题。
  • 终止进程并重新开始 - 没有任何变化

  • 巨大的表构造如下,并且没有任何外键或其他任何其他约束:

    CREATE TABLE FMV_CATALOG(
        UNIQUE_ID TEXT,
        PRODUCT_ID INTEGER,
        <bunch of metadata/filtered columns - total of 20 columns>
    );



    用于查找产品的查询是:

    SELECT
        PRODUCT_ID
        ,UNIQUE_ID
        <all other required columns, ~20 columns>
    FROM
        FMV_CATALOG
    WHERE
        UNIQUE_ID = '<some id>_<other id>'
    AND PRODUCT_NAME LIKE '%iPhone%'
    <and other optional, rarely used, filters.>



    我完全没有想法,所以任何帮助将不胜感激。
    谢谢!

    更新(更多信息):

    之前忘记提及的重要信息,罗布提醒了我。我的数据库连接始终打开,仅在用户注销时关闭。当我们保持连接打开时,我们注意到应用程序的所有部分都有巨大的性能,因为我们有数百个在其他情况下执行的小查询(但不是在浏览/搜索产品目录时)。

    用于创建索引的查询如下:

    CREATE INDEX IDX_MV_CATALOG ON MV_CATALOG(UNIQUE_ID);



    此外,即使列名为 UNIQUE_ID,它也不是唯一的。本来应该是这样的,现在重复了N次。我知道这是错误的,我们会尽快更改。

    这个“UNIQUE_ID”(不是真正唯一的)是通过连接另外两个表的 ID 来填充的。这样,当用户在我们的目录中搜索时,我们的“物化 View ”消除了至少三个连接的需要,这将我们的查询时间从约 20 秒缩短到约 2 秒。

    我们不直接在查询中调用 sqlite3 API,我们围绕它开发了一个包装类,我们已经使用它至少 2 年了。这是我们有史以来第一次遇到这种情况,但这也是我们第一次处理如此多的数据。

    最佳答案

    一些想法:

  • 您没有向我们展示在 FMV_CATALOG 上创建的任何索引.如果不出意外,如果 UNIQUE_ID顾名思义,是唯一的,那么我倾向于用 PRIMARY KEY 来定义表。 :
    CREATE TABLE FMV_CATALOG(
        UNIQUE_ID TEXT PRIMARY KEY,
        PRODUCT_ID INTEGER,
        <bunch of metadata/filtered columns - total of 20 columns>
    );
    
  • 您应该尝试使用 SQLite EXPLAIN QUERY PLAN 命令查看查询并查看其计划并确保它正在利用您的索引。按原样执行此操作,然后再次使用 PRIMARY KEY (也许如果这仍然没有做到,在您的 WHERE 子句中的字段上建立索引),并确保最终查询肯定使用您的索引。
  • 我不知道为什么,如果你有唯一的 id,为什么你还要查看其他字段。如果添加主键(可能还有其他索引)不能解决问题,我可能会尝试仅根据唯一 ID 检索记录,然后检查与代码中其他参数的一致性。我认为您不需要这样做,但这是最坏的情况。

  • 就它为什么会变慢而言,如果不看代码就更难猜测发生了什么(我确信这太复杂了,无法在一个简单的 S.O. 问题中分享)。我可以想象奇怪的行为,例如,如果你没有 sqlite3_finalize在您的一个 sqlite3_prepare_v2 之后语句,或者如果您不小心关闭数据库失败,然后在其他地方再次打开它。我可以想象如果 sqlite3 的序列可能会出现性能问题。电话不完全正确。使用类似 FMDB 的东西可以最大限度地减少发生此类问题的机会(以及简化您的 SQLite 代码)。或者,如果这一步过于激进,请尝试编写自己的宏来调用 SQLite 调用,但还要记录您调用了 sqlite3 的事实。函数,然后翻阅该日志并仔细检查 SQLite 调用的顺序。

    我唯一能建议的是您是否可以构建一个可以重现异常行为的简化项目。追踪 Heisenbug可能会令人气愤:除非您可以持续重现该错误,否则很难追踪。

    关于ios - Sqlite 在 iOS 上的简单(但大)表上随机减慢速度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18792132/

    有关ios - Sqlite 在 iOS 上的简单(但大)表上随机减慢速度的更多相关文章

    1. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

      我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

    2. ruby-on-rails - openshift 上的 rails 控制台 - 2

      我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新ruby​​gems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems

    3. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

      我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

    4. ruby - 简单获取法拉第超时 - 2

      有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url

    5. ruby - 如何验证 IO.copy_stream 是否成功 - 2

      这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

    6. Ruby 文件 IO 定界符? - 2

      我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

    7. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

      我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

    8. ruby-on-rails - 简单的 Ruby on Rails 问题——如何将评论附加到用户和文章? - 2

      我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。

    9. ruby - 使用 Ruby 通过 Outlook 发送消息的最简单方法是什么? - 2

      我的工作要求我为某些测试自动生成电子邮件。我一直在四处寻找,但未能找到可以快速实现的合理解决方案。它需要在outlook而不是其他邮件服务器中,因为我们有一些奇怪的身份验证规则,我们需要保存草稿而不是仅仅发送邮件的选项。显然win32ole可以做到这一点,但我找不到任何相当简单的例子。 最佳答案 假设存储了Outlook凭据并且您设置为自动登录到Outlook,WIN32OLE可以很好地完成此操作:require'win32ole'outlook=WIN32OLE.new('Outlook.Application')message=

    10. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

      华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

    随机推荐