使用applyBatch插入数千个联系人条目的速度很慢

时间:2011-04-08 14:12:44

标签: android android-contentprovider android-contentresolver

我正在开发一个应用程序,我需要插入大量的联系人条目。目前约有600个联系人,共有6000个电话号码。最大的联系人有1800个电话号码。

截至今天的状态是我创建了一个自定义帐户来保存联系人,因此用户可以选择在“联系人”视图中查看联系人。

但是联系人的插入非常缓慢。我使用ContentResolver.applyBatch插入联系人。我尝试了不同大小的ContentProviderOperation列表(100,200,400),但总运行时间约为。相同。插入所有联系人和号码大约需要30分钟!

我发现在SQlite中缓慢插入的大多数问题都会导致交易。但是因为我使用ContentResolver.applyBatch方法,所以我不控制它,我会假设ContentResolver为我负责事务管理。

所以,我的问题是:我做错了什么,或者我能做些什么来加快速度呢?

的Anders

修改 @jcwenger: 原来如此。好解释!

那么我将首先插入raw_contacts表,然后插入带有名称和数字的数据表。我将失去的是我在applyBatch中使用的raw_id的后向引用。

所以我必须获取新插入的raw_contacts行的所有id以用作数据表中的外键吗?

6 个答案:

答案 0 :(得分:51)

使用ContentResolver.bulkInsert (Uri url, ContentValues[] values)代替ApplyBatch()

ApplyBatch(1)使用事务和(2)它为整个批次锁定ContentProvider一次,而不是每次操作锁定/解锁一次。因此,它比一次一个(非批处理)快一点。

但是,由于批处理中的每个操作都可以有不同的URI等,因此会产生大量的开销。 “哦,一个新的操作!我想知道它进入了什么桌子......在这里,我将插入一行......哦,一个新的操作!我想知道它在哪个表格......”无限广告。由于将URI转换为表格的大部分工作涉及大量字符串比较,因此显然非常慢。

相比之下,bulkInsert将整堆值应用于同一个表。它说,“批量插入......找到桌子,好吧,插入!插入!插入!插入!插入!”快得多。

当然,它需要您的ContentResolver有效地实现bulkInsert。大多数人都这样做,除非你自己写的,在这种情况下需要一些编码。

答案 1 :(得分:10)

bulkInsert:对于那些感兴趣的人,这是我能够试验的代码。注意我们如何避免int / long / floats的一些分配:)这可以节省更多时间。

private int doBulkInsertOptimised(Uri uri, ContentValues values[]) {
    long startTime = System.currentTimeMillis();
    long endTime = 0;
    //TimingInfo timingInfo = new TimingInfo(startTime);

    SQLiteDatabase db = mOpenHelper.getWritableDatabase();

    DatabaseUtils.InsertHelper inserter =
        new DatabaseUtils.InsertHelper(db, Tables.GUYS); 

    // Get the numeric indexes for each of the columns that we're updating
    final int guiStrColumn = inserter.getColumnIndex(Guys.STRINGCOLUMNTYPE);
    final int guyDoubleColumn = inserter.getColumnIndex(Guys.DOUBLECOLUMNTYPE);
//...
    final int guyIntColumn = inserter.getColumnIndex(Guys.INTEGERCOLUMUNTYPE);

    db.beginTransaction();
    int numInserted = 0;
    try {
        int len = values.length;
        for (int i = 0; i < len; i++) {
            inserter.prepareForInsert();

            String guyID = (String)(values[i].get(Guys.GUY_ID)); 
            inserter.bind(guiStrColumn, guyID);


            // convert to double ourselves to save an allocation.
            double d = ((Number)(values[i].get(Guys.DOUBLECOLUMNTYPE))).doubleValue();
            inserter.bind(guyDoubleColumn, lat);


            // getting the raw Object and converting it int ourselves saves
            // an allocation (the alternative is ContentValues.getAsInt, which
            // returns a Integer object)

            int status = ((Number) values[i].get(Guys.INTEGERCOLUMUNTYPE)).intValue();
            inserter.bind(guyIntColumn, status);

            inserter.execute();
        }
        numInserted = len;
        db.setTransactionSuccessful();
    } finally {
        db.endTransaction();
        inserter.close();

        endTime = System.currentTimeMillis();

        if (LOGV) {
            long timeTaken = (endTime - startTime);
            Log.v(TAG, "Time taken to insert " + values.length + " records was " + timeTaken + 
                    " milliseconds " + " or " + (timeTaken/1000) + "seconds");
        }
    }
    getContext().getContentResolver().notifyChange(uri, null);
    return numInserted;
}

答案 2 :(得分:2)

有关如何覆盖bulkInsert()的示例,以便加快倍数插入,可以找到here

答案 3 :(得分:1)

@jcwenger首​​先,在阅读完你的帖子后,我认为这就是原因 bulkInsert比ApplyBatch更快,但在阅读Contact Provider的代码后,我不这么认为。 1.你说ApplyBatch使用事务,是的,但bulkInsert也使用事务。这是它的代码:

public int bulkInsert(Uri uri, ContentValues[] values) {
    int numValues = values.length;
    mDb = mOpenHelper.getWritableDatabase();
    mDb.beginTransactionWithListener(this);
    try {
        for (int i = 0; i < numValues; i++) {
            Uri result = insertInTransaction(uri, values[i]);
            if (result != null) {
                mNotifyChange = true;
            }
            mDb.yieldIfContendedSafely();
        }
        mDb.setTransactionSuccessful();
    } finally {
        mDb.endTransaction();
    }
    onEndTransaction();
    return numValues;
}

