Baza danych Android SQLite: powolne wstawianie

92

Muszę przeanalizować dość duży plik XML (od około stu do kilkuset kilobajtów), którego używam Xml#parse(String, ContentHandler). Obecnie testuję to z plikiem 152 KB.

Podczas analizowania, ja też wstawić dane w bazie danych SQLite z wykorzystaniem połączeń podobne do następujących: getWritableDatabase().insert(TABLE_NAME, "_id", values). Wszystko to razem zajmuje około 80 sekund dla pliku testowego 152KB (co sprowadza się do wstawienia około 200 wierszy).

Kiedy komentuję wszystkie instrukcje wstawiania (ale zostawiam we wszystkim innym, na przykład tworzenie ContentValuesitp.), Ten sam plik zajmuje tylko 23 sekundy.

Czy to normalne, że operacje na bazie danych mają tak duży narzut? Czy mogę coś z tym zrobić?

benvd
źródło

Odpowiedzi:

191

Powinieneś robić wkładki wsadowe.

Pseudo kod:

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

To znacznie zwiększyło prędkość wstawiania w moich aplikacjach.

Aktualizacja:
@Yuku zamieścił bardzo interesujący post na blogu: Android używa inserthelpera do szybszego wstawiania do bazy danych sqlite

WarrenFaith
źródło
4
Wstawienie wszystkich wartości ContentValues ​​w ten sposób zajmuje tylko sekundę lub dwie. Wielkie dzięki!
benvd
Czy otwieranie długotrwałej transakcji obejmującej cały czas trwania operacji analizowania XML, a następnie zatwierdzanie jej na końcu jest bezpieczne? A może lista wstawień powinna być lokalnie buforowana w parserze XML, a następnie otwierana i zatwierdzana krótkotrwała transakcja po zakończeniu analizowania?
Graham Borland,
2
zawinięcie moich 60 insertów transakcją zwiększyło wydajność 10x. owinięcie go transakcją i użycie przygotowanej instrukcji (SQLiteStatement) zwiększyło ją 20x!
stefs
2
benvd dzięki za komentarz. Wstawiałem 20 tys. Rekordów, zajęło to około 8 minut, ale po użyciu transakcji zajmuje to tylko 20 sekund :-)
Bear
2
Ten wpis na blogu omawia inną optymalizację przy użyciu prawie ukrytego narzędzia InsertHelper outofwhatbox.com/blog/2010/12/…
Randy Sugianto 'Yuku'
68

Ponieważ InsertHelper wspomniany przez Yuku i Bretta jest obecnie przestarzały (poziom API 17), wydaje się, że właściwą alternatywą zalecaną przez Google jest użycie SQLiteStatement .

Użyłem metody wstawiania do bazy danych w następujący sposób:

database.insert(table, null, values);

Po tym, jak doświadczyłem poważnych problemów z wydajnością, poniższy kod przyspieszył moje 500 wstawek z 14,5 sekundy do zaledwie 270 ms , niesamowite!

Oto jak użyłem 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
źródło
14
Jeden haczyk, którego należy tutaj uniknąć: indeks w bindString jest oparty na 1, a nie na 0
Nazwa wyświetlana
@qefzec Dziękuję za to rozwiązanie… To właśnie skróciło czas wstawiania w mojej aplikacji z 78 sekund do 4 dla dodanych 900 wierszy ..
csanonymus
1
Dziękuję Panu. 20000 rekordów po 6 pól danych, w tym VARCHAR (80) od 4 minut do 8 sekund. Właściwie to powinno być oznaczone jako najlepsza odpowiedź, IMHO.
TomeeNS
1
Świetny! Nasz test porównawczy na 200 testowych wkładkach jednocześnie z 15 kolumnami na wkład generował poprawę od 4100% do 10400% w zależności od urządzenia i pamięci wewnętrznej / zewnętrznej. Wcześniejszy występ zagroziłby naszemu projektowi, zanim zostałby uruchomiony.
Frank
od 15 minut do 45 sekund dla 13600 rzędów
DoruChidean,
13

Kompilowanie instrukcji sql insert pomaga przyspieszyć działanie. Może również wymagać więcej wysiłku, aby wszystko podeprzeć i zapobiec możliwemu wstrzyknięciu, ponieważ teraz wszystko jest na twoich barkach.

Innym podejściem, które może przyspieszyć działanie, jest niedokumentowana klasa android.database.DatabaseUtils.InsertHelper. Rozumiem, że w rzeczywistości zawiera skompilowane instrukcje wstawiania. Przejście od nieskompilowanych wstawek transakcyjnych do skompilowanych wstawek transakcyjnych oznaczało około 3-krotny wzrost szybkości (2 ms na wkładkę do 0,6 ms na wstawkę) dla moich dużych (ponad 200 000 wpisów), ale prostych wstawek SQLite.

Przykładowy kod:

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();
Brett
źródło
i jak dodać ContentValue w iHelp.bind (first_index, thing_1); ?
Vasil Valchev
3

Jeśli tabela ma indeks, rozważ usunięcie go przed wstawieniem rekordów, a następnie dodanie go z powrotem po zatwierdzeniu rekordów.

kaD'argo
źródło
1

Jeśli używasz 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;

}

Następnie funkcja prywatna do wstawiania (nadal wewnątrz dostawcy treści):

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


    }
CircuitBreaker716
źródło