47 votes

SQLite peut-il gérer 90 millions d'enregistrements?

Ou devrais-je utiliser un autre marteau pour résoudre ce problème.

J'ai une utilisation très simple-cas pour le stockage des données, de manière efficace d'une matrice creuse, que j'ai tenté de stocker dans une base de données SQLite. J'ai créé un tableau:

create TABLE data ( id1 INTEGER KEY, timet INTEGER KEY, value REAL )

dans lequel j'ai insérer un grand nombre de données, (800 éléments, toutes les 10 minutes, 45 fois par jour), la plupart des jours de l'année. Le n-uplet de (id1,préventive) sera toujours unique.

L'préventive valeur est en secondes depuis l'epoch, et sera toujours en augmentation. Le id1 est, à toutes fins pratiques, un entier aléatoire. Il n'y a probablement que 20000 id unique bien.

Alors je dirais comme d'accéder à toutes les valeurs où id1==someid ou accès à tous les éléments où préventive==de temps en temps. Sur mes tests à l'aide de la dernière SQLite via l'interface C sur Linux, recherche pour l'un de ces (ou toute variante de cette recherche) prend environ 30 secondes, ce qui n'est pas assez rapide pour mon cas d'utilisation.

J'ai essayé de définir un index de la base de données, mais cela a ralenti le curseur d'insertion complètement impraticable vitesses (j'ai peut-être fait de cette manière incorrecte si...)

Le tableau ci-dessus conduit à de très lent d'accès pour toutes les données. Ma question est:

  • Est SQLite complètement le mauvais outil pour cela?
  • Puis-je définir des indices pour accélérer les choses de manière significative?
  • Dois-je utiliser quelque chose comme HDF5 au lieu de SQL pour cela?

Veuillez excuser mon très compréhension de base de SQL!

Merci

- Je inclure un exemple de code qui montre comment l'insertion de la vitesse ralentit énormément lors de l'utilisation d'indices. Avec la 'création d'un" indice de déclarations en place, le code prend en 19 minutes. Sans cela, il s'exécute en 18 secondes.


#include <iostream>
#include <sqlite3.h>

void checkdbres( int res, int expected, const std::string msg ) 
{
  if (res != expected) { std::cerr << msg << std::endl; exit(1); } 
}

int main(int argc, char **argv)
{
  const size_t nRecords = 800*45*30;

  sqlite3      *dbhandle = NULL;
  sqlite3_stmt *pStmt = NULL;
  char statement[512];

  checkdbres( sqlite3_open("/tmp/junk.db", &dbhandle ), SQLITE_OK, "Failed to open db");

  checkdbres( sqlite3_prepare_v2( dbhandle, "create table if not exists data ( issueid INTEGER KEY, time INTEGER KEY, value REAL);", -1, & pStmt, NULL ), SQLITE_OK, "Failed to build create statement");
  checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute insert statement" );
  checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize insert");
  checkdbres( sqlite3_prepare_v2( dbhandle, "create index issueidindex on data (issueid );", -1, & pStmt, NULL ), SQLITE_OK, "Failed to build create statement");
  checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute insert statement" );
  checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize insert");
  checkdbres( sqlite3_prepare_v2( dbhandle, "create index timeindex on data (time);", -1, & pStmt, NULL ), SQLITE_OK, "Failed to build create statement");
  checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute insert statement" );
  checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize insert");

  for ( size_t idx=0; idx < nRecords; ++idx)
  {
    if (idx%800==0)
    {
      checkdbres( sqlite3_prepare_v2( dbhandle, "BEGIN TRANSACTION", -1, & pStmt, NULL ), SQLITE_OK, "Failed to begin transaction");
      checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute begin transaction" );
      checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize begin transaction");
      std::cout << "idx " << idx << " of " << nRecords << std::endl;
    }

    const size_t time = idx/800;
    const size_t issueid = idx % 800;
    const float value = static_cast<float>(rand()) / RAND_MAX;
    sprintf( statement, "insert into data values (%d,%d,%f);", issueid, (int)time, value );
    checkdbres( sqlite3_prepare_v2( dbhandle, statement, -1, &pStmt, NULL ), SQLITE_OK, "Failed to build statement");
    checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute insert statement" );
    checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize insert");

    if (idx%800==799)
    {
      checkdbres( sqlite3_prepare_v2( dbhandle, "END TRANSACTION", -1, & pStmt, NULL ), SQLITE_OK, "Failed to end transaction");
      checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute end transaction" );
      checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize end transaction");
    }
  }

  checkdbres( sqlite3_close( dbhandle ), SQLITE_OK, "Failed to close db" ); 
}

31voto

Robert Harvey Points 103562

Insérez-vous tous les 800 éléments à la fois? Si vous l'êtes, faire les insertions dans une transaction accélérera considérablement le processus.

Voir http://www.sqlite.org/faq.html#q19

SQLite peut gérer de très grandes bases de données. Voir http://www.sqlite.org/limits.html

11voto

Brian O'Kennedy Points 824

Pour répondre à ma propre question juste comme un endroit pour mettre quelques détails:

Il s'avère (comme l'a très justement suggéré ci-dessus) que la création de l'index est l'étape lente, et chaque fois que je fais une autre opération de plaquettes, l'index est mis à jour, ce qui prend du temps. Ma solution est de: (A) créer la table de données (B) insérer toutes mes données historiques (plusieurs années) (C) créer l'index

