【问题标题】:Android Room. Which method of insertion will be faster?安卓室。哪种插入方法会更快?
【发布时间】:2018-05-17 06:26:48
【问题描述】:

我使用 Room 作为 SQLite 上的抽象层。在阅读了这个page之后,我发现我们可以同时插入多个对象。目前我使用 For 循环来插入对象,即每个 For 循环迭代中的一个对象。我目前知道的两种插入方式是:

  1. 使用 For 循环并一次插入每个对象

    @Insert(onConflict = OnConflictStrategy.REPLACE) public void addActivity(Person person);

  2. 插入一个数组或对象列表。

    @Insert(onConflict = OnConflictStrategy.REPLACE) public void insertUsers(Person ... people);

我在编写对象插入的代码时,不知道第二种插入方式。现在我想知道这两种方式之间的速度是否有明显差异,以便我可以更改代码以提高应用程序的性能。

【问题讨论】:

  • 您可以通过在数据库中使用每种方法插入 10k 个项目并对其进行计时来轻松测试它。我认为批量插入会更高效
  • 我对一组 12k 项进行了测试,for-looping 需要 55 秒才能保存,而使用准备好的语句方法在 600 毫秒内保存了所有内容。我认为没有理由不使用第二种方法。
  • @BenoitDuffez 嘿伙计,感谢您的努力。这是一个有趣的观察。请考虑将此评论添加为答案,以便其他人可以看到。

标签: android sqlite sql-insert android-room android-architecture-components


【解决方案1】:

按照 OP 在对他们问题的评论中的要求,以下是(为了清楚起见,作为答案)我为检查性能所做的工作:

之前,一个一个地插入对象:

@Dao
abstract class MyDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    abstract fun insert(items: MyObject): Long
    // ...
}

同步代码:

val response = backend.downloadItems() // download from server
val items = response.getData() // this is a List<MyObject>
if (items != null) {
    for (i in items) {
        myDao.persist(s)
    }
}

这在华为 P10+ 上花了一分钟。

我把它改成了:

@Dao
abstract class MyDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    abstract fun insert(items: Iterable<MyObject>)
    // ...
}

同步代码:

val response = backend.downloadItems() // download from server
val items = response.getData() // this is a List<MyObject>
response.getData()?.let { myDao.insert(it) }

这不到一秒钟。

这里的重点是专门使用Iterable&lt;&gt; 版本的DAO @Insert 方法,正如@iDemigod 所说,使用Iterable&lt;&gt; 版本的EntityInsertionAdapter

上述函数的主体在@iDemigod 的回答中,它对所有插入使用一个准备好的语句。

将 SQL 解析为语句是昂贵的,并且使用语句会为整个插入批处理创建一个事务,这可以帮助解决其他问题(我在数据库上有一个可观察到的 LiveData&lt;&gt;,在此期间被通知了 12k 次插入...性能糟糕)。

【讨论】:

  • 这是正确的答案,我的计算也得出了同样的结论。
  • 我有 10300 个对象,我从 json 文件加载并将它们插入房间。通过此更改,插入时间从 55 秒变为 3 秒。正是我需要的。谢谢大佬
【解决方案2】:

在这种特殊情况下,Room 生成的类在后台使用EntityInsertionAdapter。并且有两种方法,我们需要检查:

  1. 这个用于插入单个实体

     public final long insertAndReturnId(T entity) {
        final SupportSQLiteStatement stmt = acquire();
        try {
            bind(stmt, entity);
            return stmt.executeInsert();
        } finally {
            release(stmt);
        }
    }
    
  2. 虽然这个是用来插入实体数组的

    public final void insert(Iterable<T> entities) {
        final SupportSQLiteStatement stmt = acquire();
        try {
            for (T entity : entities) {
                bind(stmt, entity);
                stmt.executeInsert();
            }
        } finally {
            release(stmt);
        }
    }
    

您可以看到内部结构与您的几乎相同 - stmt.executeInsert(); 被调用一次或在循环中调用。我能想到的使用insertUsers 方法的唯一性能变化是更改通知,它只会发生一次,当所有用户都将被插入时。但如果你已经在用@Transaction 包裹的循环中插入,那么就不会有任何变化。

【讨论】:

  • 只调用一次acquire()有什么性能提升吗?
  • 是的,因为它在内部每次调用mOpenHelper.getWritableDatabase().compileStatement(sql); acquire()。我不确定它是否重要,因为它只是将 sql 语句从 String 转换为 SupportSQLiteStatement 对象。但是如果性能是优先考虑的,它肯定会改善整个情况:)
  • 是的,所以我尝试了两种方法的插入,发现通过数组插入快了 10 倍。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-15
  • 1970-01-01
  • 2019-11-22
  • 1970-01-01
  • 1970-01-01
  • 2021-08-03
  • 1970-01-01
相关资源
最近更新 更多