Подтвердить что ты не робот

Вставка тысяч записей контактов с использованием applyBatch выполняется медленно

Я разрабатываю приложение, где мне нужно вставить много контактов. В настоящее время около 600 контактов, в общей сложности 6000 номеров телефонов. Самый большой контакт имеет 1800 телефонных номеров.

Состояние на сегодняшний день состоит в том, что я создал пользовательскую учетную запись для хранения контактов, поэтому пользователь может выбрать, чтобы увидеть контакт в представлении "Контакты".

Но вставка контактов очень медленная. Я вставляю контакты, используя ContentResolver.applyBatch. Я пробовал с различными размерами списка ContentProviderOperation (100, 200, 400), но общее время работы составляет ок. тоже самое. Для вставки всех контактов и номеров требуется около 30 минут!

Большинство проблем, которые я нашел относительно медленной вставки в SQlite, вызывают транзакции. Но поскольку я использую метод ContentResolver.applyBatch, я не контролирую это, и я бы предположил, что ContentResolver позаботится об управлении транзакциями для меня.

Итак, на мой вопрос: я делаю что-то не так, или я могу что-то сделать, чтобы ускорить это?

Андерс

Edit: @jcwenger: О, я вижу. Хорошее объяснение!

Итак, мне нужно будет сначала вставить в таблицу raw_contacts, а затем данные с именами и цифрами. То, что я проиграю, это обратная ссылка на raw_id, которую я использую в applyBatch.

Итак, мне нужно будет получить все идентификаторы вновь вставленных строк raw_contacts для использования в качестве внешних ключей в таблице данных?

4b9b3361

Ответ 1

Используйте ContentResolver.bulkInsert (Uri url, ContentValues[] values) вместо ApplyBatch()

ApplyBatch (1) использует транзакции и (2) блокирует ContentProvider один раз для всей партии, вместо этого блокируя/разблокируя один раз за операцию. из-за этого он немного быстрее, чем делает их по одному (без пакетирования).

Однако, поскольку каждая операция в пакете может иметь другой URI и т.д., существует огромное количество накладных расходов. "О, новая операция! Интересно, в какую таблицу она входит... Здесь я вставлю одну строку... О, новая операция! Интересно, в какую таблицу она входит..." ad infinitium. Поскольку большая часть работы по превращению URI в таблицы включает в себя множество сравнений строк, она, очевидно, очень медленная.

В отличие от этого, bulkInsert применяет целую кучу значений в одну и ту же таблицу. Это идет: "Насыпная вставка... найдите стол, хорошо, вставьте! Вставьте! Вставить! Вставить! Вставить!" Гораздо быстрее.

Конечно, это потребует, чтобы ваш ContentResolver эффективно реализовал bulkInsert. Большинство из них делают, если вы сами не написали его, и в этом случае потребуется немного кодирования.

Ответ 2

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

Ответ 3

Пример того, как переопределить bulkInsert(), чтобы ускорить вставку с множественными значениями, можно найти здесь

Ответ 4

@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 также использует трансации. Так что я не думаю, что причина. 2. Вы сказали, что bulkInsert применяет целую кучу значений к той же таблице. Мне жаль, что я не могу найти связанный код в исходном коде froyo. И я хочу знать, как вы могли это найти? Не могли бы вы сказать мне?

Я считаю, что:

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.

Любой вопрос, пожалуйста, свяжитесь со мной.

Ответ 5

Я получаю основное решение для вас, используйте "точки урожая" в пакетной операции.

Оборотная сторона использования пакетных операций заключается в том, что большая партия может долго блокировать базу данных, предотвращая доступ других приложений к данным и потенциально вызывающие диалоги ANR ( "Приложение не отвечает" ).

Чтобы избежать таких блокировок базы данных, обязательно вставьте " пределы урожайности" в пакете. Точка доходности указывает провайдеру контента, что перед выполнением следующей операции он может зафиксировать изменения, которые уже были сделаны, уступить другим запросам, открыть другую транзакцию и продолжить операции обработки.

Точка выхода не будет автоматически фиксировать транзакцию, но только если в базе данных есть еще один запрос. Обычно адаптер синхронизации должен вставлять предел текучести в начале каждой последовательности операций сырого контакта в партии. См. withYieldAllowed (boolean).

Я надеюсь, что это может быть полезно для вас.

Ответ 6

Вот пример вставки того же объема данных в течение 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: запуск вставки в партии: ср. 17 февраля 12:48:45 GMT + 02: 00 2016 02-17 12: 49: 16.446 2073-2090/com.vayosoft.mlab D/BatchInsertionTest: Конец вставки пакета, истекший: 00: 30.951

Ответ 7

Только для информации читателей этой темы.

Я столкнулся с проблемой производительности даже при использовании applyBatch(). В моем случае были триггеры базы данных, написанные на одной из таблиц. Я удалил триггеры таблицы и ее бум. Теперь мое приложение вставляет строки с быстрой скоростью благословения.