17、Core Data 存储、性能分析与关系数据库基础

Core Data 存储、性能分析与关系数据库基础

1. Core Data 存储过程

在保存数据时,Core Data 会执行一系列操作。首先,它会从 Z_PRIMARYKEY 表中获取当前的最大主键,并通过后续的 UPDATE 语句对其进行递增。这两条 SQL 语句会被包装在一个事务( BEGIN EXCLUSIVE COMMIT )中,以确保同一时间没有其他操作写入数据库。接着,新的情绪数据会被插入,并且相应区域的 updatedAt 属性会被更新。同样,这两条语句也会被包装在一个事务中。

与获取请求不同,SQL 调试输出不会告诉我们保存请求执行所需的时间。不过,我们可以使用 Core Data 工具来获取这些信息。

2. Core Data 工具

Core Data 工具提供了一些特殊的工具,可用于分析应用程序的持久化性能。你可以将这些 Core Data 工具与其他工具(如时间分析器、内存分配器或 I/O 活动工具)结合使用,以记录应用程序的全面性能分析。

预定义的 Core Data 模板包括获取、保存和缓存未命中工具。此外,你还可以从工具库中选择另一个工具:故障工具。

2.1 工具提供的指标

Core Data 工具提供了以下指标:
- 获取(Fetches) :你可以确切地看到获取请求发生的频率、返回的对象数量以及所需的时间。
- 保存(Saves) :与获取请求类似,保存工具显示保存操作发生的频率以及每次保存所需的时间。
- 故障(Faults) :故障工具显示 Core Data 必须满足的对象和关系故障。在详细视图中,你可以看到正在满足的故障的对象 ID 和关系名称,以及这些操作的持续时间。
- 缓存未命中(Cache Misses) :在缓存未命中跟踪中,你可以看到有多少触发的故障必须从 SQLite 中获取,因为数据尚未存在于存储的行缓存中。与故障工具一样,缓存未命中会分别报告对象和关系故障。在详细视图中,你可以看到哪些对象导致了这些缓存未命中,以及往返 SQLite 所需的时间。

对于 Core Data 工具记录的所有事件,你可以在扩展详细视图中检查调用堆栈。这将告诉你所选事件源自代码的哪个部分。

2.2 示例分析

以 Moody 示例应用程序为例,启动应用程序后记录的跟踪显示,第一个获取请求是支持情绪表视图的获取结果控制器的初始获取。然后,当我们向下滚动表视图时,会触发更多的获取请求,因为会加载新的数据批次。

有趣的是故障和缓存未命中这两个跟踪。可以看到有很多故障正在被满足,详细视图显示这些故障是国家对象。Core Data 必须从 SQLite 中检索大量这些故障的数据,这在缓存未命中跟踪中的线条与上方故障跟踪中的线条相匹配时得到了体现。频繁访问 SQLite 可能会在滚动过程中导致明显的卡顿。

2.3 解决方案

为了解决这个问题,我们可以对数据模型进行非规范化处理,即将国家代码直接存储在情绪实体中。但在这种情况下,我们将采取另一种方法,即预取情绪表视图所需的国家对象。当我们想要显示所有情绪时,只需预取所有国家对象(因为我们知道国家的最大数量相当小)。以下是实现预取的代码:

extension MoodSource {
    func prefetch(in context: NSManagedObjectContext)
        -> [MoodyModel.Country]
    {
        switch self {
        case .all:
            return MoodyModel.Country.fetch(in: context) { request in
                request.predicate = MoodyModel.Country.defaultPredicate
            }
        // ...
        }
    }
}

预取后,缓存未命中跟踪中的所有线条都消失了。虽然仍然有很多国家对象故障正在被满足,但这些对象的数据已经被加载到行缓存中。

3. 线程保护

类似于 SQL 调试启动参数,从 iOS 8 和 macOS 10.10 开始,你可以指定另一个参数: -com.apple.CoreData.ConcurrencyDebug 1 。使用这个参数运行应用程序有助于调试线程问题:每当你从错误的队列访问托管对象或托管对象上下文时,Core Data 都会抛出异常。

4. 关系数据库基础

4.1 嵌入式数据库

Core Data 的默认存储是 SQLite 存储。SQLite 是一个嵌入式数据库,它运行在应用程序内部,没有单独的数据库进程供应用程序连接。Core Data 使用结构化查询语言(SQL)与数据库 API 进行通信。当 Core Data 想要数据库执行某些操作(如检索数据或修改数据)时,它会生成一个 SQL 语句,然后将该字符串发送到 SQLite API。

