在Android上同时将数据保留在内存和数据库中的最佳实践

31
我们正在设计一款Android应用程序,其中包含大量数据("客户"、"产品"、"订单"等),我们不想每次需要某条记录时都查询SQLite。 我们希望尽可能避免频繁地查询数据库,因此决定始终将某些数据保存在内存中。
我们最初的想法是创建两个简单的类:
1. "MemoryRecord": 一个类,它基本上包含一个对象数组(字符串、整数、双精度、日期时间等),这些是来自表记录的数据,以及从该数组中获取/输出这些数据的所有方法。 2. "MemoryTable": 一个类,它基本上包含[键,MemoryRecord]映射和操纵此映射以及将记录插入/更新/从数据库中删除的所有方法。
这些类将派生到我们在数据库中拥有的各种类型的表中。 当然还有其他有用的方法,但在这一点上它们并不重要。
因此,在启动应用程序时,我们将使用这些类将这些表从SQLite数据库加载到内存中,并且每当我们需要更改某些数据时,我们将在内存中进行更改,然后立即将其发布到数据库中。
但是,我们希望您提供帮助/建议。 您能否建议实现此类事情更简单或更有效的方法? 或者已经为我们执行此操作的现有类?
我理解您们正在尝试向我展示什么,并为此表示感谢。
但是,假设我们有一张表包含2000条记录,我需要列出这些记录。 对于每个记录,我必须查询其他30个表(其中一些具有1000条记录,其他具有10条记录)以在列表中添加其他信息,而此时它正在"飞行"(如您所知,在此刻我们必须非常快)。
现在你可能会说:"只需使用所有这些'连接'构建主查询,并在一个步骤中带来所有需要的内容。 如果您的数据库设计良好等,则SQLite可以非常快..."。

好的,但是这个查询将变得非常复杂,即使SQLite非常快,它也会变得“太”慢(我已经确认为2到4秒,这对我们来说是不可接受的)。

另一个麻烦是,根据用户的交互,我们需要“重新查询”所有记录,因为涉及的表不同,我们必须与另一组表“重新连接”。

因此,另一种选择是仅带有主要记录(这永远不会改变,无论用户做什么或想要什么),不进行连接(这非常快!),并在每次需要一些数据时查询其他表。请注意,在只有10条记录的表中,我们将多次抓取相同的记录。在这种情况下,这是浪费时间,因为无论SQLite有多快,查询、游标、抓取等操作总是比从某种“内存缓存”中获取记录更昂贵。我想澄清的是,我们不打算始终在内存中保留所有数据,只是某些我们经常查询的表。

然后我们来到了最初的问题:最佳的“缓存”这些记录的方法是什么?我真的很想专注于这个讨论而不是“为什么需要缓存数据?”


