Android java: OutOfMemory при вставке огромного списка в базу данных SQL
Я просто работаю над проектом Android, где используется ORM greendao. Это позволяет мне добавлять несколько объектов (которые, очевидно, являются объектами) в базу данных одновременно (за одну транзакцию). На практике есть метод
insertOrReplaceInTx(...)
который принимает в качестве параметра коллекцию, учитывая список объектов. Проблема в том, что мой список содержит 12000 объектов, и вставка время от времени приводит к исключению OutOfMemory. Я думаю об умном способе решить проблему и предотвратить будущее ООМ. Единственная идея, которая приходит мне в голову, - это разбить огромную коллекцию на подколлекции (скажем, по 500 элементов в каждой) и зафиксировать в цикле, делая несколько небольших коммитов вместо одного огромного. Плохо то, что для вставки записей требуется больше времени, а время здесь имеет значение. В конце концов, я не уверен, что выполнение коммита после коммита все равно не убивает кучу. Возможно, вызовите метод sleep() между ними, чтобы GC очистил кучу... но это выглядит для меня как некрасивое решение.
какие-нибудь умные идеи на вашей стороне? заранее спасибо
1 ответ
Я работаю над проектом с использованием greendao 1.3.1. Некоторые из таблиц, содержащих около 200000 сущностей (не содержат много свойств).
Я прочитал сущности из csv и, чтобы ускорить процесс, разработал небольшое решение, которое также могло бы помочь с вашей OOM-проблемой.
Для объяснения:
greendao использует кеш и после каждой вставки обновляет сущность, чтобы получить идентификатор строки, и, возможно, вставляет сущность в ее кеш. Вдобавок к этому greendao запускает транзакцию, если вы вызываете метод вставки или обновления, и если транзакция еще не выполнена. Это замедляет "массовые" вставки и увеличивает использование памяти, а также снижает скорость.
Что я сделал:
Производительность (время)
Чтобы закрепить все, я начал транзакцию, прежде чем вставлять. Таким образом, greendao не будет запускать транзакцию для каждой вставки, и все вставки и обновления находятся в одной транзакции, что имеет дополнительные преимущества в отношении согласованности данных. Вы можете использовать код как это:
SQLiteDatabase db = dao.getDatabase();
db.beginTransaction();
try {
// do all your inserts and so on here.
db.setTransactionSuccessful();
} catch (Exception ex) {
} finally {
db.endTransaction();
}
Но это еще не поможет вам с вашей OOM-проблемой.
Использование памяти
Решение 1
Если вы не хотите связываться с greendao-кодом, вы можете выдать DaoSession.clear()
время от времени. Это, безусловно, простое решение, но оно будет менее производительным, чем решение 2.
Решение 2
Чтобы greendao не мог обновить и вставить объект в кеш, вы можете заменить метод private long executeInsert(T entity, SQLiteStatement stmt)
с этим кодом в AbstractDao.java:
/**
* Insert an entity into the table associated with a concrete DAO.
*
* @return row ID of newly inserted entity
*/
public long insertOrReplace(T entity, boolean update) {
return executeInsert(entity, statements.getInsertOrReplaceStatement(), update);
}
private long executeInsert(T entity, SQLiteStatement stmt) {
return executeInsert(entity, stmt, true);
}
private long executeInsert(T entity, SQLiteStatement stmt, boolean update) {
long rowId;
if (db.isDbLockedByCurrentThread()) {
synchronized (stmt) {
bindValues(stmt, entity);
rowId = stmt.executeInsert();
}
} else {
// Do TX to acquire a connection before locking the stmt to avoid deadlocks
db.beginTransaction();
try {
synchronized (stmt) {
bindValues(stmt, entity);
rowId = stmt.executeInsert();
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
if (update) {
updateKeyAfterInsertAndAttach(entity, rowId, true);
}
return rowId;
}