Maintenant, toutes les recherches etc sont vraiment rapide et sqlite fait un excellent travail. Ultérieure des mises à jour quotidiennes maintenant, prenez quelques secondes pour insérer seulement 800 enregistrements, mais c'est pas un problème, car il ne circule toutes les 10 minutes.

Merci à Robert Harvey et maxwellb pour l'aide/suggestions/réponses ci-dessus.

9voto

Robert Harvey Points 103562

J'ai regardé ton code, et je pense que vous pourriez être en faire trop avec l' prepare et finalize des déclarations. Je ne suis en aucun cas SQLite expert, mais il doit y avoir une surcharge importante dans la préparation d'une déclaration à chaque passage dans la boucle.

Citant le SQLite site web:

Après une instruction préparée a été évalué par un ou plusieurs appels à sqlite3_step(), il peut être remis à zéro dans afin d'être évalués à nouveau par un appel d' sqlite3_reset(). À l'aide de sqlite3_reset() sur un déclaration préparée à l'avance plutôt la création d'un nouvelle déclaration préparée à l'avance évite les appels inutiles à sqlite3_prepare(). Dans de nombreux SQL déclarations, le temps nécessaire à l'exécution de sqlite3_prepare() est égal ou supérieur à le temps nécessaire en sqlite3_step(). Afin d'éviter les appels à sqlite3_prepare() peut entraîner une amélioration significative des performances.

http://www.sqlite.org/cintro.html

Dans votre cas, plutôt que de préparer une nouvelle déclaration, à chaque fois, vous pourriez essayer de liaison de nouvelles valeurs à votre déclaration.

Cela dit, je pense que l'index pourrait être le véritable coupable, puisque le temps ne cesse d'augmenter à mesure que vous ajoutez plus de données. Je suis assez curieux à propos de cette où j'ai l'intention de faire quelques tests sur le week-end.

6voto

Robert Harvey Points 103562

Puisque nous savons que la capture de vos données est très rapide quand il n'y a pas d'index sur la table, ce qui pourrait effectivement le travail est ceci:

  1. La Capture de l'800 les valeurs dans une table temporaire avec aucun indice.

  2. Copier les enregistrements de la table principale (contenant des index) à l'aide de la forme de l'INSÉRER DANS qui prend une instruction SELECT.

  3. Supprimer les enregistrements de la table temporaire.

Cette technique est basée sur la théorie que l'INSERTION DANS qui prend une instruction SELECT est plus rapide que l'exécution individuelle des INSERTs.

Étape 2 peut être exécuté en arrière-plan à l'aide de la Asynchrones Module, si elle s'avère être encore un peu lent. Cette méthode tire parti des morceaux de temps d'arrêt entre les captures.

5voto

maxwellb Points 3713

Envisagez d'utiliser une table pour les nouvelles insertions du jour donné, sans index. Ensuite, à la fin de chaque journée, exécutez un script qui:

  1. Insérer de nouvelles valeurs de new_table dans master_table
  2. Effacez la nouvelle table pour le jour suivant du traitement

Si vous pouvez effectuer des recherches sur les données historiques dans O (log n) et des recherches sur les données d'aujourd'hui dans O (n), cela devrait fournir un bon compromis.

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