База данных Android SQLite: медленная вставка

92

Мне нужно проанализировать довольно большой XML-файл (от сотни до нескольких сотен килобайт), который я использую Xml#parse(String, ContentHandler). В настоящее время я тестирую это с файлом 152 КБ.

Во время разбора, я также вставка данных в базе данных SQLite , используя вызовы , подобные следующему: getWritableDatabase().insert(TABLE_NAME, "_id", values). Все это вместе занимает около 80 секунд для тестового файла размером 152 КБ (что сводится к вставке примерно 200 строк).

Когда я комментирую все операторы вставки (но оставляю все остальное, например, создание ContentValuesи т. Д.), Один и тот же файл занимает всего 23 секунды.

Нормально ли для операций с базой данных такие большие накладные расходы? Могу я что-нибудь с этим поделать?

Benvd
источник

Ответы:

192

Вам следует делать пакетные вставки.

Псевдокод:

db.beginTransaction();
for (entry : listOfEntries) {
    db.insert(entry);
}
db.setTransactionSuccessful();
db.endTransaction();

Это значительно увеличило скорость вставки в мои приложения.

Обновление:
@Yuku предоставил очень интересную запись в блоге: Android использует inserthelper для более быстрой вставки в базу данных sqlite

УорренВера
источник
4
Вставка всех ContentValues ​​таким образом занимает всего пару секунд. Большое спасибо!
Benvd
Безопасно ли открывать длительную транзакцию, покрывающую всю продолжительность операции синтаксического анализа XML, а затем фиксировать ее в конце? Или следует локально кэшировать список вставок внутри анализатора XML, а затем открывать и фиксировать кратковременную транзакцию после завершения синтаксического анализа?
Graham Borland
2
упаковка моих 60 вставок с транзакцией увеличила производительность в 10 раз. упаковка его транзакцией и использование подготовленного оператора (SQLiteStatement) увеличила его в 20 раз!
stefs
2
benvd спасибо за комментарий. Я вставлял 20k записей, это заняло около 8 минут, но после использования транзакции это займет всего 20 секунд :-)
Медведь
2
В этой записи блога обсуждается еще одна оптимизация с использованием почти скрытого InsertHelper outofwhatbox.com/blog/2010/12/…
Рэнди Суганто 'Yuku'
68

Поскольку InsertHelper, упомянутый Юку и Бреттом, устарел (уровень API 17), кажется, что правильной альтернативой, рекомендованной Google, является использование SQLiteStatement .

Я использовал такой метод вставки в базу данных:

database.insert(table, null, values);

После того, как у меня также возникли серьезные проблемы с производительностью, следующий код ускорил мои 500 вставок с 14,5 до всего 270 мс , потрясающе!

Вот как я использовал SQLiteStatement:

private void insertTestData() {
    String sql = "insert into producttable (name, description, price, stock_available) values (?, ?, ?, ?);";

    dbHandler.getWritableDatabase();
    database.beginTransaction();
    SQLiteStatement stmt = database.compileStatement(sql);

    for (int i = 0; i < NUMBER_OF_ROWS; i++) {
        //generate some values

        stmt.bindString(1, randomName);
        stmt.bindString(2, randomDescription);
        stmt.bindDouble(3, randomPrice);
        stmt.bindLong(4, randomNumber);

        long entryID = stmt.executeInsert();
        stmt.clearBindings();
    }

    database.setTransactionSuccessful();
    database.endTransaction();

    dbHandler.close();
}
qefzec
источник
14
Здесь следует избегать одной уловки: индекс в bindString основан на 1, а не на 0
Отображаемое имя
@qefzec Спасибо за это решение .. Это просто снизило время вставки в моем приложении с 78 секунд до 4 для добавленных 900 строк ..
csanonymus
1
Спасибо, сэр. 20000 записей с 6 полями данных каждое, включая VARCHAR (80) от 4 минут до 8 секунд. Собственно, это следует отметить как лучший ответ, ИМХО.
TomeeNS
1
Большой! Наш тест на 200 тестовых вставок одновременно с 15 столбцами на вставку показал улучшение от 4100% до 10400% в зависимости от устройства и внутренней / внешней памяти. Предыдущее выступление обрекло наш проект еще до того, как он сдвинулся с мертвой точки.
Франк
с 15 минут до 45
секунд
13

