Améliorer les performances INSERT par seconde de SQLite?

L’optimisation de SQLite est délicate. Les performances d’insertion en bloc d’une application C peuvent varier de 85 insertions par seconde à plus de 96 000 insertions par seconde!

Contexte: Nous utilisons SQLite dans le cadre d’une application de bureau. Nous avons de grandes quantités de données de configuration stockées dans des fichiers XML qui sont analysées et chargées dans une firebase database SQLite pour un traitement ultérieur lorsque l’application est initialisée. SQLite est idéal pour cette situation car il est rapide, il ne nécessite aucune configuration spécialisée et la firebase database est stockée sur disque en tant que fichier unique.

Justification: Au départ, j’étais déçu par la performance que je voyais. Il s’avère que les performances de SQLite peuvent varier considérablement (à la fois pour les insertions en bloc et les sélections) en fonction de la configuration de la firebase database et de la manière dont vous utilisez l’API. Ce n’était pas une mince affaire de savoir quelles étaient toutes les options et techniques, alors j’ai pensé qu’il était prudent de créer cette entrée du wiki de la communauté pour partager les résultats avec les lecteurs Stack Overflow afin d’économiser aux ennuis des mêmes investigations.

L’expérience: Plutôt que de simplement parler de conseils de performance au sens général (par exemple, “Utiliser une transaction!” ), J’ai pensé qu’il était préférable d’écrire du code C et de mesurer l’impact de différentes options. Nous allons commencer avec quelques données simples:

  • Un fichier texte de 28 Mo délimité par des tabulations (environ 865 000 enregistrements) de l’ horaire de transit complet de la ville de Toronto
  • Ma machine de test est un P4 à 3,60 GHz sous Windows XP.
  • Le code est compilé avec Visual C ++ 2005 en tant que “Release” avec “Full Optimization” (/ Ox) et Favor Fast Code (/ Ot).
  • J’utilise le “Amalgamation” de SQLite, compilé directement dans mon application de test. La version SQLite que je possède est un peu plus ancienne (3.6.7), mais je pense que ces résultats seront comparables à ceux de la dernière version (veuillez laisser un commentaire si vous en pensez autrement).

Écrivons du code!

Le code: Un programme simple C qui lit le fichier texte ligne par ligne, divise la chaîne en valeurs, puis insère les données dans une firebase database SQLite. Dans cette version “baseline” du code, la firebase database est créée, mais nous n’insérerons pas réellement de données:

/************************************************************* Baseline code to experiment with SQLite performance. Input data is a 28 MB TAB-delimited text file of the complete Toronto Transit System schedule/route info from http://www.toronto.ca/open/datasets/ttc-routes/ **************************************************************/ #include  #include  #include  #include  #include "sqlite3.h" #define INPUTDATA "C:\\TTC_schedule_scheduleitem_10-27-2009.txt" #define DATABASE "c:\\TTC_schedule_scheduleitem_10-27-2009.sqlite" #define TABLE "CREATE TABLE IF NOT EXISTS TTC (id INTEGER PRIMARY KEY, Route_ID TEXT, Branch_Code TEXT, Version INTEGER, Stop INTEGER, Vehicle_Index INTEGER, Day Integer, Time TEXT)" #define BUFFER_SIZE 256 int main(int argc, char **argv) { sqlite3 * db; sqlite3_stmt * stmt; char * sErrMsg = 0; char * tail = 0; int nRetCode; int n = 0; clock_t cStartClock; FILE * pFile; char sInputBuf [BUFFER_SIZE] = "\0"; char * sRT = 0; /* Route */ char * sBR = 0; /* Branch */ char * sVR = 0; /* Version */ char * sST = 0; /* Stop Number */ char * sVI = 0; /* Vehicle */ char * sDT = 0; /* Date */ char * sTM = 0; /* Time */ char sSQL [BUFFER_SIZE] = "\0"; /*********************************************/ /* Open the Database and create the Schema */ sqlite3_open(DATABASE, &db); sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg); /*********************************************/ /* Open input file and import into Database*/ cStartClock = clock(); pFile = fopen (INPUTDATA,"r"); while (!feof(pFile)) { fgets (sInputBuf, BUFFER_SIZE, pFile); sRT = strtok (sInputBuf, "\t"); /* Get Route */ sBR = strtok (NULL, "\t"); /* Get Branch */ sVR = strtok (NULL, "\t"); /* Get Version */ sST = strtok (NULL, "\t"); /* Get Stop Number */ sVI = strtok (NULL, "\t"); /* Get Vehicle */ sDT = strtok (NULL, "\t"); /* Get Date */ sTM = strtok (NULL, "\t"); /* Get Time */ /* ACTUAL INSERT WILL GO HERE */ n++; } fclose (pFile); printf("Imported %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC); sqlite3_close(db); return 0; } 

