Cơ sở dữ liệu Android SQLite: chèn chậm


91

Tôi cần phân tích cú pháp một tệp XML khá lớn (dao động trong khoảng một trăm kilobyte đến vài trăm kilobyte) mà tôi đang sử dụng Xml#parse(String, ContentHandler). Tôi hiện đang thử nghiệm điều này với tệp 152KB.

Trong phân tích, tôi cũng chèn các dữ liệu trong một cơ sở dữ liệu SQLite sử dụng các cuộc gọi tương tự như sau: getWritableDatabase().insert(TABLE_NAME, "_id", values). Tất cả điều này cùng nhau mất khoảng 80 giây cho tệp thử nghiệm 152KB (đi kèm để chèn khoảng 200 hàng).

Khi tôi nhận xét ra tất cả các câu lệnh chèn (nhưng để lại trong mọi thứ khác, chẳng hạn như tạo, ContentValuesv.v.) cùng một tệp chỉ mất 23 giây.

Có bình thường khi hoạt động cơ sở dữ liệu có chi phí lớn như vậy không? Tôi có thể làm bất cứ điều gì về điều đó?

Câu trả lời:


190

Bạn nên thực hiện chèn hàng loạt.

Mã giả:

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

Điều đó đã làm tăng tốc độ chèn trong ứng dụng của tôi.

Cập nhật:
@Yuku đã cung cấp một bài đăng trên blog rất thú vị: Android sử dụng inserthelper để chèn nhanh hơn vào cơ sở dữ liệu sqlite


4
Việc chèn tất cả ContentValues ​​theo cách này chỉ mất một hoặc hai giây. Cảm ơn rất nhiều!
benvd

Có an toàn không khi mở một giao dịch dài hạn, bao gồm toàn bộ thời gian của hoạt động phân tích cú pháp XML và sau đó cam kết nó vào cuối? Hay danh sách các chèn có nên được lưu trữ cục bộ bên trong trình phân tích cú pháp XML và sau đó mở và cam kết một giao dịch ngắn hạn sau khi phân tích cú pháp hoàn tất?
Graham Borland

2
gói 60 lần chèn của tôi với một giao dịch đã tăng hiệu suất lên 10 lần. gói nó bằng một giao dịch và sử dụng một câu lệnh chuẩn bị sẵn (SQLiteStatement) đã tăng nó lên 20 lần!
stefs

2
benvd cảm ơn vì nhận xét. Tôi đang chèn bản ghi 20k, mất khoảng 8 phút nhưng sau khi sử dụng một giao dịch, chỉ mất 20 giây :-)
Chịu

2
Mục blog này thảo luận về một cách tối ưu hóa khác bằng cách sử dụng InsertHelper gần như ẩn outofwhatbox.com/blog/2010/12/…
Randy Sugianto 'Yuku' 24/11/11

68

Vì InsertHelper được Yuku và Brett đề cập hiện không còn được dùng nữa (API cấp 17), có vẻ như giải pháp thay thế phù hợp được Google đề xuất là sử dụng SQLiteStatement .

Tôi đã sử dụng phương pháp chèn cơ sở dữ liệu như thế này:

database.insert(table, null, values);

Sau khi tôi cũng gặp phải một số vấn đề nghiêm trọng về hiệu suất, đoạn mã sau đã tăng tốc 500 lần chèn của tôi từ 14,5 giây xuống chỉ còn 270 ms , thật tuyệt vời!

Đây là cách tôi sử dụng 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
Một catch để tránh ở đây: các chỉ số trong bindString là 1-dựa và không phải 0 dựa
Hiển thị tên

@qefzec Cảm ơn bạn cho solution..This này chỉ cần bỏ thời gian chèn trong ứng dụng của tôi từ 78 giây đến 4 cho 900 hàng thêm ..
csanonymus

1
Cảm ơn ngài. 20000 bản ghi với 6 trường dữ liệu mỗi trường, bao gồm VARCHAR (80) từ 4 phút đến 8 giây. Trên thực tế, đây nên được đánh dấu là câu trả lời hay nhất, IMHO.
TomeeNS

1
Tuyệt quá! Điểm chuẩn của chúng tôi trên 200 chèn thử nghiệm cùng một lúc với 15 cột trên mỗi chèn tạo ra cải thiện từ 4100% đến 10400% tùy thuộc vào thiết bị và bộ nhớ trong / ngoài. Hiệu suất trước đó có thể đã khiến dự án của chúng tôi bị hủy hoại trước khi nó đi vào hoạt động.
Frank

từ 15 phút xuống 45 giây cho 13600 hàng
DoruChidean

13

Biên dịch câu lệnh chèn sql giúp đẩy nhanh tiến độ. Nó cũng có thể đòi hỏi nhiều nỗ lực hơn để củng cố mọi thứ và ngăn ngừa khả năng bị tiêm vì bây giờ tất cả đều nằm trên vai bạn.

Một cách tiếp cận khác cũng có thể tăng tốc mọi thứ là lớp android.database.DatabaseUtils.InsertHelper được tài liệu hóa dưới dạng tài liệu. Sự hiểu biết của tôi là nó thực sự bao bọc các câu lệnh chèn đã biên dịch. Đi từ chèn giao dịch không được biên dịch sang chèn được giao dịch đã biên dịch có tốc độ tăng khoảng 3 lần (2ms mỗi lần chèn đến .6ms mỗi lần chèn) cho các chèn SQLite lớn (200K +) nhưng đơn giản của tôi.

Mã mẫu:

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

và cách thêm ContentValue trong iHelp.bind (first_index, thing_1); ?
Vasil Valchev

3

Nếu bảng có một chỉ mục trên đó, hãy cân nhắc bỏ nó trước khi chèn các bản ghi và sau đó thêm lại nó sau khi bạn đã cam kết các bản ghi của mình.


1

Nếu sử dụng 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;

}

Sau đó, chức năng riêng tư để thực hiện chèn (vẫn bên trong trình cung cấp nội dung của bạn):

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


    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.