使用applyBatch插入数以千计的联系人条目很慢
我正在开发一个应用程序,我需要插入大量的联系人条目。 目前约有600个联系人,共有6000个电话号码。 最大的联系人有1800个电话号码。
截至今天的状态是,我创build了一个自定义帐户来保存联系人,因此用户可以select在联系人视图中查看联系人。
但是联系人的插入是非常缓慢的。 我使用ContentResolver.applyBatch插入联系人。 我已经尝试了不同大小的ContentProviderOperation列表(100,200,400),但总的运行时间大约是。 一样。 插入所有的联系人和号码大约需要30分钟!
我发现在SQlite中缓慢插入的大多数问题都会导致事务处理。 但是因为我使用ContentResolver.applyBatch方法,所以我不能控制这个,而且我会假设ContentResolver为我处理事务pipe理。
所以,对于我的问题:我做错了什么,或者我能做些什么来加速?
安德斯
编辑: @jcwenger:哦,我明白了。 很好的解释!
那么我将不得不先插入到raw_contacts表中,然后插入名字和数字的数据表。 我将失去的是在applyBatch中使用的raw_id的反向引用。
所以我将不得不把新插入的raw_contacts行的所有id用作数据表中的外键?
使用ContentResolver.bulkInsert (Uri url, ContentValues[] values)
而不是ApplyBatch()
ApplyBatch(1)使用事务,(2)为整个批次lockingContentProvider一次,而不是每个操作locking/解锁一次。 正因为如此,它比一次只做一个更快(非批量)。
但是,由于批处理中的每个操作都可以具有不同的URI等,所以会有大量的开销。 “哦,一个新的操作!我想知道它在哪里… …在这里,我会插入一行…哦,一个新的操作!我不知道它是什么表… …”无限。 由于将URI转换成表格的大部分工作都涉及大量的string比较,所以显然非常慢。
相反,bulkInsert将一整堆的值应用到同一个表中。 它是,“批量插入…find表,好的,插入!插入!插入!插入!插入! 快多了。
当然,它会要求你的ContentResolver高效地实现bulkInsert。 大多数情况下,除非你自己写,否则需要一些编码。
bulkInsert:对于那些感兴趣的,这是我能够实验的代码。 注意我们如何避免int / long / float的一些分配:)这可以节省更多的时间。
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; }
有关如何重载bulkInsert()
以加快倍数插入的示例,可以在这里find
@jcwenger首先,在阅读您的文章后,我认为这是bulkInsert比ApplyBatch更快的原因,但是在阅读联系提供程序的代码后,我不这么认为。 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也使用转换。所以我不认为这是原因。 2.你说bulkInsert对同一个表应用了一整堆的值。对不起,我在froyo的源代码中找不到相关的代码。我想知道你怎么能find它?你能告诉我吗?
我认为的原因是:
bulkInsert使用mDb.yieldIfContendedSafely(),而applyBatch使用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更快的原因。
有任何问题请联系我。
我为您获得基本的解决scheme,在批量操作中使用“屈服点”。
使用批处理操作的另一面是大批量数据库可能会长时间locking数据库,从而阻止其他应用程序访问数据并可能导致ANR(“应用程序未响应”对话框)。
为了避免这种数据库locking,请确保在批次中插入“ 屈服点 ”。 屈服点指示内容提供者,在执行下一个操作之前,它可以提交已经做出的更改,屈服于其他请求,打开另一个事务并继续处理操作。
屈服点不会自动提交事务,但只有在数据库上有另一个请求正在等待时。 正常情况下,同步适配器应该在批次中的每个原始联系人操作序列的开始处插入一个屈服点。 请参阅withYieldAllowed(boolean) 。
我希望这可能对你有用。
这里是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
只是为了这个线程的读者的信息。
即使使用applyBatch(),我也面临性能问题。 在我的情况下,有一个数据库触发器写在一个表上。 我删除了桌子的触发器和它的繁荣。 现在我的应用插入祝福快速行。