Android SQLite 데이터베이스 : 느린 삽입


91

상당히 큰 XML 파일 (약 100 킬로바이트에서 수백 킬로바이트 사이에서 다름)을 구문 분석해야합니다 Xml#parse(String, ContentHandler). 현재 152KB 파일로 이것을 테스트하고 있습니다.

구문 분석 중에 다음과 유사한 호출을 사용하여 SQLite 데이터베이스에 데이터를 삽입합니다 getWritableDatabase().insert(TABLE_NAME, "_id", values).. 이 모든 것이 152KB 테스트 파일의 경우 약 80 초가 소요됩니다 (대략 200 행을 삽입하는 데 소요됨).

모든 insert 문을 주석 처리 할 때 (만들기 ContentValues등 다른 모든 것은 남겨 둡니다 ) 같은 파일이 23 초 밖에 걸리지 않습니다.

데이터베이스 작업에 그렇게 큰 오버 헤드가 발생하는 것이 정상입니까? 그것에 대해 무엇을 할 수 있습니까?

답변:


190

일괄 삽입을해야합니다.

의사 코드 :

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

그것은 내 앱의 삽입 속도를 극도로 증가 시켰습니다.

업데이트 :
@Yuku는 매우 흥미로운 블로그 게시물을 제공했습니다. Android는 sqlite 데이터베이스에 더 빠르게 삽입하기 위해 inserthelper를 사용합니다.


4
이 방식으로 모든 ContentValue를 삽입하는 데는 1 ~ 2 초 밖에 걸리지 않습니다. 감사합니다!
benvd

XML 구문 분석 작업의 전체 기간을 포함하여 장기 실행 트랜잭션을 연 다음 마지막에 커밋하는 것이 안전합니까? 아니면 삽입 목록을 XML 파서 내부에 로컬로 캐시 한 다음 파싱이 완료되면 단기 트랜잭션을 열고 커밋해야합니까?
Graham Borland

2
내 60 개의 인서트를 트랜잭션으로 감싸면 성능이 10 배 향상되었습니다. 트랜잭션으로 래핑하고 준비된 문 (SQLiteStatement)을 사용하면 20 배 증가했습니다!
stefs 2011

2
의견 주셔서 감사합니다. 2 만개의 레코드를 삽입했는데 8 분 정도 걸렸는데 트랜잭션 사용 후 20 초 밖에 걸리지 않았습니다 :-)
Bear

2
이 블로그 항목은 거의 숨겨진 InsertHelper outofwhatbox.com/blog/2010/12/…를
Randy Sugianto '

68

Yuku와 Brett에서 언급 한 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의 인덱스는 0 기반이 아닌 1 기반입니다.
표시 이름

@qefzec이 솔루션에 감사드립니다 .. 이것은 내 앱의 삽입 시간을 78 초에서 900 행 추가에 대해 4로 줄
였습니다

1
감사합니다. 4 분에서 8 초 사이의 VARCHAR (80)을 포함하여 각각 6 개의 데이터 필드가있는 20000 개의 레코드. 사실 이것은 IMHO의 베스트 답변으로 표시되어야합니다.
TomeeNS

1
큰! 장치 및 내부 / 외부 메모리에 따라 삽입 당 15 개 열이 4100 %에서 10400 % 개선 된 한 번에 200 개의 테스트 삽입물에 대한 벤치 마크입니다. 이전의 성능은 프로젝트가 시작되기 전에 우리 프로젝트를 망 쳤을 것입니다.
Frank

15 분에서 45 초까지 13600 행
DoruChidean

13

SQL 삽입 문을 컴파일하면 작업 속도를 높일 수 있습니다. 이제 모든 것이 어깨 위에 있기 때문에 모든 것을지지하고 가능한 주사를 예방하기 위해 더 많은 노력이 필요할 수 있습니다.

속도를 높일 수있는 또 다른 방법은 문서화되지 않은 android.database.DatabaseUtils.InsertHelper 클래스입니다. 내 이해는 실제로 컴파일 된 삽입 문을 래핑한다는 것입니다. 컴파일되지 않은 트랜잭션 삽입에서 컴파일 된 트랜잭션 삽입으로 이동하는 것은 제 대규모 (200,000 개 이상의 항목)이지만 간단한 SQLite 삽입의 경우 속도가 약 3 배 향상되었습니다 (삽입 당 2ms에서 삽입 당 .6ms).

샘플 코드 :

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

iHelp.bind (first_index, thing_1);에 ContentValue를 추가하는 방법 ?
Vasil Valchev 2015

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


    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.