web-dev-qa-db-fra.com

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

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

J'ai un cas d'utilisation très simple pour stocker des données, une matrice creuse, que j'ai essayé de stocker dans une base de données SQLite. J'ai créé une table:

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

dans lequel j'insère beaucoup de données (800 éléments toutes les 10 minutes, 45 fois par jour), la plupart des jours de l'année. Le tuple de (id1, horaire) sera toujours unique. 

La valeur de la minuterie est exprimée en secondes depuis l’époque et augmentera toujours. L'id1 est, à toutes fins pratiques, un entier aléatoire. Cependant, il n’ya probablement que 20000 identifiants uniques. 

Je voudrais ensuite accéder à toutes les valeurs où id1 == someid ou accéder à tous les éléments où timing == parfois. Lors de mes tests utilisant la dernière version de SQLite via l’interface C sous Linux, une recherche de l’une d’entre elles (ou de 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 pour la base de données, mais cela a ralenti l'insertion à des vitesses complètement impraticables (j'aurais peut-être mal fait cela cependant ...)

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

  • SQLite est-il complètement le mauvais outil pour cela?
  • Puis-je définir des indices pour accélérer considérablement les choses?
  • Devrais-je utiliser quelque chose comme HDF5 au lieu de SQL pour cela?

S'il vous plaît excuser ma compréhension très élémentaire de SQL!

Merci

J'inclus un exemple de code qui montre comment la vitesse d'insertion ralentit jusqu'à une analyse lors de l'utilisation d'index. Avec les instructions 'create index' en place, le code prend 19 minutes. Sans cela, il fonctionne 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" ); 
}

38
Brian O'Kennedy

Insérez-vous tous les 800 éléments en même temps? Si vous le faites, effectuer 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

29
Robert Harvey

J'ai examiné votre code et je pense que vous pourriez en faire trop avec les déclarations prepare et finalize. Je ne suis en aucun cas un expert de SQLite, mais il faut beaucoup de temps pour préparer une déclaration à chaque fois dans la boucle.

Citant le site web SQLite:

Après une déclaration préparée a été évalué par un ou plusieurs appels à sqlite3_step(), il peut être réinitialisé dans afin d'être à nouveau évalué par un appel à sqlite3_reset(). En utilisant sqlite3_reset() sur un .__ existant. déclaration préparée plutôt créer un nouvelle déclaration préparée évite appels inutiles à sqlite3_prepare(). Dans beaucoup de SQL déclarations, le temps nécessaire pour exécuter sqlite3_prepare() est égal ou supérieur à le temps requis par sqlite3_step(). Donc, en évitant les appels à sqlite3_prepare() peut entraîner un 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 pouvez essayer de lier de nouvelles valeurs à votre déclaration existante .

Tout cela étant dit, je pense que les index pourraient être le véritable coupable, car le temps ne cesse d'augmenter à mesure que vous ajoutez de nouvelles données. Je suis assez curieux de savoir où je prévois de faire des tests ce week-end. 

9
Robert Harvey

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

Il s'avère (comme suggéré correctement ci-dessus) que la création d'index est une étape lente, et chaque fois que je fais une autre transaction d'insertions, l'index est mis à jour, ce qui prend un certain 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 les index

Désormais, toutes les recherches, etc., sont très rapides et sqlite fait un excellent travail. Les mises à jour quotidiennes ultérieures prennent maintenant quelques secondes pour insérer seulement 800 enregistrements, mais ce n'est pas un problème car elles ne fonctionnent que toutes les 10 minutes environ. 

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

6
Brian O'Kennedy

Comme nous savons que la capture de vos données est rapide lorsqu'il n'y a pas d'index dans la table, voici ce qui pourrait fonctionner:

  1. Capturez les 800 valeurs dans une table temporaire sans index.

  2. Copiez les enregistrements dans la table principale (contenant les index) en utilisant le format INSERT INTO qui prend une instruction SELECT.

  3. Supprimer les enregistrements de la table temporaire.

Cette technique est basée sur la théorie selon laquelle le INSERT INTO prenant une instruction SELECT est plus rapide que l'exécution de différents INSERT. 

L'étape 2 peut être exécutée en arrière-plan à l'aide de Module asynchrone , si cela s'avère toujours un peu lent. Cela tire parti des temps d'arrêt entre les captures.

4
Robert Harvey

Pensez à utiliser une table pour les nouvelles insertions du jour donné, sans index. Puis, à la fin de chaque journée, lancez un script qui:

  1. Insérer les nouvelles valeurs de new_table dans master_table
  2. Efface la new_table pour le jour suivant du traitement

Si vous pouvez effectuer des recherches sur des données historiques dans O (log n) et des données actuelles dans O (n), cela devrait constituer un compromis intéressant.

3
maxwellb

D'après vos spécifications, je ne saurais le dire, mais si le champ ID est en augmentation constante et que le champ heure inclut YYYYMMDD pour son caractère unique et qu'il est également en constante augmentation, et que vous effectuez des recherches d'identifiant ou de recherche temporelle, le système de base de données non-base le plus simple La solution consisterait simplement à ajouter tous les enregistrements à un fichier texte ou binaire à champ fixe (car ils sont générés dans un ordre "trié") et à utiliser du code pour effectuer une recherche binaire des enregistrements souhaités (par exemple, rechercher le premier enregistrement avec l’ID ou l’heure d’intérêt, puis passez en séquence dans la plage souhaitée).

2
joe snyder

Le nombre maximum théorique de lignes dans une table est 2 ^ 64 (18446744073709551616 ou environ 1.8e + 19). Cette limite est inaccessible car la taille maximale de la base de données de 140 téraoctets sera atteinte en premier. Une base de données de 140 téraoctets ne peut contenir plus d’environ 1e + 13 lignes, et seulement s’il n’ya pas d’index et que chaque ligne contient très peu de données.

0
Dwivedi Ji

Lors de la construction de bases de données SQLite volumineuses, insérez toujours le plus de données possible avant de créer les index. Cela s'exécutera beaucoup plus rapidement que si vous créez les index avant d'insérer les données.

0
Phil Goetz