Golang中行扫描内存占用过高且无法完成处理的问题

Golang中行扫描内存占用过高且无法完成处理的问题 你好,

我已在此处提供了完整的代码:Go Playground - The Go Programming Language。我在这里所做的是运行一个定时器,并在每个时间间隔执行 processData 函数,但目前我将其限制为 5 次循环。问题是它首先运行一个 SQL 查询,然后扫描结果并运行另一个查询。但我注意到它占用了很高的内存和 CPU 使用率,之后进程就被终止了。令人惊讶的是,这仅仅是两个 SELECT 语句,但哪里可能出错了或者需要调整呢?我认为问题出在 row.Scan 上,因为当我只运行查询时,它运行得很完美。

func main() {
    fmt.Println("hello world")
}

更多关于Golang中行扫描内存占用过高且无法完成处理的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

我不确定这是否是问题所在,但你在代码中调用了 rows1.Close 两次:一次在 defer 语句中,另一次在你的 for rows1.Next() 循环之后。

func main() {
    fmt.Println("hello world")
}

更多关于Golang中行扫描内存占用过高且无法完成处理的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好,“新手”

我怀疑问题在于你在嵌套查询中使用了 defer rows.Close()defer 语句本质上附加了一个清理函数,该函数在函数返回时执行。如果你正在遍历大量行,这个列表可能会变得非常大,并持有对未关闭的结果集和/或内部循环中其他资源的引用。

StackOverflow 上的 Izca 在这里回答了一个相关问题,并提供了一些很好的例子:go - 在循环中使用 defer 正确释放资源的方法? - Stack Overflow

我建议尝试的最简单方法是将你 for rows1.Next() 循环内的代码包装到一个匿名函数闭包中:

    for rows1.Next() {
        func() {
            // ...
            defer rows2.Close()
            //
        }()
    }

这样,defer 会在每个内部循环之后执行。

你好 Sean,

我尝试在最后一行先关闭 rows1.Close(),结果遇到了这个错误:

Error errStmt1 1 sql: database is closed

然后我注释掉了 defer rows1.Close() 并打开了 rows1.Close() 这一行。接着我遇到了这个错误:

Error errStmt1 1 sql: database is closed
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x58d767]

goroutine 22 [running]:
database/sql.(*Rows).Next(0x0)
        /usr/lib/golang/src/database/sql/sql.go:2985 +0x27
main.processData()

两种方式都不行,问题可能出在哪里?毕竟这只是一个 SELECT 语句,对吧?

是的,肖恩,你说得对,我正在处理大量数据行。事实上,在继续之前,我想先和你确认一下这个:

rows1, errStmt1 := db.Query(" Select test1.aTID as aTID,test1.vtestID as gdvID,test1.latitude as glat,test1.longitude as glong,test1.eOn as acc,test1.gDateTime as mysqlDateTime,test1.speed as intSpeed,test1.direction as intDirection,test1.flLevel"+
                           " From test1 "+                                       
                           " Order By test1.gDateTime Asc")
        if errStmt1 != nil {
                fmt.Println("Error errStmt1 1", errStmt1)
        }
        defer rows1.Close()       

        for rows1.Next() {
                    var test11 test1
                var errScan1 error
                errScan1 = rows1.Scan(&test11.aTID,&test11.gdvID,&test11.glat,&test11.glong,&test11.acc,&test11.mysqlDateTime,&test11.intSpeed,&test11.intDirection,&test11.flLevel)
                if errScan1 !=nil {
                        fmt.Println("Error errScan1 1", errScan1)
                }
        } 
        if errRows1 := rows1.Err(); errRows1 != nil {
                fmt.Println("Error errRows1 1", errRows1)
        }
        rows1.Close()

我想问的是,在这个第一层(没有嵌套查询)的代码中,这样写是否正确,或者这个层面是否可能存在内存泄漏?问题是,即使没有内层查询,仅仅这个外层查询,我也倾向于遇到这个错误。

