SQLite 3在Golang中不释放内存。

huangapple go评论74阅读模式
英文:

SQLite 3 not releasing memory in Golang

问题

我在使用Go和SQLite时遇到了问题。过去我曾经修复过这个问题,没有遇到任何问题,但是已经过了一段时间,我不记得我是如何使其正常工作的了。我正在使用mattn/go-sqlite3包来处理和插入大量数据到SQLite数据库中,但是不知何故,Go总是会消耗掉我所有的内存,最终以错误状态码退出。为了确保我已经将内存耗尽的问题隔离到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 PRAGMA,但都没有成功。

根据定义的PRAGMA,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上尝试了相同的代码,两台电脑都运行着来自brew的最新版本的Go(1.3.1),其中一台电脑的内存使用量超过了10 GiB,另一台电脑的平均内存消耗量为2 GiB。这看起来是一种奇怪的行为,我该如何追踪这些差异并修复内存占用问题?

英文:

I'm having issues getting Go to play nicely with SQLite. I fixed it in the past without problems but it has been some time and I can't remember what I did to get it to work properly. I'm using the mattn/go-sqlite3 package to process and insert a lot of data into an SQLite database but somehow Go always ends up eating all my RAM until it finally exits with an error status code. Just to make sure I had isolated the memory starvation problem to SQLite I wrote the following simple program to test it:

package main
import (
&quot;database/sql&quot;
&quot;fmt&quot;
&quot;log&quot;
_ &quot;github.com/mattn/go-sqlite3&quot;
)
func main() {
db, err := sql.Open(&quot;sqlite3&quot;, &quot;./test.db&quot;); 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 &quot;user&quot; (
&quot;id&quot; TEXT,
&quot;username&quot; TEXT,
&quot;password&quot; TEXT
);
CREATE UNIQUE INDEX IF NOT EXISTS &quot;id&quot; ON &quot;user&quot; (&quot;id&quot;);
`
_, err = db.Exec(ddl); if err != nil {
log.Fatal(err)
}
queries := map[string]*sql.Stmt{}
queries[&quot;user&quot;], _ = db.Prepare(`INSERT OR REPLACE INTO &quot;user&quot; VALUES (?, ?, ?);`); if err != nil {
log.Fatal(err)
}; defer queries[&quot;user&quot;].Close()
tx, err := db.Begin(); if err != nil {
log.Fatal(err)
}
for i := 0; i &lt; 10000000; i++ {
user := map[string]string{
&quot;id&quot;: string(i),
&quot;username&quot;: &quot;foo&quot;,
&quot;password&quot;: &quot;bar&quot;,
}
_, err := tx.Stmt(queries[&quot;user&quot;]).Exec(user[&quot;id&quot;], user[&quot;username&quot;], user[&quot;password&quot;]); 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()
}

When I run the above code, Go eats more than 100 MiB of memory every second without every releasing any, and after a minute or so, it ends up consuming 6/7 GiB and then the process gets killed. I've tried variations with and without defining the SQLite PRAGMAs but with no luck.

According to the defined PRAGMAs, SQLite should never use more than 128 MiB of RAM.

Have I made any mistake or is there something wrong with either mattn/go-sqlite3 or Go GC?


Profiling with davecheney/profile as per these instructions yields this not so helpful output:

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

This was just for 1000000 iterations and memory still grows like there's no tomorow.

I also tried the same code on two MacBook Pros, both running the latest version of Go from brew (1.3.1), in one of them the memory usage topped 10 GiB and the other averaged 2 GiB of RAM consumption. This looks like an odd behavior, what can I do to trace down the discrepancies and fix the memory hogging?

答案1

得分: 6

我无法重现你的结果。它使用了约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 (下载)
github.com/mattn/go-sqlite3
$ go run simple.go
0
32768
65536

9928704
9961472
9994240
$

runtime.MemStats记录了关于Go内存分配器的统计信息。它不包括由SQLite管理的内存。例如,在程序结束时,

var ms runtime.MemStats
runtime.ReadMemStats(&ms)
fmt.Println(
ms.Alloc,      // 已分配且仍在使用的字节数
ms.TotalAlloc, // 已分配的字节数(即使已释放)
ms.Sys,        // 从系统获取的字节数(下面的XxxSys之和)
ms.Mallocs,    // malloc的次数
ms.Frees,      // free的次数
)

输出:

12161440 7953059928 18757880 160014535 159826250

它也适用于Go 1.4 Beta 1

$ go version
go version go1.4beta1 linux/amd64
英文:

I'm unable to reproduce your results. It uses about 100 MiB of memory.

$ 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
&lt;SNIP&gt;
9928704
9961472
9994240
$

A runtime.MemStats records statistics about the Go memory allocator. It does not include memory managed by SQLite. For example, at the end of the program,

var ms runtime.MemStats
runtime.ReadMemStats(&amp;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
)

Output:

12161440 7953059928 18757880 160014535 159826250

And it also works on Go 1.4 Beta 1

<pre>
$ go version
go version go1.4beta1 linux/amd64
</pre>

答案2

得分: 4

go-sqlite3包已经修复:

https://github.com/mattn/go-sqlite3/commit/e63d2546a03e8591c61871c4e494587cc28bdd79

英文:

The go-sqlite3 package has been fixed:

https://github.com/mattn/go-sqlite3/commit/e63d2546a03e8591c61871c4e494587cc28bdd79

答案3

得分: 3

这个问题在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)
}
// 在这里关闭rows!
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()
}
英文:

This has been discussed on GitHub at length. I'm posting here to help

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

Solution was found, issue had to do with this statement:

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

this is throwing away the rows and the code following never calls Close().

This version of the test program works correctly

package main
import (
&quot;database/sql&quot;
&quot;fmt&quot;
_ &quot;github.com/mattn/go-sqlite3&quot;
&quot;log&quot;
)
func main() {
db, err := sql.Open(&quot;sqlite3&quot;, &quot;./test.db&quot;)
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 &quot;user&quot; (
&quot;id&quot; TEXT,
&quot;username&quot; TEXT,
&quot;password&quot; TEXT
);
CREATE UNIQUE INDEX IF NOT EXISTS &quot;id&quot; ON &quot;user&quot; (&quot;id&quot;);
`
_, err = db.Exec(ddl)
if err != nil {
log.Fatal(err)
}
queries := map[string]*sql.Stmt{}
queries[&quot;user&quot;], _ = db.Prepare(`INSERT OR REPLACE INTO &quot;user&quot; VALUES (?, ?, ?);`)
if err != nil {
log.Fatal(err)
}
defer queries[&quot;user&quot;].Close()
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
for i := 0; i &lt; 10000000; i++ {
user := map[string]string{
&quot;id&quot;:       string(i),
&quot;username&quot;: &quot;foo&quot;,
&quot;password&quot;: &quot;bar&quot;,
}
rows, err := tx.Stmt(queries[&quot;user&quot;]).Exec(user[&quot;id&quot;], user[&quot;username&quot;], user[&quot;password&quot;])
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()
}

huangapple
  • 本文由 发表于 2014年10月20日 06:48:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/26456253.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定