SQLite 3 не освобождает память в Golang

у меня возникли проблемы с тем, чтобы хорошо играть с SQLite, я делал это в прошлом без проблем, но прошло некоторое время, и я не могу вспомнить, что я сделал, чтобы заставить его работать правильно. Я использую Go вместе с mattn/go-sqlite3 пакет для обработки и вставки большого количества данных в базу данных SQLite, но как-то всегда заканчивается съедением всей моей оперативной памяти, пока, наконец, не выйдет с кодом состояния ошибки. Чтобы убедиться, что я изолировал проблему нехватки памяти в SQLite, я написал после простой программы, чтобы проверить его:

package main

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

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

    ddl := `
        PRAGMA automatic_index = ON;
        PRAGMA cache_size = 32768;
        PRAGMA cache_spill = OFF;
        PRAGMA foreign_keys = ON;
        PRAGMA journal_size_limit = 67110000;
        PRAGMA locking_mode = NORMAL;
        PRAGMA page_size = 4096;
        PRAGMA recursive_triggers = ON;
        PRAGMA secure_delete = ON;
        PRAGMA synchronous = NORMAL;
        PRAGMA temp_store = MEMORY;
        PRAGMA journal_mode = WAL;
        PRAGMA wal_autocheckpoint = 16384;

        CREATE TABLE IF NOT EXISTS "user" (
            "id" TEXT,
            "username" TEXT,
            "password" TEXT
        );

        CREATE UNIQUE INDEX IF NOT EXISTS "id" ON "user" ("id");
    `

    _, err = db.Exec(ddl); if err != nil {
        log.Fatal(err)
    }

    queries := map[string]*sql.Stmt{}

    queries["user"], _ = db.Prepare(`INSERT OR REPLACE INTO "user" VALUES (?, ?, ?);`); if err != nil {
        log.Fatal(err)
    }; defer queries["user"].Close()

    tx, err := db.Begin(); if err != nil {
        log.Fatal(err)
    }

    for i := 0; i < 10000000; i++ {
        user := map[string]string{
            "id": string(i),
            "username": "foo",
            "password": "bar",
        }

        _, err := tx.Stmt(queries["user"]).Exec(user["id"], user["username"], user["password"]); if err != nil {
            log.Fatal(err)
        }

        if i % 32768 == 0 {
            tx.Commit()
            db.Exec(`PRAGMA shrink_memory;`)

            tx, err = db.Begin(); if err != nil {
                log.Fatal(err)
            }

            fmt.Println(i)
        }
    }

    tx.Commit()
}

когда я запускаю вышеуказанный код, Go съедает более 100 MiB памяти каждую секунду без каждого выпуска, через минуту или около того он в конечном итоге потребляет 6/7 GiB, а затем процесс убивается. Я пробовал варианты С и без определения Прагм SQLite, но без успеха.

согласно определенным Прагмам, SQLite никогда не должен использовать более 128 MiB ОЗУ.

Я сделал какую-либо ошибку или есть что-то не так с mattn/go-sqlite3 или Go GC?


профилирование с помощью davecheney/profile по состоянию на эти инструкции дает этот не очень полезный результат:

alix@900X4C:~/Go/src$ go tool pprof --text ./test /tmp/profile102098478/mem.pprof
Adjusting heap profiles for 1-in-4096 sampling rate
Total: 0.0 MB
     0.0 100.0% 100.0%      0.0 100.0% runtime.allocm
     0.0   0.0% 100.0%      0.0 100.0% database/sql.(*DB).Exec
     0.0   0.0% 100.0%      0.0 100.0% database/sql.(*DB).conn
     0.0   0.0% 100.0%      0.0 100.0% database/sql.(*DB).exec
     0.0   0.0% 100.0%      0.0 100.0% github.com/mattn/go-sqlite3.(*SQLiteDriver).Open
     0.0   0.0% 100.0%      0.0 100.0% github.com/mattn/go-sqlite3._Cfunc_sqlite3_threadsafe
     0.0   0.0% 100.0%      0.0 100.0% main.main
     0.0   0.0% 100.0%      0.0 100.0% runtime.cgocall
     0.0   0.0% 100.0%      0.0 100.0% runtime.gosched0
     0.0   0.0% 100.0%      0.0 100.0% runtime.main
     0.0   0.0% 100.0%      0.0 100.0% runtime.newextram

