如何降低Realm数据库的崩溃

简介: 云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! Realm的崩溃,猝不及防,不仅仅是Realm,任何数据库导致的奔溃总是个难题,总有那么零星几个让人没有头绪的bug。

云栖号资讯:【点击查看更多行业资讯
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!


Realm的崩溃,猝不及防,不仅仅是Realm,任何数据库导致的奔溃总是个难题,总有那么零星几个让人没有头绪的bug。

本文提供了一个思路来解决Realm数据库崩溃问题

代码部分见重点内容,Java等其他平台也可参考。

谨记以下几点:

  • Realm的数据写入是同步阻塞的,但是读取不会阻塞
  • Realm托管的对象是不可以跨线程的,即不同线程是不可以修改彼此的对象的
  • Realm托管的对象的任何修改必须是在realm.write{} 中完成的
  • Realm 采用了 零拷贝 架构。
  • 尽量少使用写入事件少量事件,可以尝试批量写入更多数据
  • 将写入操作载入到专门的线程中执行。
  • 推迟初始化任何用到 Realm API 属性的类型,直到应用完成 Realm 配置。否则会崩溃。

官方明确的限制:

  • 类名称的长度最大只能存储 57 个 UTF8 字符。
  • 属性名称的长度最大只能支持 63 个 UTF8 字符。
  • Data 和 String 属性不能保存超过 16 MB 大小的数据
  • 每个单独的 Realm 文件大小无法超过应用在 iOS 系统中所被允许使用的内存量——这个量对于每个设备而言都是不同的,并且还取决于当时内存空间的碎片化情况(关于此问题有一个相关的 Radar:rdar://17119975)。如果您需要存储海量数据的话,那么可以选择使用多个 Realm 文件并进行映射。
  • 对字符串进行排序以及不区分大小写查询只支持“基础拉丁字符集”、“拉丁字符补充集”、“拉丁文扩展字符集 A” 以及”拉丁文扩展字符集 B“(UTF-8 的范围在 0~591 之间)。

Realm中多线程中的问题

一、跨线程修改数据

条件一: 线程A创建了对象xiaoming,并托管到realm中

条件二: 同时线程B创建了对象xiaomei,并托管到realm中

问:此时,我能从线程A中的直接修改线程B中创建的xiaomei吗?

不可以,对象一旦托管到realm中,修改其他线程中的Realm对象会导致崩溃

二、跨线程传输

官方实例:

1

Realm 提供了一个机制,通过以下三个步骤来保证受到线程限制的实例能够安全传递:

通过受到线程限制的对象来构造一个 ThreadSafeReference;

将此 ThreadSafeReference 传递给目标线程或者队列;

通过在目标 Realm 上调用 Realm.resolve(_:) 来解析此引用。

三、摆脱Realm数据托管,自由修改对象

这时我想修改xiaoming,假如把年龄从29 修改到了 28,我不希望立刻存到数据库中,因为我不确定 年龄为28是否正确,我想要临时修改,不让数据库托管,这时候怎么办?

答案是: 深拷贝 + 主键更新

2

3

Int、String、Float、Double、Bool、Date、Data、List、List、List、List、List、List、List、List等全部支持

四、如何实现不同线程 使用不同的Realm

4

五、上面的这个看似解决了问题,实际上会存在很大隐患。

项目中大部分relam奔溃的元凶就是这个了。

如主线程通过realm得到了xiaoming,子线程中获取的realm实例以及xiaoming和主线程是不同的,但这时在写入事件中对xiangming进行操作,还是会崩溃。

解决办法:

1.所有托管的对象利用上文提到的ThreadSafeReference,统一写入。

缺点:ThreadSafeReference 对象最多只能够解析一次。如果 ThreadSafeReference 解析失败的话,将会导致 Realm 的原始版本被锁死,直到引用被释放为止。因此,ThreadSafeReference 的生命周期应该很短。

2.对象的读写都确保在同一线程(包含realm实例,以及对象)

我建议获取realm实例和对象读写,放在同一线程中,那么如何保证同一线程呢?

  • 优先级低的数据操作考虑:GCD的异步串行队列 会开辟一条新的线程,可以利用这一点
  • 优先级高的数据操作考虑:主线程

重点内容:

深拷贝,主键更新

废话不多说,见代码:

import Foundation
import RealmSwift
class RealmManager{
static let shared = RealmManager()
private init() {
  _realmMain = try? Realm()
}
private var _realmMain: Realm?
public var realm: Realm? {
    get {
        if Thread.isMainThread {
            return _realmMain ?? (try? Realm())
        } else {
            return try? Realm()
        }
    }
}
/// 查询,返回的对象托管到realm中
func objects<Element: Object>(_ type: Element.Type) -> [Element] {
    var result = [Element]()
    realm!.objects(type).forEach { (element) in
        result.append(element)
    }
    return result
}
/// 查询,返回的对象托管到realm中
func object<Element: Object, KeyType>(ofType type: Element.Type, forPrimaryKey key: KeyType) -> Element? {
    return realm!.object(ofType: type, forPrimaryKey: key)
}

// MARK: - Safe operation 安全操作

/// 安全查询,返回的对象不托管到realm中
func safeQuery<Element: Object>(_ type: Element.Type) -> [Element] {
    var result = [Element]()
    realm!.objects(type).forEach { (element) in
        result.append(element.detached())
    }
    return result
}
/// 安全查询,返回的对象不托管到realm中
func safeQuery<Element: Object, KeyType>(ofType type: Element.Type, forPrimaryKey key: KeyType) -> Element? {
    return realm!.object(ofType: type, forPrimaryKey: key)?.detached()
}
/// 安全写入数据,保证不会出错
func safeWrite<T>(object:T) where T:Object {
    let newRealm = realm
    /// 深拷贝
    let obj = object.detached()
    if T.primaryKey() == nil{
        // 删除老数据,然后更新
        newRealm?.delete(object)
        try? newRealm?.write {
            newRealm?.add(obj)
        }
    }else{
        // 通过主键更新
        try? newRealm?.write {
            newRealm?.add(obj, update: .all)
        }
    }
    
}

}

上述代码中,我分别实现了

  • 普通查询
  • 安全查询
  • 普通写入
  • 安全写入

如果程序员能保证线程安全,使用普通查询,普通写入,不能保证时,至少实现一种安全操作,不必全部查询写入都使用安全操作。

还可以进行优化,如,只要是主线程获取的数据,做个标记,不去操作,不用进行深拷贝,写入时,直接操作。

六、其他注意事项

一、绕过App Store 出现的提交 bug

在应用目标的 “Build Phases” 中创建一条新的 “Run Script Phase”,然后将下面这段代码粘贴到脚本文本框内:

bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/strip-frameworks.sh"

二、多种数据库初始化的情况

let realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "MyInMemoryRealm"))

