36 votes

L'insertion de milliers d'entrées de contact à l'aide de applyBatch est lente

Je suis du développement d'une application où j'ai besoin d'insérer beaucoup d'entrées de Contact. À l'heure actuelle, environ 600 contacts avec un total de 6000 numéros de téléphone. Le plus grand contact a 1800 numéros de téléphone.

État de la situation aujourd'hui est que j'ai créé un Compte personnalisé pour tenir les Contacts, de sorte que l'utilisateur peut choisir d'afficher les contacts dans l'affichage des Contacts.

Mais l'insertion de la des contacts est très lent. - Je insérer les contacts à l'aide ContentResolver.applyBatch. J'ai essayé avec différentes tailles de la ContentProviderOperation liste(100, 200, 400), mais le temps total d'exécution est d'environ. la même. Pour insérer tous les contacts et les numéros prend environ 30 minutes!

La plupart des questions que j'ai trouvé en ce qui concerne lente insertion dans SQlite, apporte des transactions. Mais depuis que j'utilise le ContentResolver.applyBatch-méthode je n'ai pas le contrôle de cette, et je suppose que le ContentResolver s'occupe de la gestion des transactions pour moi.

Donc, ma question: Suis-je en train de faire quelque chose de mal, ou est-ce que je peux faire pour accélérer les choses?

Anders

Edit: @jcwenger: Oh, je vois. Bonne explication!

Alors je vais avoir à insérer dans le raw_contacts table, puis la datatable avec le nom et les numéros. Ce que je vais perdre est la référence de retour à la raw_id que j'utilise dans le applyBatch.

Donc je vais avoir à obtenir toutes les id de la nouvellement inséré raw_contacts lignes à utiliser comme clé étrangère dans la table de données?

53voto

jcwenger Points 6988

Utiliser ContentResolver.bulkInsert (Uri url, ContentValues[] values) au lieu de ApplyBatch()

ApplyBatch (1) utilise les transactions et (2) il verrouille le ContentProvider une fois pour le lot entier à la place de verrouillage/déverrouillage une fois par opération. de ce fait, il est légèrement plus rapide que de le faire un à la fois (non groupées).

Toutefois, puisque chaque Opération dans le Lot peut avoir un URI différent et ainsi de suite, il ya une énorme quantité de frais généraux. "Oh, une nouvelle opération! Je me demande ce que la table, elle va... Ici, je vais insérer une seule ligne... Oh, une nouvelle opération! Je me demande ce que la table, elle va..." ad infinitium. Puisque la plupart des travaux de tournage Uri dans des tables implique beaucoup de comparaisons de chaînes, il est évidemment très lente.

En revanche, bulkInsert s'applique tout un tas de valeurs à la même table. Il va, "Bulk insert... trouvez la table, d'accord, insérer! insérer! insérer! insérer! insérer!" Beaucoup plus rapide.

Il sera, bien sûr, besoin de votre ContentResolver à mettre en œuvre bulkInsert efficacement. La plupart le font, sauf si vous l'avez écrit vous-même, auquel cas il va prendre un peu de codage.

10voto

Viren Points 1501

bulkInsert: Pour ceux que ça intéresse, voici le code que j'ai pu expérimenter. Faites attention à la manière dont nous pouvons éviter certaines allocations pour int / long / floats :) cela pourrait économiser plus de temps.

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

2voto

David-mu Points 1004

Vous trouverez ici un exemple de comment remplacer le bulkInsert() afin d'accélérer l'insertion de multiples .

1voto

Vrajesh Points 152

Je reçois la solution de base pour vous, utiliser le "rendement de points" dans l' opération de traitement.

Le revers de la médaille de l'aide groupées des opérations est qu'un gros lot peut se verrouiller la base de données pour un long temps de prévenir les autres applications d'accéder à des données et pouvant causer des ANRs ("l'Application ne Répond Pas" boîtes de dialogue.)

Pour éviter de tels problèmes de blocage de la base de données, veillez à insérer le "rendement de points" dans le lot. Un point de céder indique au fournisseur de contenu qu'avant l'exécution de la prochaine opération, il peut valider les modifications qui ont déjà été faites, céder à d'autres demandes, d'ouvrir une autre transaction et de poursuivre les opérations de traitement.

Un point de céder, ne va pas automatiquement de valider la transaction, mais seulement si il y a une autre demande en attente sur la base de données. Normalement, une synchronisation de la carte insérer un point de céder au début de chaque raw fonctionnement du contact de la séquence dans le lot. Voir withYieldAllowed(boolean).

J'espère que c'est peut-être utile pour vous.

1voto

jiangyan.lily Points 130

@jcwenger Au premier abord, après avoir lu votre post, je pense que c'est la raison de bulkInsert est plus rapide que ApplyBatch, mais après avoir lu le code de Fournisseur de Contact, je ne le pense pas. 1.Vous avez dit ApplyBatch utiliser des transactions, oui, mais bulkInsert également utiliser les transactions. Voici le code de celui-ci:

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

C'est-à-dire, bulkInsert également utiliser les transactions.Donc je ne pense pas que ce soit la raison. 2.Vous avez dit bulkInsert s'applique tout un tas de valeurs à la même table.Je suis désolé je ne peux pas trouver de code dans le code source de la version froyo.Et je veux savoir comment on peut trouver cela?Pourriez-vous me dire?

La raison pour laquelle je pense, c'est que:

bulkInsert utiliser mDb.yieldIfContendedSafely (), tandis que applyBatch utilisation mDb.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY)/*SLEEP_AFTER_YIELD_DELAY = 4000*/

après avoir lu le code de SQLiteDatabase.java je trouve que, si une fois dans yieldIfContendedSafely, elle fera un sommeil, mais si vous ne définissez pas le temps, il ne veut pas dormir.Vous pouvez consulter le code ci-dessous qui est un morceau de code de 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;
}

Je pense que c'est la raison de bulkInsert est plus rapide que applyBatch.

Toute question, veuillez me contacter.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X