在 for 循环中使用数据库连接时发生内存泄漏
Memory leaks when using db connections within a for loop
我正在尝试在无限循环中连续查询 Postgres 数据库。但是,查询似乎没有关闭,垃圾收集器可能没有工作,我面临内存分配问题。
脚本设计如下:
在 func main()
中,我声明了一个全局数据库连接。然后执行 func checkCross(...)
,其中包含 for 循环,每次迭代都会执行 func importPrices(...)
,然后从数据库中导入行。
import (
"database/sql"
_"github.com/lib/pq"
"runtime"
)
var db *sql.DB
func main() {
var err error
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+" dbname=%s password=%s ", host, port, user, dbname, password)
//connection
db, err = sql.Open("postgres", psqlInfo)
if err != nil {
log.Fatal(err)
}
defer db.Close()
checkCross("EUR_USD")
...
func checkCross(...)
func checkCross(instrument string) {
for i := 1; i < 4; i++ {
rawData, _ := importPrices(instrument)
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
fmt.Printf("alloc [%v] \t heapAlloc [%v] \n", mem.Alloc, mem.HeapAlloc)
...
}
func importPrices(...)
func importPrices(instrument string) ([]float64, []time.Time) {
var price_ float64
var date_ time.Time
price := make([]float64, 10000000)
date := make([]time.Time, 10000000)
queryCommand := fmt.Sprintf("SELECT * FROM table where instrument = '%[1]v' ORDER BY 1 ASC;", instrument)
rows, err := db.Query(queryCommand)
if err != nil {
log.Fatal(err)
}
//scan from db
z := 0
for rows.Next() {
err := rows.Scan(&date_, &price_)
if err != nil {
log.Fatal(err)
}
price[z] = price_
date[z] = date_
z+=1
}
price = price[:z]
date = date[:z]
if err := rows.Err(); err != nil {
log.Fatal(err)
}
rows.Close()
return price, date
输出
alloc [321664064] heapAlloc [321664064]
alloc [641723264] heapAlloc [641723264]
alloc [961781984] heapAlloc [961781984]
你能指导我我的代码或方法中的问题在哪里吗?
我读过一些文章,他们建议在 for 循环中使用 rows.Close()
而不是 defer rows.Close()
但这个解决方案不起作用。
您在每次调用 importPrices
.
时分配两个 10M 元素的片段(float64
是 8 个字节,time.Time
更大)
当然,这些切片中的大部分最终都会变成垃圾(据说,您用从 importPrices
返回的那两个切片覆盖了一些变量),但是这些垃圾自然会 浮动直到为你的程序提供支持的 Go 运行时决定收集它。在此之前,内存消耗将会增加。
了解该语言的两个 "standard" 实现(您应该使用其中一个)具有 scanning 与主程序流程。换句话说,当程序丢失对分配的内存块的最后一个实时引用时,在那一刻没有任何事情发生关于块占用的内存——它必须等到GC 会找到它然后收集。发生这种情况的确切时间几乎无法预测; Go 具有复杂的动态算法以 速度 GC 以某种方式在花费在收集垃圾上的 CPU 周期量和让内存消耗增长之间取得某种平衡。
至于手头的问题,通常的方法是简单地重用迭代之间分配的内存。
也就是说,在调用 importPrices
之前预先分配您的切片,
将它们传递到那里,然后在再次传递它们之前将它们重置为零长度。
我正在尝试在无限循环中连续查询 Postgres 数据库。但是,查询似乎没有关闭,垃圾收集器可能没有工作,我面临内存分配问题。
脚本设计如下:
在 func main()
中,我声明了一个全局数据库连接。然后执行 func checkCross(...)
,其中包含 for 循环,每次迭代都会执行 func importPrices(...)
,然后从数据库中导入行。
import (
"database/sql"
_"github.com/lib/pq"
"runtime"
)
var db *sql.DB
func main() {
var err error
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+" dbname=%s password=%s ", host, port, user, dbname, password)
//connection
db, err = sql.Open("postgres", psqlInfo)
if err != nil {
log.Fatal(err)
}
defer db.Close()
checkCross("EUR_USD")
...
func checkCross(...)
func checkCross(instrument string) {
for i := 1; i < 4; i++ {
rawData, _ := importPrices(instrument)
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
fmt.Printf("alloc [%v] \t heapAlloc [%v] \n", mem.Alloc, mem.HeapAlloc)
...
}
func importPrices(...)
func importPrices(instrument string) ([]float64, []time.Time) {
var price_ float64
var date_ time.Time
price := make([]float64, 10000000)
date := make([]time.Time, 10000000)
queryCommand := fmt.Sprintf("SELECT * FROM table where instrument = '%[1]v' ORDER BY 1 ASC;", instrument)
rows, err := db.Query(queryCommand)
if err != nil {
log.Fatal(err)
}
//scan from db
z := 0
for rows.Next() {
err := rows.Scan(&date_, &price_)
if err != nil {
log.Fatal(err)
}
price[z] = price_
date[z] = date_
z+=1
}
price = price[:z]
date = date[:z]
if err := rows.Err(); err != nil {
log.Fatal(err)
}
rows.Close()
return price, date
输出
alloc [321664064] heapAlloc [321664064]
alloc [641723264] heapAlloc [641723264]
alloc [961781984] heapAlloc [961781984]
你能指导我我的代码或方法中的问题在哪里吗?
我读过一些文章,他们建议在 for 循环中使用 rows.Close()
而不是 defer rows.Close()
但这个解决方案不起作用。
您在每次调用 importPrices
.
float64
是 8 个字节,time.Time
更大)
当然,这些切片中的大部分最终都会变成垃圾(据说,您用从 importPrices
返回的那两个切片覆盖了一些变量),但是这些垃圾自然会 浮动直到为你的程序提供支持的 Go 运行时决定收集它。在此之前,内存消耗将会增加。
了解该语言的两个 "standard" 实现(您应该使用其中一个)具有 scanning 与主程序流程。换句话说,当程序丢失对分配的内存块的最后一个实时引用时,在那一刻没有任何事情发生关于块占用的内存——它必须等到GC 会找到它然后收集。发生这种情况的确切时间几乎无法预测; Go 具有复杂的动态算法以 速度 GC 以某种方式在花费在收集垃圾上的 CPU 周期量和让内存消耗增长之间取得某种平衡。
至于手头的问题,通常的方法是简单地重用迭代之间分配的内存。
也就是说,在调用 importPrices
之前预先分配您的切片,
将它们传递到那里,然后在再次传递它们之前将它们重置为零长度。