创建一个完全在内存中运行的 Realm 数据库 (in-memory Realm),它将不会存储在磁盘当中.

2、一般情况下,包含版本升级

do {
        _realm = try Realm(configuration: Realm.Configuration(schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in
            // 数据库迁移
            if (oldSchemaVersion < 2) {
                // 添加新字段
                migration.enumerateObjects(ofType: RealmTPPlanItemModel.className()) { oldObject, newObject in
                    newObject?["timeStyle"] = "EEEE"
                }
            }
        }))
    }catch{
        TPLog.log(error.localizedDescription)
    }

3、打包进项目里的数据库的使用

        let config = Realm.Configuration(
            fileURL: Bundle.main.url(forResource: "defaultAPP", withExtension: "realm"), readOnly: true, schemaVersion:2)
        // 通过配置打开 Realm 数据库
        let realm = try! Realm(configuration: config)
        return realm
    }

【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/live

立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK

原文发布时间:2020-04-29
本文作者:恶熊本二
本文来自:“掘金”,了解相关信息可以关注“掘金”

相关文章
|
7月前
|
消息中间件 关系型数据库 Kafka
实时计算 Flink版产品使用合集之使用DTS从RDSMySQL数据库同步数据到云Kafka,增量同步数据延迟时间超过1秒。如何诊断问题并降低延迟
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStreamAPI、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
7月前
|
Oracle 关系型数据库 数据库
实时计算 Flink版产品使用合集之Oracle归档日志一天就达到了15GB并导致数据库崩溃,是什么导致的
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
7月前
|
缓存 监控 安全
宝塔数据库崩溃解决方案详解
宝塔数据库崩溃解决方案详解
|
存储 SQL 缓存
如何在高性能的前提下,降低数据库存储成本?
如何在高性能的前提下,降低数据库存储成本?
294 0
如何在高性能的前提下,降低数据库存储成本?
|
SQL 缓存 NoSQL
MySQL 数据库崩溃(crash)的常见原因和解决办法---发表到 《数据和云》 公众号
Linux 系统中的 systemd 和 mysqld_safe 会在 mysqld 进程 crash 后自动重新启动 MySQL 的服务,需要注意的是使用 kill -9 杀死 mysqld 进程系统会自动重新启动,而只使用 kill 命令则不会重新启动
951 0
|
存储 NoSQL Linux
如何有效降低产品级内存数据库快照尾延迟
本文讲解内存键值对数据库在使用 fork 拍摄快照时引起的请求尾延迟激增问题如何解决的实践方案。
如何有效降低产品级内存数据库快照尾延迟
|
存储 运维 Cloud Native
客户案例|国泰产险引入阿里云Lindorm数据库,实现存储成本降低75%
日前,国泰财产保险有限责任公司(以下简称“国泰产险”)通过引入阿里云Lindorm数据库,在历史保单分析场景下,查询性能获得约70%提升,同时通过Lindorm深度优化的ZSTD压缩算法,存储效率进一步提升30%,整体综合成本下降75%。
客户案例|国泰产险引入阿里云Lindorm数据库,实现存储成本降低75%
|
弹性计算 资源调度 Kubernetes
双11特刊 | 全面云原生化,数据库实例独共享混部 最高降低30%成本
2021年双十一是阿里巴巴集团的核心应用全面云化的第二年。今年在保证稳定性的前提下,主要探索如何利用云原生的技术优势,降低成本,提升资源利用率。在今年大促中,针对核心集群采用独享共享实例混部,统一了底层资源,结合交易业务云盘化使得混部单元大促成本下降30%+。
579 0
双11特刊 | 全面云原生化,数据库实例独共享混部 最高降低30%成本
|
SQL 机器学习/深度学习 缓存
|
存储 搜索推荐 关系型数据库
别裁员!别裁员!别裁员! 一招降低企业数据库IT成本
别裁员!别裁员!别裁员! 一招降低IT数据库成本. 2020疫情无情,多数企业因此受挫,特别中小企业,甚至到了要裁员的地步, 但是人才是最宝贵的,裁员一定是下下策,如何渡过这个难关,疫情带给我们什么反思? 开源节流有新方法,通常数据库在企业IT支出中的占比将近一半,降低数据库成本对降低企业IT成本效果明显,但是一般企业没有专业DBA,很难在这方面下手,不过没关系,有了云厂商,一切变得简单。
2004 0
别裁员!别裁员!别裁员! 一招降低企业数据库IT成本