SQLite 解析 Core Data 发送的 SQL 语句并执行它们。反过来,SQLite 库会在文件系统中读取或写入数据库。与一些独立运行的 SQL 数据库不同,SQLite 是应用程序的一部分,没有单独的进程。

4.2 表、列和行

关系数据库中的数据组织成表。表也称为关系,因此关系数据库是基于表的数据库。表中的数据进一步组织成列,每行是表中的一个条目。

关系数据库有一个所谓的模式,它描述了数据库有哪些表以及每个表有哪些属性或列。数据只能根据定义的模式进行存储。在使用 SQL 时,通常会为表添加一个所谓的主键属性,Core Data 也是如此。这个属性(通常是一个整数)唯一标识每一行。当插入新行时,数据库会自动为新行的主键分配一个新的(递增的)值。

4.3 数据库系统架构

典型的数据库系统可以分为四个组件:
- 查询处理器(Query Processor) :处理 SQL 语句,将其转换为对存储数据执行的一系列请求和操作。它的一个重要任务是优化查询,创建一个查询计划,描述检索数据的最佳方式。
- 存储管理器(Storage Manager) :负责数据在文件系统中的存储、页面缓存使用的内存以及两者之间的交互。
- 事务管理器(Transaction Manager) :确保数据库的完整性。SQLite 是一个事务性数据库,通过依赖一组称为 ACID 的属性,确保所有更改和查询看起来是原子的、一致的、隔离的和持久的。Core Data 使用事务来确保对上下文的 save(_:) 方法的调用也是事务性的。
- 数据和元数据(Data & Metadata) :数据库的最后一个组成部分是数据和元数据,即存储的内容。实际数据、数据库的内容以及元数据(如数据库模式和索引)都持久化在文件系统中。

4.4 SQL 语言

SQL 是用于与数据库交互的语言。以下是一些简单的 SQL 查询示例:

-- 查询 1994 年的电影
SELECT id, title, year FROM Movie WHERE year = 1994;
-- 查询 2000 年之前的电影的 id
SELECT id FROM Movie WHERE year < 2000;
-- 查询 id 为 3 的电影
SELECT id, title, year FROM Movie WHERE id = 3;

我们还可以使用 ORDER BY 对查询结果进行排序:

-- 按年份对 1990 年之后的电影进行排序
SELECT id, title, year FROM Movie WHERE year > 1990 ORDER BY year;

对于大型数据集,我们可以为表的给定属性添加索引,以提高排序或搜索的效率。当在获取请求中设置排序描述符时,Core Data 会在生成的 SELECT 语句中添加相应的 ORDER BY 子句。

4.5 关系的实现

关系数据库中有多种实现关系的方式,下面介绍三种常见的关系:
- 一对一(One-To-One) :可以基于 id 字段创建一对一关系。例如,为了让每部电影都有一个标题图像,我们可以在 Image Movie 表中分别添加 titleImage titleImageOf 列。同时,需要确保在更新或删除任一表中的条目时,另一方也会相应更新。
- 一对多(One-To-Many) :如果要将多个 Image 行与单个 Movie 关联起来,在 Movie 行中添加对所有相关 Image 行的反向引用是不可行的。相反,我们可以在每个 Image 行中添加一个 movie 属性。通过以下查询可以轻松找到给定 Movie 行的所有相关 Image 行的 id

SELECT id FROM Image WHERE movie == 2;
  • 多对多(Many-To-Many) :多对多关系不能通过在现有表中添加属性来实现,因此需要创建另一个表。例如,为了实现电影和导演之间的多对多关系,我们可以创建一个 Director 表。

综上所述,在优化 Core Data 代码之前,诊断性能瓶颈非常重要。通过使用 SQL 调试启动参数和 Core Data 工具,我们可以获得精确的性能分析指标,并深入了解 Core Data 在幕后的操作。如果在不同队列上使用多个上下文,并发调试启动参数可以在遇到线程问题时节省大量调试工作。同时,了解关系数据库的基础知识有助于我们更好地理解 Core Data 的工作原理。

5. 关系实现的详细示例

5.1 一对一关系示例

为了更清晰地展示一对一关系的实现,我们来看一个具体的表格示例。假设有 Movie 表和 Image 表,为了建立一对一关系,我们在两个表中分别添加相应的列。

Movie
id title year titleImage
2 12 Angry Men 1957 1
Image
id url width height titleImageOf
1 http://www.imdb.com/images/12.jpg 67 98 2

