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;
}
Другие вопросы по тегам