it-swarm.com.de

Kann SQLite 90 Millionen Datensätze verarbeiten?

Oder sollte ich einen anderen Hammer verwenden, um dieses Problem zu beheben.

Ich habe einen sehr einfachen Anwendungsfall für das Speichern von Daten, effektiv eine spärliche Matrix, die ich versucht habe, in einer SQLite-Datenbank zu speichern. Ich habe eine Tabelle erstellt:

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

an den meisten Tagen im Jahr füge ich viele Daten ein (800 Elemente alle 10 Minuten, 45 Mal pro Tag). Der Tupel von (Id1, Zeit) wird immer eindeutig sein. 

Der Zeitwert ist Sekunden seit der Epoche und wird immer größer. Die id1 ist für alle praktischen Zwecke eine zufällige ganze Zahl. Es gibt wahrscheinlich nur 20000 eindeutige IDs. 

Ich möchte dann auf alle Werte zugreifen, bei denen id1 == someid ist, oder auf alle Elemente zugreifen, für die irgendwann der Zeitpunkt == gilt. Bei meinen Tests mit der neuesten SQLite über die C-Schnittstelle unter Linux dauert eine Suche nach einer davon (oder einer beliebigen Variante dieser Suche) etwa 30 Sekunden. Dies ist für meinen Anwendungsfall nicht schnell genug. 

Ich habe versucht, einen Index für die Datenbank zu definieren, aber dies verlangsamte das Einfügen auf völlig undurchführbare Geschwindigkeiten (möglicherweise habe ich das falsch gemacht ...)

Die obige Tabelle führt zu einem sehr langsamen Zugriff auf alle Daten. Meine Frage ist:

  • Ist SQLite dafür komplett das falsche Werkzeug?
  • Kann ich Indizes definieren, um die Abläufe erheblich zu beschleunigen?
  • Soll ich dafür etwas wie HDF5 anstelle von SQL verwenden?

Bitte entschuldigen Sie mein grundlegendes Verständnis von SQL!

Vielen Dank

Ich füge ein Codebeispiel hinzu, das zeigt, wie sich die Einfügungsgeschwindigkeit bei der Verwendung von Indizes auf eine Durchforstung verlangsamt. Wenn die Anweisungen zum Erstellen des Index vorhanden sind, dauert der Code 19 Minuten. Ohne das läuft es in 18 Sekunden. 


#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

Fügen Sie alle 800 Elemente gleichzeitig ein? Wenn Sie die Einfügungen innerhalb einer Transaktion durchführen, wird der Vorgang erheblich beschleunigt.

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

SQLite kann sehr große Datenbanken verarbeiten. Siehe http://www.sqlite.org/limits.html

29
Robert Harvey

Ich habe mir Ihren Code angesehen und denke, Sie könnten ihn mit den Anweisungen prepare und finalize übertreiben. Ich bin auf keinen Fall ein SQLite-Experte, aber es ist mit erheblichem Aufwand verbunden, jedes Mal eine Anweisung zu erstellen, wenn die Schleife durchlaufen wird.

Zitieren von der SQLite-Website:

Nachdem eine vorbereitete Anweisung durch einen oder mehrere Aufrufe von sqlite3_step() ausgewertet wurde, kann sie zurückgesetzt werden, um durch einen Aufruf von sqlite3_reset() erneut ausgewertet zu werden. Wenn Sie sqlite3_reset() für eine vorhandene vorbereitete Anweisung verwenden, anstatt eine neue vorbereitete Anweisung zu erstellen, werden unnötige Aufrufe von sqlite3_prepare() vermieden. In vielen SQL-Anweisungen entspricht die Zeit, die zum Ausführen von sqlite3_prepare() benötigt wird, der Zeit, die von sqlite3_step() benötigt wird, oder überschreitet diese. Das Vermeiden von Aufrufen von sqlite3_prepare() kann zu einer erheblichen Leistungsverbesserung führen.

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

Anstatt jedes Mal eine neue Anweisung vorzubereiten, können Sie versuchen, neue Werte an Ihre vorhandene Anweisung zu binden .

Trotzdem denke ich, dass die Indizes der eigentliche Schuldige sein könnten, da die Zeit immer länger wird, je mehr Daten hinzugefügt werden. Ich bin schon gespannt, wo ich am Wochenende ein paar Tests machen werde.

