База даних Android SQLite: повільне вставлення


91

Мені потрібно проаналізувати досить великий XML-файл (коливається від ста кілобайт до кількох сотень кілобайт), якими я і користуюся Xml#parse(String, ContentHandler). В даний час я тестую це за допомогою файлу 152 КБ.

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

Коли я коментую всі оператори вставки (але залишаю все інше, наприклад, створення ContentValuesтощо), той самий файл займає лише 23 секунди.

Чи нормально для операцій з базою даних такі великі накладні витрати? Чи можу я щось з цим зробити?

Відповіді:


190

Ви повинні робити пакетні вставки.

Псевдокод:

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

Це надзвичайно збільшило швидкість вставок у мої програми.

Оновлення:
@Yuku надав дуже цікавий допис у блозі: Android використовує inserthelper для швидшого вставлення в базу даних sqlite


4
Вставка всіх ContentValues ​​таким чином займає лише секунду або дві. Дуже дякую!
benvd,

Чи безпечно відкривати довготривалу транзакцію, що охоплює всю тривалість операції синтаксичного аналізу XML, а потім фіксує її наприкінці? Або список вставок повинен бути локально кешований всередині синтаксичного аналізатора XML, а потім мати короткочасну транзакцію, відкриту та здійснену після завершення аналізу?
Грем Борланд

2
обгортання моїх 60 вставок транзакцією збільшило продуктивність в 10 разів. обгортання його транзакцією та використання підготовленого оператора (SQLiteStatement) збільшило його в 20 разів!
stefs

2
benvd дякую за коментар. Я вставляв 20 тисяч записів, це зайняло близько 8 хвилин, але після використання транзакції потрібно лише 20 секунд :-)
Ведмідь

2
Цей запис у блозі обговорює чергову оптимізацію з використанням майже прихованого InsertHelper outofwhatbox.com/blog/2010/12/…
Ренді Сугіанто 'Юку'

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();
}

14
Тут слід уникнути одного лову: індекс у bindString базується на 1, а не на 0
Відображуване ім’я

@qefzec Дякую за це рішення .. Це просто скоротило час вставки в моєму додатку з 78 секунд до 4 на 900 доданих рядків ..
csanonymus

1
Дякую вам сер. 20000 записів із 6 полями даних, включаючи VARCHAR (80) від 4 хвилин до 8 секунд. Насправді це має бути позначено як найкраща відповідь, ІМХО.
TomeeNS

1
Чудово! Наш орієнтир на 200 тестових вкладок одночасно з 15 стовпцями на вкладиш генерував від 4100% до 10400% покращення залежно від пристрою та внутрішньої / зовнішньої пам'яті. Попередній виступ прирек би наш проект до того, як він розпочався.
Френк

з 15 хв до 45 с для 13600 рядків
ДоруЧідей

13

Компіляція вставки sql допомагає пришвидшити процес. Це також може зажадати більше зусиль, щоб укріпити все і запобігти можливому впорскуванню, оскільки це все на ваших плечах.

Іншим підходом, який також може пришвидшити ситуацію, є недостатньо задокументований клас android.database.DatabaseUtils.InsertHelper. Я розумію, що він насправді обгортає скомпільовані оператори вставки. Перехід від некомпільованих транзакційних вкладишів до скомпільованих транзакційних вкладишів збільшив швидкість у 3 рази (2 мс на вставку до .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);


    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.