也就是说,bulkInsert也使用了transations.So我不认为这就是原因。 2.你说bulkInsert将一大堆值应用到同一个表中。对不起,我在froyo的源代码中找不到相关的代码。我想知道你怎么能找到它?你能告诉我吗?

我认为的原因是:

bulkInsert在使用applyBatch时使用mDb.yieldIfContendedSafely() mDb.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY)/ * SLEEP_AFTER_YIELD_DELAY = 4000 * /

在阅读了SQLiteDatabase.java的代码之后,我发现,如果在yieldIfContendedSafely中设置一个时间,它会进行休眠,但是如果你没有设置时间,它就不会睡觉。你可以参考代码下面是SQLiteDatabase.java的一段代码

private boolean yieldIfContendedHelper(boolean checkFullyYielded, long     sleepAfterYieldDelay) {
    if (mLock.getQueueLength() == 0) {
        // Reset the lock acquire time since we know that the thread was willing to yield
        // the lock at this time.
        mLockAcquiredWallTime = SystemClock.elapsedRealtime();
        mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
        return false;
    }
    setTransactionSuccessful();
    SQLiteTransactionListener transactionListener = mTransactionListener;
    endTransaction();
    if (checkFullyYielded) {
        if (this.isDbLockedByCurrentThread()) {
            throw new IllegalStateException(
                    "Db locked more than once. yielfIfContended cannot yield");
        }
    }
    if (sleepAfterYieldDelay > 0) {
        // Sleep for up to sleepAfterYieldDelay milliseconds, waking up periodically to
        // check if anyone is using the database.  If the database is not contended,
        // retake the lock and return.
        long remainingDelay = sleepAfterYieldDelay;
        while (remainingDelay > 0) {
            try {
                Thread.sleep(remainingDelay < SLEEP_AFTER_YIELD_QUANTUM ?
                        remainingDelay : SLEEP_AFTER_YIELD_QUANTUM);
            } catch (InterruptedException e) {
                Thread.interrupted();
            }
            remainingDelay -= SLEEP_AFTER_YIELD_QUANTUM;
            if (mLock.getQueueLength() == 0) {
                break;
            }
        }
    }
    beginTransactionWithListener(transactionListener);
    return true;
}

我认为这是bulkInsert比applyBatch更快的原因。

有任何问题请与我联系。

答案 4 :(得分:1)

我为你找到了基本的解决方案, 在批处理操作中使用“收益点”。

使用批处理操作的另一方面是大批量可能会长时间锁定数据库,导致其他应用程序无法访问数据并可能导致ANR(“应用程序无响应”对话框。)

要避免数据库的此类锁定,请确保在批处理中插入“屈服点”。屈服点向内容提供者指示在执行下一个操作之前,它可以提交已经进行的更改,产生其他请求,打开另一个事务并继续处理操作。

屈服点不会自动提交事务,但仅当有另一个请求在数据库上等待时。通常,同步适配器应在批处理中的每个原始接触操作序列的开头插入一个屈服点。见withYieldAllowed(boolean)

我希望它可能对你有用。

答案 5 :(得分:1)

以下是在30秒内插入相同数据量的示例。

 public void testBatchInsertion() throws RemoteException, OperationApplicationException {
    final SimpleDateFormat FORMATTER = new SimpleDateFormat("mm:ss.SSS");
    long startTime = System.currentTimeMillis();
    Log.d("BatchInsertionTest", "Starting batch insertion on: " + new Date(startTime));

    final int MAX_OPERATIONS_FOR_INSERTION = 200;
    ArrayList<ContentProviderOperation> ops = new ArrayList<>();
    for(int i = 0; i < 600; i++){
        generateSampleProviderOperation(ops);
        if(ops.size() >= MAX_OPERATIONS_FOR_INSERTION){
            getContext().getContentResolver().applyBatch(ContactsContract.AUTHORITY,ops);
            ops.clear();
        }
    }
    if(ops.size() > 0)
        getContext().getContentResolver().applyBatch(ContactsContract.AUTHORITY,ops);
    Log.d("BatchInsertionTest", "End of batch insertion, elapsed: " + FORMATTER.format(new Date(System.currentTimeMillis() - startTime)));

}
private void generateSampleProviderOperation(ArrayList<ContentProviderOperation> ops){
    int backReference = ops.size();
    ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
            .withValue(ContactsContract.RawContacts.AGGREGATION_MODE, ContactsContract.RawContacts.AGGREGATION_MODE_DISABLED)
            .build()
    );
    ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                    .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, backReference)
                    .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                    .withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, "GIVEN_NAME " + (backReference + 1))
                    .withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, "FAMILY_NAME")
                    .build()
    );
    for(int i = 0; i < 10; i++)
        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                        .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, backReference)
                        .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
                        .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MAIN)
                        .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, Integer.toString((backReference + 1) * 10 + i))
                        .build()
        );
}

日志: 02-17 12:48:45.496 2073-2090 / com.vayosoft.mlab D / BatchInsertionTest:开始批量插入:Wed Feb 17 12:48:45 GMT + 02:00 2016 02-17 12:49:16.446 2073-2090 / com.vayosoft.mlab D / BatchInsertionTest:批量插入结束,已过去:00:30.951