9
Robert Harvey

Beantwortung meiner eigenen Frage nur als Ort, um einige Details anzugeben:

Es stellt sich (wie oben richtig angedeutet) heraus, dass die Indexerstellung der langsame Schritt ist, und jedes Mal, wenn ich eine weitere Transaktion von Einfügungen vornehme, wird der Index aktualisiert, was einige Zeit in Anspruch nimmt. Meine Lösung lautet: (A) Erstellen Sie die Datentabelle (B) füge alle meine historischen Daten ein (mehrere Jahre) (C) Erstellen Sie die Indizes

Nun sind alle Lookups usw. sehr schnell und sqlite leistet einen tollen Job. Nachfolgende tägliche Aktualisierungen benötigen nun einige Sekunden, um nur 800 Datensätze einzufügen. Dies ist jedoch kein Problem, da sie nur alle 10 Minuten ausgeführt werden. 

Vielen Dank an Robert Harvey und Maxwellb für die Hilfe/Vorschläge/Antworten oben. 

6
Brian O'Kennedy

Da wir wissen, dass die Erfassung Ihrer Daten schnell ist, wenn es keinen Index für die Tabelle gibt, könnte dies in der Tat funktionieren:

  1. Erfassen Sie die 800 Werte in einer temporären Tabelle ohne Index.

  2. Kopieren Sie die Datensätze in die Haupttabelle (die Indizes enthält), indem Sie die Form von INSERT INTO verwenden, die eine SELECT-Anweisung annimmt.

  3. Löschen Sie die Datensätze aus der temporären Tabelle.

Diese Technik basiert auf der Theorie, dass das INSERT INTO, das eine SELECT-Anweisung verwendet, schneller ist als die Ausführung einzelner INSERTs. 

Schritt 2 kann im Hintergrund ausgeführt werden, indem das Asynchronous Module verwendet wird, wenn es sich immer noch als etwas langsam erweist. Dies nutzt die wenigen Ausfallzeiten zwischen den Captures.

4
Robert Harvey

Erwägen Sie die Verwendung einer Tabelle für neue Einfügungen des angegebenen Tages ohne Index. Führen Sie am Ende eines jeden Tages ein Skript aus, das Folgendes ausführt:

  1. Fügen Sie neue Werte aus new_table in master_table ein
  2. Löschen Sie die neue_Tabelle für den nächsten Verarbeitungstag

Wenn Sie in O (log n) nach historischen Daten suchen und in O (n) nach aktuellen Daten suchen können, sollte dies einen netten Kompromiss darstellen.

3
maxwellb

Ich kann es nicht an Ihren Angaben erkennen, aber wenn das ID-Feld immer größer wird und das Zeitfeld YYYYMMDD für die Eindeutigkeit enthält und auch immer steigt, und Sie entweder ID-Suchen oder Zeit-Suchen durchführen, dann die einfachste Nicht-Datenbank Die Lösung wäre, einfach alle Datensätze an eine Textdatei oder eine Binärdatei mit festem Feld anzufügen (da sie in der Reihenfolge "sortiert" generiert werden) und den Code für eine binäre Suche nach den gewünschten Datensätzen zu verwenden (z. B. den ersten Datensatz mit suchen) die ID oder Zeit von Interesse, dann schrittweise durch den gewünschten Bereich).

2
joe snyder

Die theoretische maximale Anzahl von Zeilen in einer Tabelle beträgt 2 ^ 64 (18446744073709551616 oder etwa 1,8e + 19). Dieses Limit ist nicht erreichbar, da zuerst die maximale Datenbankgröße von 140 Terabyte erreicht wird. Eine 140-Terabyte-Datenbank kann nicht mehr als ungefähr 1e + 13 Zeilen enthalten, und nur dann, wenn keine Indizes vorhanden sind und jede Zeile nur sehr wenige Daten enthält.

0
Dwivedi Ji

Fügen Sie beim Erstellen großer SQLite-Datenbanken immer so viele Daten wie möglich ein, bevor Sie die Indizes erstellen. Dies wird um ein Vielfaches schneller ausgeführt, als wenn Sie die Indizes vor dem Einfügen der Daten erstellen.

0
Phil Goetz