在这个示例中, Movie 表中的 titleImage 列指向 Image 表中的 id ,而 Image 表中的 titleImageOf 列指向 Movie 表中的 id 。当我们更新或删除其中一个表的条目时,需要同步更新另一个表的对应信息。例如,如果删除 Image 表中 id 为 1 的行,就需要将 Movie 表中对应电影的 titleImage 属性置空或删除。

5.2 一对多关系示例

接下来看一对多关系的示例。假设我们要将多个 Image 行与单个 Movie 关联起来,我们在 Image 表中添加 movie 属性。

Image
id url width height movie
1 http://www.imdb.com/images/12-a.jpg 67 98 2
2 http://www.imdb.com/images/12-b.jpg 67 94 2
3 http://www.imdb.com/images/12-c.jpg 67 94 2
4 http://www.imdb.com/images/CoG-a.jpg 72 102 1

通过这个表结构,我们可以很容易地找到给定 Movie 行的所有相关 Image 行的 id 。例如,要找到 movie 为 2 的所有 Image 行的 id ,可以使用以下 SQL 查询:

SELECT id FROM Image WHERE movie == 2;

5.3 多对多关系示例

多对多关系需要创建一个额外的表来实现。假设我们有 Movie 表和 Person 表,要实现电影和导演之间的多对多关系,我们创建 Director 表。

Person
id name
1 Sidney Lumet
2 Kátia Lund
3 Frank Darabont
4 Fernando Meirelles
Director
movie director
1 2
1 4
2 1

通过 Director 表,我们可以清晰地表示电影和导演之间的多对多关系。例如,电影 id 为 1 的有导演 id 为 2 和 4 的两位导演。

6. 数据库操作流程总结

6.1 保存数据流程

下面是 Core Data 保存数据的流程,使用 mermaid 流程图表示:

graph TD;
    A[开始保存] --> B[从 Z_PRIMARYKEY 表获取最大主键];
    B --> C[递增主键并更新];
    C --> D[开启事务];
    D --> E[插入新的情绪数据];
    E --> F[更新相应区域的 updatedAt 属性];
    F --> G[提交事务];
    G --> H[结束保存];

6.2 查询数据流程

查询数据时,SQL 语句会经过以下处理流程:

graph TD;
    A[发送 SQL 语句] --> B[查询处理器接收];
    B --> C[优化查询,创建查询计划];
    C --> D[根据查询计划执行操作];
    D --> E[存储管理器处理数据存储和检索];
    E --> F[返回查询结果];

7. 总结

7.1 Core Data 性能优化要点

  • 性能诊断 :在优化 Core Data 代码之前,使用 SQL 调试启动参数和 Core Data 工具来诊断性能瓶颈。通过这些工具,我们可以获取精确的性能分析指标,了解 Core Data 在幕后的操作,如获取请求的频率、保存操作的时间、故障和缓存未命中情况等。
  • 数据预取 :当遇到频繁访问 SQLite 导致的性能问题时,可以考虑预取相关数据。例如,在 Moody 示例应用程序中,预取情绪表视图所需的国家对象,避免了频繁从 SQLite 中获取数据,减少了缓存未命中,提高了滚动的流畅性。
  • 线程保护 :使用 -com.apple.CoreData.ConcurrencyDebug 1 参数运行应用程序,有助于调试线程问题。当从错误的队列访问托管对象或托管对象上下文时,Core Data 会抛出异常,帮助我们及时发现和解决线程相关的问题。

7.2 关系数据库知识要点

  • 数据库结构 :关系数据库中的数据组织成表,表由列和行组成。每个表有一个模式,描述了表的属性和结构。同时,通常会为表添加主键属性,用于唯一标识每一行。
  • 数据库组件 :典型的数据库系统由查询处理器、存储管理器、事务管理器和数据与元数据组成。查询处理器负责处理和优化 SQL 查询,存储管理器负责数据的存储和检索,事务管理器确保数据库的完整性,数据和元数据则是存储的具体内容。
  • SQL 操作 :SQL 是与数据库交互的语言,常见的操作包括查询、排序和处理关系。通过 SELECT FROM WHERE 等关键字进行查询,使用 ORDER BY 进行排序。在处理关系时,有一对一、一对多和多对多三种常见关系,分别通过不同的表结构和操作来实现。

通过深入了解 Core Data 的存储、性能分析以及关系数据库的基础知识,我们可以更好地开发和优化使用 Core Data 的应用程序,提高应用程序的性能和稳定性。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值