Le contrôle”

Exécuter le code tel quel ne permet pas d’exécuter des opérations de firebase database, mais cela nous donnera une idée de la vitesse de traitement des E / S et des opérations de traitement de chaînes sur les fichiers C brutes.

864913 enregistrements importés en 0.94 secondes

Génial! Nous pouvons faire 920 000 insertions par seconde, à condition de ne pas faire d’insertions 🙂


Le “pire scénario-scénario”

Nous allons générer la chaîne SQL en utilisant les valeurs lues dans le fichier et appeler cette opération SQL en utilisant sqlite3_exec:

 sprintf(sSQL, "INSERT INTO TTC VALUES (NULL, '%s', '%s', '%s', '%s', '%s', '%s', '%s')", sRT, sBR, sVR, sST, sVI, sDT, sTM); sqlite3_exec(db, sSQL, NULL, NULL, &sErrMsg); 

Cela va être lent car le SQL sera compilé dans le code VDBE pour chaque insertion et chaque insertion se produira dans sa propre transaction. Quelle est la lenteur?

Importé 864913 enregistrements en 9933.61 secondes

Yikes! 2 heures et 45 minutes! C’est seulement 85 inserts par seconde.

Utiliser une transaction

Par défaut, SQLite évaluera chaque instruction INSERT / UPDATE au sein d’une transaction unique. Si vous effectuez un grand nombre d’insertions, il est conseillé d’envelopper votre opération dans une transaction:

 sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg); pFile = fopen (INPUTDATA,"r"); while (!feof(pFile)) { ... } fclose (pFile); sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg); 

Importé 864913 enregistrements en 38.03 secondes

C’est mieux. En regroupant toutes nos plaquettes en une seule transaction, nous avons amélioré notre performance à 23 000 insertions par seconde.

Utiliser une déclaration préparée

Utiliser une transaction était une amélioration considérable, mais recomstackr l’instruction SQL pour chaque insertion n’a aucun sens si nous utilisons le même over-over SQL. Utilisons sqlite3_prepare_v2 pour comstackr notre instruction SQL une seule fois, puis lions nos parameters à cette instruction en utilisant sqlite3_bind_text :

 /* Open input file and import into the database */ cStartClock = clock(); sprintf(sSQL, "INSERT INTO TTC VALUES (NULL, @RT, @BR, @VR, @ST, @VI, @DT, @TM)"); sqlite3_prepare_v2(db, sSQL, BUFFER_SIZE, &stmt, &tail); sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg); pFile = fopen (INPUTDATA,"r"); while (!feof(pFile)) { fgets (sInputBuf, BUFFER_SIZE, pFile); sRT = strtok (sInputBuf, "\t"); /* Get Route */ sBR = strtok (NULL, "\t"); /* Get Branch */ sVR = strtok (NULL, "\t"); /* Get Version */ sST = strtok (NULL, "\t"); /* Get Stop Number */ sVI = strtok (NULL, "\t"); /* Get Vehicle */ sDT = strtok (NULL, "\t"); /* Get Date */ sTM = strtok (NULL, "\t"); /* Get Time */ sqlite3_bind_text(stmt, 1, sRT, -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 2, sBR, -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 3, sVR, -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 4, sST, -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 5, sVI, -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 6, sDT, -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 7, sTM, -1, SQLITE_TRANSIENT); sqlite3_step(stmt); sqlite3_clear_bindings(stmt); sqlite3_reset(stmt); n++; } fclose (pFile); sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg); printf("Imported %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC); sqlite3_finalize(stmt); sqlite3_close(db); return 0; 

Importé 864913 enregistrements en 16,27 secondes

Agréable! Il y a un peu plus de code (n’oubliez pas d’appeler sqlite3_clear_bindings et sqlite3_reset ), mais nous avons plus que doublé nos performances à 53 000 insertions par seconde.

PRAGMA synchrone = OFF

Par défaut, SQLite se mettra en pause après avoir émis une commande d’écriture au niveau du système d’exploitation. Cela garantit que les données sont écrites sur le disque. En définissant synchronous = OFF , nous demandons à SQLite de simplement transférer les données vers le système d’exploitation pour qu’elles les écrivent, puis continuer. Il est possible que le fichier de firebase database soit endommagé si l’ordinateur subit une panne catastrophique (ou une panne de courant) avant que les données ne soient écrites sur le plateau:

 /* Open the database and create the schema */ sqlite3_open(DATABASE, &db); sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg); sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg); 