Компиляция инструкции sql insert помогает ускорить процесс. Также может потребоваться больше усилий, чтобы укрепить все и предотвратить возможную инъекцию, поскольку теперь все это на ваших плечах.

Другой подход, который также может ускорить процесс, - это недокументированный класс android.database.DatabaseUtils.InsertHelper. Насколько я понимаю, он фактически обертывает скомпилированные операторы вставки. Переход от некомпилированных транзакционных вставок к скомпилированным транзакционным вставкам дал примерно 3-кратное увеличение скорости (от 2 мс на вставку до 0,6 мс на вставку) для моих больших (более 200 тыс. Записей), но простых вставок SQLite.

Образец кода:

SQLiteDatabse db = getWriteableDatabase();

//use the db you would normally use for db.insert, and the "table_name"
//is the same one you would use in db.insert()
InsertHelper iHelp = new InsertHelper(db, "table_name");

//Get the indices you need to bind data to
//Similar to Cursor.getColumnIndex("col_name");                 
int first_index = iHelp.getColumnIndex("first");
int last_index = iHelp.getColumnIndex("last");

try
{
   db.beginTransaction();
   for(int i=0 ; i<num_things ; ++i)
   {
       //need to tell the helper you are inserting (rather than replacing)
       iHelp.prepareForInsert();

       //do the equivalent of ContentValues.put("field","value") here
       iHelp.bind(first_index, thing_1);
       iHelp.bind(last_index, thing_2);

       //the db.insert() equilvalent
       iHelp.execute();
   }
   db.setTransactionSuccessful();
}
finally
{
    db.endTransaction();
}
db.close();
Бретт
источник
и как добавить ContentValue в iHelp.bind (first_index, thing_1); ?
Васил Валчев
3

Если в таблице есть индекс, подумайте о том, чтобы удалить его перед вставкой записей, а затем добавить обратно после того, как вы зафиксировали свои записи.

КаД'арго
источник
1

При использовании ContentProvider:

@Override
public int bulkInsert(Uri uri, ContentValues[] bulkinsertvalues) {

    int QueryType = sUriMatcher.match(uri);
    int returnValue=0;
    SQLiteDatabase db = mOpenHelper.getWritableDatabase();

     switch (QueryType) {

         case SOME_URI_IM_LOOKING_FOR: //replace this with your real URI

            db.beginTransaction();

            for (int i = 0; i < bulkinsertvalues.length; i++) {
                //get an individual result from the array of ContentValues
                ContentValues values = bulkinsertvalues[i];
                //insert this record into the local SQLite database using a private function you create, "insertIndividualRecord" (replace with a better function name)
                insertIndividualRecord(uri, values);    
            }

            db.setTransactionSuccessful();
            db.endTransaction();                 

            break;  

         default:
             throw new IllegalArgumentException("Unknown URI " + uri);

     }    

    return returnValue;

}

Затем частная функция для выполнения вставки (все еще внутри вашего поставщика контента):

       private Uri insertIndividualRecord(Uri uri, ContentValues values){

            //see content provider documentation if this is confusing
            if (sUriMatcher.match(uri) != THE_CONSTANT_IM_LOOKING_FOR) {
                throw new IllegalArgumentException("Unknown URI " + uri);
            }

            //example validation if you have a field called "name" in your database
            if (values.containsKey(YOUR_CONSTANT_FOR_NAME) == false) {
                values.put(YOUR_CONSTANT_FOR_NAME, "");
            }

            //******add all your other validations

            //**********

           //time to insert records into your local SQLite database
           SQLiteDatabase db = mOpenHelper.getWritableDatabase();
           long rowId = db.insert(YOUR_TABLE_NAME, null, values);           

           if (rowId > 0) {
               Uri myUri = ContentUris.withAppendedId(MY_INSERT_URI, rowId);
               getContext().getContentResolver().notifyChange(myUri, null);

               return myUri;
           }


           throw new SQLException("Failed to insert row into " + uri);


    }
Прерыватель цепи716
источник