Это было только для 1000000 итераций, и память все еще растет, как будто нет завтра.

Я также попробовал тот же код на двух MacBook Pro, оба работают с последней версией Go от brew (1.3.1), в одном из них использование памяти превысило 10 GiB, а другой усредненный 2 GiB Потребление ОЗУ. Это выглядит как странное поведение, что я могу сделать, чтобы проследить несоответствия и исправить память?

3 ответов


Я не могу воспроизвести ваши результаты. Он использует около 100 MiB памяти.

$ go version
go version devel +7ab3adc146c9 Sun Oct 19 10:33:50 2014 -0700 linux/amd64
$ sqlite3 --version
3.8.2 2013-12-06 14:53:30 27392118af4c38c5203a04b8013e1afdb1cebd0d
$ go get -v github.com/mattn/go-sqlite3
github.com/mattn/go-sqlite3 (download)
github.com/mattn/go-sqlite3
$ go run simple.go
0
32768
65536
<SNIP>
9928704
9961472
9994240
$

среда выполнения.MemStats записывает статистику о распределителе памяти Go. Он не включает память, управляемую SQLite. Например, в конце программы,

var ms runtime.MemStats
runtime.ReadMemStats(&ms)
fmt.Println(
    ms.Alloc,      // bytes allocated and still in use
    ms.TotalAlloc, // bytes allocated (even if freed)
    ms.Sys,        // bytes obtained from system (sum of XxxSys below)
    ms.Mallocs,    // number of mallocs
    ms.Frees,      // number of frees
)

выход:

12161440 7953059928 18757880 160014535 159826250

и он также работает на Go 1.4 Beta 1

$ go version
go version go1.4beta1 linux/amd64

это подробно обсуждалось на GitHub. Я публикую здесь, чтобы помочь

https://github.com/mattn/go-sqlite3/issues/157

решение найдено, проблема связана с этим утверждением:

_, err := tx.Stmt(queries["user"]).Exec(user["id"], user["username"], user["password"]); 

это выбрасывание строк и кода, следующего за никогда не вызывает Close ().

эта версия тестовой программы работает правильно

package main

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

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

    ddl := `
        PRAGMA automatic_index = ON;
        PRAGMA cache_size = 32768;
        PRAGMA cache_spill = OFF;
        PRAGMA foreign_keys = ON;
        PRAGMA journal_size_limit = 67110000;
        PRAGMA locking_mode = NORMAL;
        PRAGMA page_size = 4096;
        PRAGMA recursive_triggers = ON;
        PRAGMA secure_delete = ON;
        PRAGMA synchronous = NORMAL;
        PRAGMA temp_store = MEMORY;
        PRAGMA journal_mode = WAL;
        PRAGMA wal_autocheckpoint = 16384;

        CREATE TABLE IF NOT EXISTS "user" (
            "id" TEXT,
            "username" TEXT,
            "password" TEXT
        );

        CREATE UNIQUE INDEX IF NOT EXISTS "id" ON "user" ("id");
    `

    _, err = db.Exec(ddl)
    if err != nil {
        log.Fatal(err)
    }

    queries := map[string]*sql.Stmt{}

    queries["user"], _ = db.Prepare(`INSERT OR REPLACE INTO "user" VALUES (?, ?, ?);`)
    if err != nil {
        log.Fatal(err)
    }
    defer queries["user"].Close()

    tx, err := db.Begin()
    if err != nil {
        log.Fatal(err)
    }

    for i := 0; i < 10000000; i++ {
        user := map[string]string{
            "id":       string(i),
            "username": "foo",
            "password": "bar",
        }

        rows, err := tx.Stmt(queries["user"]).Exec(user["id"], user["username"], user["password"])
        if err != nil {
            log.Fatal(err)
        }
        // CLOSE ROWS HERE!
        rows.Close()

        if i%32768 == 0 {
            tx.Commit()
            db.Exec(`PRAGMA shrink_memory;`)

            tx, err = db.Begin()
            if err != nil {
                log.Fatal(err)
            }

            fmt.Println(i)
        }
    }

    tx.Commit()
}