Importé 864913 enregistrements en 12,41 secondes

Les améliorations sont désormais plus petites, mais nous en avons 69 600 par seconde.

PRAGMA journal_mode = MEMORY

Envisagez de stocker le journal de restauration en mémoire en évaluant PRAGMA journal_mode = MEMORY . Votre transaction sera plus rapide, mais si vous perdez de la puissance ou si votre programme plante pendant une transaction, votre firebase database pourrait être dans un état corrompu avec une transaction partiellement terminée:

 /* Open the database and create the schema */ sqlite3_open(DATABASE, &db); sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg); sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg); 

864913 enregistrements importés en 13.50 secondes

Un peu plus lent que l’optimisation précédente à 64 000 insertions par seconde.

PRAGMA synchrone = OFF et PRAGMA journal_mode = MEMORY

Combinons les deux optimisations précédentes. C’est un peu plus risqué (en cas de crash), mais nous ne faisons qu’importer des données (ne pas faire fonctionner une banque):

 /* Open the database and create the schema */ sqlite3_open(DATABASE, &db); sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg); sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg); sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg); 

Importé 864913 enregistrements en 12.00 secondes

Fantastique! Nous sums en mesure de réaliser 72 000 insertions par seconde.

Utiliser une firebase database en mémoire

Juste pour les coups de pied, construisons sur toutes les optimisations précédentes et redéfinissons le nom de fichier de la firebase database afin que nous travaillions entièrement en mémoire vive:

 #define DATABASE ":memory:" 

Importé 864913 enregistrements en 10,94 secondes

Il n’est pas très pratique de stocker notre firebase database en mémoire vive, mais il est impressionnant de pouvoir réaliser 79 000 insertions par seconde.

Refactoring C Code

