SQLite очень медленно происходит вставка в таблицу

Рейтинг: 0Ответов: 1Опубликовано: 17.03.2023

Почему вставка в таблицу происходит очень медленно. Для примера я решил вставить 1000 записей, что по времени вылилось ~1 минуту? Это вообще как? Что я делаю не так? Вот пример:

package main

import (
    "database/sql"
    "log"

    _ "github.com/mattn/go-sqlite3"
)

type record struct {
    field1 string
    field2 string
    field3 string
}

func main() {
    db, err := sql.Open("sqlite3", "./data.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    OpenCreateTable(db)

    for i := 0; i < 1000; i++ {
        if err := Insert(db, record{"One", "Two", "Three"}); err != nil {
            log.Fatal(err)
        }
    }
}

func OpenCreateTable(db *sql.DB) error {
    stmt, err := db.Prepare(`CREATE TABLE IF NOT EXISTS records (id INTEGER PRIMARY KEY, field1 TEXT, field2 TEXT, field3 TEXT);`)
    if err != nil {
        return err
    }
    if _, err = stmt.Exec(); err != nil {
        return err
    }
    return nil
}

func Insert(db *sql.DB, r record) error {
    stmt, err := db.Prepare(`INSERT INTO records (field1, field2, field3) VALUES (?, ?, ?)`)
    if err != nil {
        return err
    }
    if _, err = stmt.Exec(r.field1, r.field2, r.field3); err != nil {
        return err
    }
    return nil
}

Конфиг компа:
Процессор Intel(R) Core(TM) i7-6800K CPU @ 3.40GHz, 3401 МГц, ядер: 6, логических процессоров: 12
16Gb DDR3
ST1000DM010-2EP102

Ответы

▲ 3Принят

Оберните вашу последовательность вставок в транзакцию

package main

import (
    "database/sql"
    "fmt"
    "log"
    "time"

    _ "github.com/mattn/go-sqlite3"
)

type record struct {
    field1 string
    field2 string
    field3 string
}

func main() {
    db, err := sql.Open("sqlite3", "./data.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    start := time.Now()
    OpenCreateTable(db)
    if err := Begin(db); err != nil {
        log.Fatal(err)
    }

    for i := 0; i < 1000; i++ {
        if err := Insert(db, record{"One", "Two", "Three"}); err != nil {
            log.Fatal(err)
        }
    }
    Commit(db)
    dt := time.Now().Sub(start)
    fmt.Println("Duration: ", dt)
}

func OpenCreateTable(db *sql.DB) error {
    stmt, err := db.Prepare(`CREATE TABLE IF NOT EXISTS records (id INTEGER PRIMARY KEY, field1 TEXT, field2 TEXT, field3 TEXT);`)
    if err != nil {
        return err
    }
    if _, err = stmt.Exec(); err != nil {
        return err
    }
    return nil
}

func Insert(db *sql.DB, r record) error {
    stmt, err := db.Prepare(`INSERT INTO records (field1, field2, field3) VALUES (?, ?, ?)`)
    if err != nil {
        return err
    }
    if _, err = stmt.Exec(r.field1, r.field2, r.field3); err != nil {
        return err
    }
    return nil
}

func Begin(db *sql.DB) error {
    stmt, err := db.Prepare(`BEGIN`)
    if err != nil {
        return err
    }
    if _, err = stmt.Exec(); err != nil {
        return err
    }
    return nil
}

func Commit(db *sql.DB) error {
    stmt, err := db.Prepare(`COMMIT`)
    if err != nil {
        return err
    }
    if _, err = stmt.Exec(); err != nil {
        return err
    }
    return nil
}

У меня получилось 25-30 миллисекунд.

UPDATE

Если вынести Prepare для INSERT во внешний код, то ускорение практически в два раза.

package main

import (
    "database/sql"
    "fmt"
    "log"
    "time"

    _ "github.com/mattn/go-sqlite3"
)

type record struct {
    field1 string
    field2 string
    field3 string
}

var (
    insertStmt *sql.Stmt
)

func main() {
    db, err := sql.Open("sqlite3", "./data.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    start := time.Now()
    OpenCreateTable(db)
    if err := Begin(db); err != nil {
        log.Fatal(err)
    }

    insertStmt, err = db.Prepare(`INSERT INTO records (field1, field2, field3) VALUES (?, ?, ?)`)
    if err != nil {
        panic(err)
    }
    for i := 0; i < 1000; i++ {
        if err := Insert(db, record{"One", "Two", "Three"}); err != nil {
            log.Fatal(err)
        }
    }
    Commit(db)
    dt := time.Now().Sub(start)
    fmt.Println("Duration: ", dt)
}

func OpenCreateTable(db *sql.DB) error {
    stmt, err := db.Prepare(`CREATE TABLE IF NOT EXISTS records (id INTEGER PRIMARY KEY, field1 TEXT, field2 TEXT, field3 TEXT);`)
    if err != nil {
        panic(err)
    }
    if _, err = stmt.Exec(); err != nil {
        return err
    }
    return nil
}

func Insert(db *sql.DB, r record) error {
    var err error
    if _, err = insertStmt.Exec(r.field1, r.field2, r.field3); err != nil {
        return err
    }
    return nil
}

func Begin(db *sql.DB) error {
    stmt, err := db.Prepare(`BEGIN`)
    if err != nil {
        return err
    }
    if _, err = stmt.Exec(); err != nil {
        return err
    }
    return nil
}

func Commit(db *sql.DB) error {
    stmt, err := db.Prepare(`COMMIT`)
    if err != nil {
        return err
    }
    if _, err = stmt.Exec(); err != nil {
        return err
    }
    return nil
}

Получается стабильно 15 миллисекунд

Ещё кучу миллисекунд можно добыть, если отключить синхронный доступ. Для этого нужно указать ключ _sync=0 в строке где открывается файл:

sql.Open("sqlite3", "./data.db?_sync=0")

Можно до кучи добавить &_journal=OFF, но там выигрыш получается не столь значительный.