web-dev-qa-db-fra.com

Une requête sqlite3 plus rapide au rendez-vous? J'ai besoin de traiter 1 million de lignes le plus rapidement possible

Quel est le moyen le plus rapide de lire une table sqlite3 dans Golang?

package main

import (
    "fmt"
    "database/sql"
    _ "github.com/mattn/go-sqlite3"
    "log"
    "time"
)

func main() {
    start := time.Now()

    db, err := sql.Open("sqlite3", "/Users/robertking/go/src/bitbucket.org/thematicanalysis/optimization_test/robs.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    rows, err := db.Query("select * from data")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    for rows.Next() {
    }
    err = rows.Err()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(time.Since(start))
}

Cela prend 8 secondes en Go parce que .Next est lent . En python, une fetchall ne prend que 4 secondes! Je suis en train de réécrire dans GO pour gagner en performance et non en perdre.

Voici le code python, je n'ai pas trouvé d'équivalent de fetchall dans go:

import time

start = time.time()
import sqlite3
conn = sqlite3.connect('/Users/robertking/go/src/bitbucket.org/thematicanalysis/optimization_test/robs.db')
c = conn.cursor()
c.execute("SELECT * FROM data")
x = c.fetchall()
print time.time() - start

Edit: ajouter une prime. Je lis les données dans go, python et C, voici les résultats. Ne voulez pas utiliser C, mais restera avec python si GO n'est pas plus rapide.:

py: 2.45s
go: 2.13s (using github.com/mxk/go-sqlite/sqlite3 instead of github.com/mattn/go-sqlite3)
c:  0.32s

Je me sens comme aller devrait être plus proche du côté c de la chose? Quelqu'un sait comment faire plus vite? est-il possible d'éviter le mutex avec le mode lecture seule?

modifier:

Il semble que toutes les implémentations de sqlite3 soient lentes (trop de réflexions et trop d'appels à la conversion pour obtenir des conversions). Donc, je vais devoir écrire ma propre interface.

Voici le schéma:

CREATE TABLE mytable
(
  c0   REAL,
  c1   INTEGER,
  c15  TEXT,
  c16  TEXT,
  c17  TEXT,
  c18  TEXT,
  c19  TEXT,
  c47  TEXT,
  c74  REAL DEFAULT 0,
  c77  TEXT,
  c101 TEXT,
  c103 TEXT,
  c108 TEXT,
  c110 TEXT,
  c125 TEXT,
  c126 TEXT,
  c127 REAL DEFAULT 0,
  x    INTEGER
    PRIMARY KEY
);

et la requête est dynamique mais habituellement, quelque chose comme ceci:

SELECT c77,c77,c125,c126,c127,c74 from mytable

modifier:

on dirait que je vais bifurquer l'implémentation de sqlite3 et faire quelques méthodes qui se concentrent sur la performance, 

voici un exemple de code beaucoup plus rapide:.

package main


/*
 #cgo LDFLAGS: -l sqlite3

#include "sqlite3.h"
*/
import "C"

import (
    //"database/sql"
    "log"
    "reflect"
    "unsafe"
)

type Row struct {
    v77 string
    v125 string
    v126 string
    v127 float64
    v74 float64
}

// cStr returns a pointer to the first byte in s.
func cStr(s string) *C.char {
    h := (*reflect.StringHeader)(unsafe.Pointer(&s))
    return (*C.char)(unsafe.Pointer(h.Data))
}

func main() {
    getDataFromSqlite()
}

func getDataFromSqlite() {
    var db *C.sqlite3
    name := "../data_dbs/all_columns.db"
    rc := C.sqlite3_open_v2(cStr(name+"\x00"), &db, C.SQLITE_OPEN_READONLY, nil)

  var stmt *C.sqlite3_stmt;
  rc = C.sqlite3_prepare_v2(db, cStr("SELECT c77,c125,c126,c127,c74 from data\x00"), C.int(-1), &stmt, nil);
  rc = C.sqlite3_reset(stmt);

    var result C.double
    result = 0.0
    rc = C.sqlite3_step(stmt)
    for rc == C.SQLITE_ROW {
    C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_text(stmt, 0))))
    C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_text(stmt, 1))))
    C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_text(stmt, 2))))
    C.sqlite3_column_double(stmt, 3)
    result += C.sqlite3_column_double(stmt, 4)
        rc = C.sqlite3_step(stmt)
  }
    log.Println(result)
}
23
robert king

Introduction

Mon hypothèse était que nous avons un problème avec la façon dont la performance est mesurée ici, alors j'ai écrit un petit programme Go pour générer des enregistrements et les enregistrer dans une base de données SQLite ainsi qu'une implémentation Python et Go d'une petite tâche à effectuer sur ces enregistrements. .

Vous pouvez trouver le référentiel correspondant à https://github.com/mwmahlberg/sqlite3perf

Le modèle de données

Les enregistrements générés sont constitués de 

Le schéma de la table est relativement simple:

sqlite> .schema
CREATE TABLE bench (ID int PRIMARY KEY ASC, Rand TEXT, hash TEXT);

Tout d’abord, j’ai généré 1,5 million d’enregistrements et passé l’aspirateur dans la base de

$ ./sqlite3perf generate -r 1500000 -v