2
我们希望尽可能避免频繁查询数据库,因此决定将某些数据始终保留在内存中。-- 你是否使用Traceview确认了这是应用程序的问题?"我们需要您的帮助/建议"-- 我的建议是:首先证明存在问题。你可用的RAM非常少。构建一个大型框架来处理不存在的问题将是浪费精力。如果您已经证明这是一个问题,我很想看到您写的博客文章,因为我一直对性能测试结果感兴趣。 - CommonsWare
@CommonsWare:我理解你的观点并完全同意。但是,作为PalmOS和.NetCF开发人员,我们之前已经面临过这个问题。在PalmOS中,所有数据都是按设计存储在内存中(.pdb),获取数据时没有性能问题。另一方面,在WM中,我们遇到了“问题”,然后我们创建了上面列出的“解决方案”。但现在,在Android中,我们希望以“正确的方式”处理。我们想知道每次查询数据库时是否会遇到性能问题。因此,我们决定在这里寻求建议。无论如何,谢谢。 - Christian
2
我们想知道每次查询数据库时是否会遇到性能问题。因此,我们决定在这里寻求建议。不,你没有这样做。我希望你这样做了。相反,你宣布存在一个问题(“我们不想每次需要一些记录时都查询sqlite”),并且你想要关于你的解决方案的帮助。与大多数平台一样,“查询数据库时是否会遇到性能问题”这个问题的答案是“取决于查询”。相信我,仅仅因为WM上存在问题并不意味着其他地方也存在同样的问题。使用Traceview。 - CommonsWare
你不需要在滚动时查询数据库。你可以在活动开始时查询所有需要的数据。 - Dmitry Ryadnenko
根据您的编辑,看起来您“需要”一个复杂的设计,其中包括缓存和/或内存数据库,因为您个人认为执行连接操作太过复杂。在这两种选择中,我会坚持(更高效的)最佳实践并正确编写我的查询,使用join语句。我曾经做过类似的事情,一旦重构了代码并编写方法来帮助生成查询并从游标中填充对象,它就不会太难以管理。虽然,如果有人决定使用内存数据库,SQLiteOpenHelper提供将null作为数据库文件名的选项。 - spaaarky21
当你有一个复杂的设计时,你可以创建一个View,以便更容易地进行简单查询。(http://www.sqlite.org/lang_createview.html)尽管在SQLite中视图是只读的,但它仍然非常有用。将其视为具有计算列的表,但完全与原始数据表保持最新。 - Jelle
2个回答

69
这个平台上的大多数应用程序(联系人、电子邮件、Gmail、日历等)都不会这样做。其中一些具有极其复杂的数据库模式,可能具有大量数据,并且不需要这样做。您建议要做的事情将会给您带来巨大的痛苦,而没有明显的好处。
你应该首先专注于设计能够进行高效查询的数据库和模式。我能想到的数据库访问速度缓慢的两个主要原因是:
- 您具有非常复杂的数据模式。 - 您具有大量的数据。
如果您将拥有大量数据,则无论如何都无法承受将其全部存储在内存中,因此这是一条死路。如果您具有复杂的结构,在任何情况下进行优化都会使性能得到改进。在两种情况下,您的数据库模式将是良好性能的关键。
实际上,优化模式可能有点黑科技(我也不是专家),但您需要注意的一些问题是:在要查询的行上正确创建索引,设计联接以便采取有效路径等。我相信有很多人可以帮助您解决这个问题。
您还可以尝试查看一些平台数据库的源代码,以获得如何设计良好性能的想法。例如,联系人数据库(特别是从2.0开始)非常复杂,并且具有许多优化以在相对较大的数据和具有许多不同查询类型的可扩展数据集上提供良好性能。
更新:
以下是优化数据库的重要性的很好说明。在Android的媒体提供程序数据库中,平台的新版本显着改变了模式以添加一些新功能。将现有的媒体数据库修改为新模式的升级代码可能需要8分钟或更长时间来执行。
工程师进行了优化,将实际测试数据库的升级时间从8分钟缩短到8秒。性能提高了60倍。
这个优化是什么?

这是为了在升级操作中使用的一个重要列上创建临时索引(然后在完成后删除它)。因此,这种60倍的性能提升即使包括了在升级期间构建索引所需的时间。

SQLite是一种非常高效的技术,如果你知道该如何使用的话。但如果你不注意如何使用它,可能会导致糟糕的性能表现。如果你在使用SQLite时遇到性能问题,通过改进SQLite的使用方式可以解决这个问题。


5
使用内存缓存的问题在于您需要将其与数据库保持同步。我发现查询数据库实际上非常快,您可能会过度优化。我对具有不同数据集的查询进行了大量测试,它们从未超过10-20毫秒。
当然,这完全取决于您如何使用数据。ListViews已经非常优化,可以处理大量行(我已经测试了5000多个,没有真正的问题)。
如果您要继续使用内存缓存,您可能希望在数据库更改其内容时通知缓存,然后更新缓存。这样,任何人都可以更新数据库而不必了解缓存。此外,如果在数据库上构建ContentProvider,则可以使用ContentResolver在注册ContentObserver时通知您更改。

我很高兴知道你所说的那些统计数据。我相当确定我们正在进行预优化,但这是因为我们过去在SQLite和移动设备(WM)方面的经验。内存和数据库之间的同步不是我们担心的问题,因为我们在更改某些内容的同时会将其发布到数据库中。缓存将是全局的,因此所有应用程序都将看到相同的内容。所有更改都将首先在内存中进行,然后在数据库中进行(在这些类中实现了“原子”操作)。 - Christian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接