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()
}