Ensuite, j'ai appelé l'implémentation Go pour ces enregistrements de 1,5M. L’implémentation de Go ainsi que celle de Python remplissent essentiellement la même tâche simple:

  1. Lire toutes les entrées de la base de données.
  2. Pour chaque ligne, décodez la valeur aléatoire de hex, puis créez un SHA256 à partir du résultat.
  3. Comparez la chaîne hex SHA256 générée à celle stockée dans la base de données
  4. Si elles correspondent, continuez, sinon pause.

Hypothèses

Mon hypothèse était explicitement que Python effectuait un certain type de chargement paresseux et/ou éventuellement d’exécution de la requête SQL.

Les resultats

Aller mise en œuvre

$ ./sqlite3perf bench
2017/12/31 15:21:48 bench called
2017/12/31 15:21:48 Time after query: 4.824009ms
2017/12/31 15:21:48 Beginning loop
2017/12/31 15:21:48 Acessing the first result set 
    ID 0,
    Rand: 6a8a4ad02e5e872a,
    hash: 571f1053a7c2aaa56e5c076e69389deb4db46cc08f5518c66a4bc593e62b9aa4
took 548.32µs
2017/12/31 15:21:50 641,664 rows processed
2017/12/31 15:21:52 1,325,186 rows processed
2017/12/31 15:21:53 1,500,000 rows processed
2017/12/31 15:21:53 Finished loop after 4.519083493s
2017/12/31 15:21:53 Average 3.015µs per record, 4.523936078s overall

Notez les valeurs de "time after query" (le temps nécessaire pour que la commande de requête revienne) et le temps nécessaire pour accéder au premier jeu de résultats après l'itération sur le jeu de résultats démarré.

Implémentation Python

$ python bench.py 
12/31/2017 15:25:41 Starting up
12/31/2017 15:25:41 Time after query: 1874µs
12/31/2017 15:25:41 Beginning loop
12/31/2017 15:25:44 Accessing first result set
    ID: 0
    Rand: 6a8a4ad02e5e872a
    hash: 571f1053a7c2aaa56e5c076e69389deb4db46cc08f5518c66a4bc593e62b9aa4
took 2.719312 s
12/31/2017 15:25:50 Finished loop after 9.147431s
12/31/2017 15:25:50 Average: 6.098µs per record, 0:00:09.149522 overall

Encore une fois, notez la valeur pour "temps après requête" et le temps nécessaire pour accéder au premier jeu de résultats.

Résumé

L’implémentation de Go a mis un certain temps à revenir après l’envoi de la requête SELECT, tandis que Python semblait était extrêmement rapide en comparaison. Cependant, depuis le temps nécessaire pour accéder au premier jeu de résultats, nous pouvons constater que l'implémentation de Go est 500 fois plus rapide pour accéder au premier jeu de résultats (5.372329ms vs 2719.312ms) et environ deux fois plus rapide pour la tâche. à portée de main comme l'implémentation Python.

Remarques

  • Afin de prouver l'hypothèse selon laquelle Python effectue un chargement différé sur le jeu de résultats, il fallait accéder à chaque ligne et colonne afin de s'assurer que Python est forcé de lire la valeur de la base de données.
  • J'ai choisi une tâche de hachage parce que, vraisemblablement, la mise en œuvre de SHA256 est hautement optimisée dans les deux langues.

Conclusion

Python semble effectuer un chargement paresseux des jeux de résultats et même éventuellement n'exécuter une requête que si le jeu de résultats correspondant est réellement utilisé. Dans ce scénario simulé, le pilote SQLite de Mattn pour Go dépasse celui de Python d'environ 100% et de plusieurs ordres de grandeur, en fonction de ce que vous souhaitez faire.

Edit: Pour accélérer le traitement, implémentez votre tâche dans Go. Bien que l'envoi de la requête prenne plus de temps, accéder aux différentes lignes du jeu de résultats est bien plus rapide. Je suggérerais de commencer avec un petit sous-ensemble de vos données, par exemple 50 000 enregistrements. Ensuite, pour améliorer encore votre code, utilisez profiling pour identifier vos goulots d'étranglement. En fonction de ce que vous voulez faire pendant le traitement, les pipelines peuvent par exemple vous aider, mais il est difficile de dire comment améliorer la vitesse de traitement de la tâche à exécuter sans un code réel ou une description détaillée.

19
Markus W Mahlberg

Analyse des valeurs des lignes extraites exemple de lecture étape 10 .
Puisque Query() & QueryRow() renvoie un pointeur sur les lignes et un pointeur sur Row de la requête de base de données, nous pouvons utiliser la structure Scan () sur les lignes et les lignes pour obtenir un accès aux valeurs de la structure des lignes.

for rows.Next() {

 var empID sql.NullInt64
 var empName sql.NullString
 var empAge sql.NullInt64
 var empPersonId sql.NullInt64

 if err := rows.Scan(&empID, &empName, &empAge, 
                           &empPersonId); err != nil {
          log.Fatal(err)
 }

 fmt.Printf("ID %d with personID:%d & name %s is age %d\n",       
                   empID.Int64, empPersonId.Int64, empName.String, empAge.Int64)
}

Nous avons également utilisé la fonction Scan () de la structure Row. Scan () est la seule méthode déclarée dans la structure de lignes.

func (r *Row) Scan(dest ...interface{}) error
1
user5377037