Bien que cela ne soit pas spécifiquement une amélioration SQLite, je n’aime pas les opérations d’affectation de char* supplémentaires dans la boucle while. Refactorisons rapidement ce code pour passer la sortie de strtok() directement dans sqlite3_bind_text() et laisser le compilateur essayer d’accélérer les choses pour nous:

 pFile = fopen (INPUTDATA,"r"); while (!feof(pFile)) { fgets (sInputBuf, BUFFER_SIZE, pFile); sqlite3_bind_text(stmt, 1, strtok (sInputBuf, "\t"), -1, SQLITE_TRANSIENT); /* Get Route */ sqlite3_bind_text(stmt, 2, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Branch */ sqlite3_bind_text(stmt, 3, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Version */ sqlite3_bind_text(stmt, 4, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Stop Number */ sqlite3_bind_text(stmt, 5, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Vehicle */ sqlite3_bind_text(stmt, 6, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Date */ sqlite3_bind_text(stmt, 7, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT); /* Get Time */ sqlite3_step(stmt); /* Execute the SQL Statement */ sqlite3_clear_bindings(stmt); /* Clear bindings */ sqlite3_reset(stmt); /* Reset VDBE */ n++; } fclose (pFile); 

Note: Nous revenons à l’utilisation d’un vrai fichier de firebase database. Les bases de données en mémoire sont rapides, mais pas nécessairement pratiques

Importé 864913 enregistrements en 8,94 secondes

Un léger remaniement du code de traitement de chaîne utilisé dans notre liaison de parameters nous a permis d’effectuer 96 700 insertions par seconde. Je pense qu’il est prudent de dire que c’est très rapide . Comme nous commençons à modifier d’autres variables (c’est-à-dire la taille de la page, la création de l’index, etc.), ce sera notre référence.


Résumé (jusqu’à présent)

J’espère que tu es toujours avec moi! La raison pour laquelle nous avons commencé sur cette voie est que les performances des insertions en masse varient énormément avec SQLite, et il n’est pas toujours évident de savoir quels changements doivent être apportés pour accélérer notre opération. En utilisant le même compilateur (et les mêmes options de compilation), la même version de SQLite et les mêmes données, nous avons optimisé notre code et notre utilisation de SQLite pour passer du pire scénario de 85 insertions par seconde à plus de 96 000 insertions par seconde!


CREATE INDEX puis INSERT vs. INSERT puis CREATE INDEX

Avant de commencer à mesurer les performances de SELECT , nous soaps que nous allons créer des index. Dans l’une des réponses ci-dessous, il a été suggéré que lors de l’insertion en bloc, il est plus rapide de créer l’index après l’insertion des données (au lieu de créer l’index, puis d’insérer les données). Essayons:

Créer un index puis insérer des données

 sqlite3_exec(db, "CREATE INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg); sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg); ... 

Importé 864913 enregistrements en 18.13 secondes

Insérer des données puis créer un index

 ... sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg); sqlite3_exec(db, "CREATE INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg); 

Importé 864913 enregistrements en 13,66 secondes

Comme prévu, les insertions en bloc sont plus lentes si une colonne est indexée, mais cela fait une différence si l’index est créé après l’insertion des données. Notre base de référence sans index est de 96 000 insertions par seconde. La création de l’index en premier, puis l’insertion de données nous donnent 47 700 insertions par seconde, alors que l’insertion des données puis la création de l’index nous donnent 63 300 insertions par seconde.


Je prendrais volontiers des suggestions pour d’autres scénarios à essayer … Et comstackrai bientôt des données similaires pour les requêtes SELECT.

Plusieurs conseils:

  1. Mettez des insertions / mises à jour dans une transaction.
  2. Pour les anciennes versions de SQLite – Considérons un mode journal moins paranoïaque ( pragma journal_mode ). Il y a NORMAL , puis il y a OFF , ce qui peut augmenter considérablement la vitesse d’insertion si vous n’êtes pas trop inquiet de la corruption possible de la firebase database si le système d’exploitation se bloque. Si votre application se bloque, les données devraient être correctes. Notez que dans les nouvelles versions, les parameters OFF/MEMORY ne sont pas sécurisés pour les plantages au niveau de l’application.
  3. Jouer avec des tailles de page fait également la différence ( PRAGMA page_size ). Avoir de plus grandes tailles de page peut rendre les lectures et les écritures un peu plus rapides car de plus grandes pages sont conservées en mémoire. Notez que plus de mémoire sera utilisée pour votre firebase database.
  4. Si vous avez des index, pensez à appeler CREATE INDEX après avoir effectué toutes vos insertions. Ceci est beaucoup plus rapide que la création de l’index et la création de vos insertions.
  5. Vous devez faire très attention si vous avez un access simultané à SQLite, car toute la firebase database est verrouillée lorsque les écritures sont terminées et bien que plusieurs lecteurs soient possibles, les écritures seront verrouillées. Cela a été quelque peu amélioré avec l’ajout d’un WAL dans les nouvelles versions de SQLite.
  6. Profitez de l’économie d’espace … les petites bases de données vont plus vite. Par exemple, si vous avez des paires de valeurs de clé, essayez si possible de faire de la clé une INTEGER PRIMARY KEY , qui remplacera la colonne de numéro de ligne unique implicite de la table.
  7. Si vous utilisez plusieurs threads, vous pouvez essayer d’utiliser le cache de pages partagées , ce qui permettra de partager les pages chargées entre les threads, ce qui peut éviter des appels d’E / S coûteux.
  8. Ne pas utiliser !feof(file) !

J’ai aussi posé des questions similaires ici et ici .

Essayez d’utiliser SQLITE_STATIC au lieu de SQLITE_TRANSIENT pour ces insertions.

SQLITE_TRANSIENT provoquera SQLite pour copier les données de chaîne avant de renvoyer.

SQLITE_STATIC indique que l’adresse mémoire que vous lui avez donnée sera valide jusqu’à ce que la requête ait été effectuée (ce qui est toujours le cas dans cette boucle). Cela vous permettra d’économiser plusieurs opérations d’allocation, de copie et de désallocation par boucle. Peut-être une grande amélioration.

Évitez sqlite3_clear_bindings (stmt);

Le code dans le test définit les liaisons à chaque fois, ce qui devrait suffire.

L’introduction de l’API C à partir des documents SQLite indique

Avant d’appeler sqlite3_step () pour la première fois ou immédiatement après sqlite3_reset (), l’application peut appeler l’une des interfaces sqlite3_bind () pour associer des valeurs aux parameters. Chaque appel à sqlite3_bind () remplace les liaisons précédentes sur le même paramètre

(voir: sqlite.org/cintro.html ). Il n’y a rien dans les docs de cette fonction indiquant que vous devez l’appeler en plus de simplement définir les liaisons.

Plus de détails: http://www.hoogli.com/blogs/micro/index.html#Avoid_sqlite3_clear_bindings ()

Sur des inserts en vrac

Inspiré par ce post et par la question Stack Overflow qui m’a amené ici – Est-il possible d’insérer plusieurs lignes à la fois dans une firebase database SQLite? – J’ai posté mon premier repository Git :

https://github.com/rdpoor/CreateOrUpdate

qui charge un tableau d’ActiveRecords dans les bases de données MySQL , SQLite ou PostgreSQL . Il inclut une option pour ignorer les enregistrements existants, les écraser ou générer une erreur. Mes repères rudimentaires montrent une amélioration de la vitesse de 10x par rapport aux écritures séquentielles – YMMV.

Je l’utilise dans le code de production où j’ai souvent besoin d’importer des jeux de données volumineux, et j’en suis plutôt satisfait.

Les importations en bloc semblent mieux fonctionner si vous pouvez fragmenter vos instructions INSERT / UPDATE . Une valeur d’environ 10 000 a bien fonctionné pour moi sur une table avec seulement quelques lignes, YMMV …

Si vous ne voulez que lire, la version un peu plus rapide (mais qui pourrait lire les données obsolètes) consiste à lire plusieurs connexions à partir de plusieurs threads (connexion par thread).

Trouvez d’abord les objects dans le tableau:

  SELECT COUNT(*) FROM table 

puis lisez les pages (LIMIT / OFFSET)

  SELECT * FROM table ORDER BY _ROWID_ LIMIT  OFFSET  

où et sont calculés par thread, comme ceci:

 int limit = (count + n_threads - 1)/n_threads; 

pour chaque fil:

 int offset = thread_index * limit 

Pour notre petite firebase database (200 Mo), cela a accéléré de 50 à 75% (3.8.0.2 64 bits sous Windows 7). Nos tables sont fortement non normalisées (1000-1500 colonnes, environ 100 000 lignes ou plus).

Trop peu ou trop peu de threads ne le feront pas, vous devez vous évaluer et vous profiler.

Pour nous aussi, SHAREDCACHE a ralenti les performances, donc j’ai placé manuellement PRIVATECACHE (car il était activé globalement pour nous)

Je n’ai pas obtenu de gain à partir des transactions jusqu’à ce que je soulève cache_size à une valeur plus élevée, à savoir PRAGMA cache_size=10000;

Après avoir lu ce tutoriel, j’ai essayé de l’implémenter dans mon programme.

J’ai 4-5 fichiers contenant des adresses. Chaque fichier contient environ 30 millions d’enregistrements. J’utilise la même configuration que vous proposez, mais mon nombre d’INSERT par seconde est très faible (~ 10.000 enregistrements par seconde).

Voici où votre suggestion échoue. Vous utilisez une seule transaction pour tous les enregistrements et une seule insertion sans erreur / échec. Disons que vous divisez chaque enregistrement en plusieurs insertions sur des tables différentes. Que se passe-t-il si le disque est cassé?

La commande ON CONFLICT ne s’applique pas, car si vous avez 10 éléments dans un enregistrement et que chaque élément doit être inséré dans une table différente, si l’élément 5 reçoit une erreur CONSTRAINT, alors les 4 insertions précédentes doivent également disparaître.

Alors, voici où le retour en arrière vient. Le seul problème avec le rollback est que vous perdez toutes vos insertions et commencez par le haut. Comment pouvez-vous résoudre ce problème?

Ma solution consistait à utiliser plusieurs transactions. Je commence et termine une transaction tous les 10.000 enregistrements (ne demandez pas pourquoi ce numéro, c’était le plus rapide que j’ai testé). J’ai créé un tableau d’une taille de 10.000 et y ai inséré les enregistrements réussis. Lorsque l’erreur se produit, je procède à une annulation, lance une transaction, insère les enregistrements de mon tableau, valide puis lance une nouvelle transaction après l’enregistrement interrompu.

Cette solution m’a aidé à contourner les problèmes que je rencontrais lorsque je traitais des fichiers contenant des enregistrements incorrects / en double (j’avais presque 4% de mauvais enregistrements).

L’algorithme que j’ai créé m’a aidé à réduire mon processus de 2 heures. Processus de chargement final du fichier 1h 30m qui est encore lent mais pas comparé aux 4h qu’il a initialement pris. J’ai réussi à accélérer les insertions de 10.000 / s à ~ 14.000 / s

Si quelqu’un a d’autres idées sur la façon de l’accélérer, je suis ouvert aux suggestions.

MISE À JOUR :

En plus de ma réponse ci-dessus, vous devez garder à l’esprit que les insertions par seconde dépendent du disque dur que vous utilisez également. Je l’ai testé sur 3 PC différents avec différents disques durs et j’ai connu des différences considérables dans les temps. PC1 (1h30), PC2 (6 heures) PC3 (14 heures), alors je me suis demandé pourquoi.

Après deux semaines de recherche et de vérification de plusieurs ressources: Disque dur, Ram, Cache, j’ai découvert que certains parameters de votre disque dur pouvaient affecter le taux d’E / S. En cliquant sur les propriétés de votre lecteur de sortie souhaité, vous pouvez voir deux options dans l’onglet général. Opt1: Compresse ce lecteur, Opt2: permet d’indexer les fichiers de ce lecteur.

En désactivant ces deux options, les 3 PC prennent à peu près le même temps pour terminer (1 heure et 20 à 40 minutes). Si vous rencontrez des insertions lentes, vérifiez si votre disque dur est configuré avec ces options. Cela vous permettra d’économiser beaucoup de temps et de maux de tête en essayant de trouver la solution

La réponse à votre question est que sqlite3 plus récent a amélioré les performances, utilisez-le.

Cette réponse Pourquoi SQLAlchemy est-il inséré avec sqlite 25 fois plus lentement qu’avec sqlite3 directement? par SqlAlchemy Orm Author a inséré 100k en 0,5 seconde, et j’ai vu des résultats similaires avec python-sqlite et SqlAlchemy. Ce qui m’amène à penser que la performance s’est améliorée avec sqlite3

Il y a une grande forme de conférence Paul Betts sur la façon dont il a fait si vite Ck akavache : https://www.youtube.com/watch?v=j7WnQhwBwqA

Peut-être que vous pouvez trouver des indices pour vous. Il est trop long de faire un bref résumé ici

Depuis la version 3.24, SQLite prend en charge l’instruction UPSERT.

Voir “SQL as Understood By SQLite” 1 Lorsqu’une ligne n’existe pas, elle est insérée autrement mise à jour. D’autres moteurs appellent cette fusion.

Utilisez les transactions de début et les transactions de fin pour insérer dans le lot.