Error errStmt1 1 sql: database is closed
panic: runtime error: invalid memory address or nil pointer dereference
        panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x58e2ae]
goroutine 20 [running]:
database/sql.(*Rows).close(0x0, {0x0, 0x0})
        /usr/lib/golang/src/database/sql/sql.go:3308 +0x8e
database/sql.(*Rows).Close(0xc000485be8?)
        /usr/lib/golang/src/database/sql/sql.go:3304 +0x1d
panic({0x60a5e0, 0x7c6950})
        /usr/lib/golang/src/runtime/panic.go:838 +0x207
database/sql.(*Rows).Next(0x0)
        /usr/lib/golang/src/database/sql/sql.go:2985 +0x27
main.processData()

根据你提供的代码分析,内存占用过高的问题主要出现在以下几个方面:

1. 未关闭数据库连接和结果集

每次查询后没有及时关闭 rows,导致连接和内存泄漏。

2. 大量重复查询

在循环中对相同数据反复查询,应该考虑批量处理或缓存结果。

3. 行扫描的内存分配

每次扫描都创建新的变量,没有复用内存。

修复后的代码示例:

package main

import (
    "database/sql"
    "fmt"
    "log"
    "time"
    _ "github.com/lib/pq"
)

func processData(db *sql.DB) error {
    // 查询主数据
    rows, err := db.Query(`
        SELECT id, name, status 
        FROM users 
        WHERE status = 'active' 
        LIMIT 1000`)
    if err != nil {
        return err
    }
    defer rows.Close()

    // 批量处理,避免N+1查询问题
    var userIDs []int64
    for rows.Next() {
        var id int64
        var name string
        var status string
        
        if err := rows.Scan(&id, &name, &status); err != nil {
            log.Printf("扫描错误: %v", err)
            continue
        }
        userIDs = append(userIDs, id)
    }

    if err = rows.Err(); err != nil {
        return err
    }

    // 批量查询详细信息,避免循环中的单独查询
    if len(userIDs) > 0 {
        query := `
            SELECT user_id, detail 
            FROM user_details 
            WHERE user_id = ANY($1)`
        
        detailRows, err := db.Query(query, userIDs)
        if err != nil {
            return err
        }
        defer detailRows.Close()

        for detailRows.Next() {
            var userID int64
            var detail string
            if err := detailRows.Scan(&userID, &detail); err != nil {
                log.Printf("扫描详情错误: %v", err)
                continue
            }
            // 处理详情数据
            _ = userID
            _ = detail
        }
    }

    return nil
}

func main() {
    db, err := sql.Open("postgres", "your-connection-string")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 设置连接池参数
    db.SetMaxOpenConns(10)
    db.SetMaxIdleConns(5)
    db.SetConnMaxLifetime(time.Hour)

    // 限制处理次数
    maxIterations := 5
    ticker := time.NewTicker(30 * time.Second)
    defer ticker.Stop()

    iteration := 0
    for range ticker.C {
        if iteration >= maxIterations {
            break
        }

        if err := processData(db); err != nil {
            log.Printf("处理数据错误: %v", err)
        }
        
        iteration++
        fmt.Printf("完成迭代 %d\n", iteration)
    }
}

关键优化点:

  1. 使用 defer rows.Close():确保结果集被正确关闭
  2. 批量查询:避免在循环中执行单独查询(N+1问题)
  3. 连接池配置:合理设置数据库连接池参数
  4. 限制结果集:使用 LIMIT 控制每次处理的数据量
  5. 错误处理:正确处理扫描和查询错误

内存监控建议:

// 添加内存监控
import "runtime"

func printMemoryUsage() {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    fmt.Printf("内存使用: Alloc = %v MiB, TotalAlloc = %v MiB, Sys = %v MiB\n",
        m.Alloc/1024/1024, m.TotalAlloc/1024/1024, m.Sys/1024/1024)
}

processData 函数前后调用 printMemoryUsage() 可以监控内存变化。

回到顶部