Go语言中内存释放 ≠ 资源释放
// QueryUserFileMetas : 批量获取用户文件信息
func QueryUserFileMetas(username string, limit int) ([]UserFile, error) {stmt, err := mydb.DBConn().Prepare("select file_sha1,file_name,file_size,upload_at," +"last_update from tbl_user_file where user_name=? limit ?")if err != nil {return nil, err}defer stmt.Close()rows, err := stmt.Query(username, limit)if err != nil {return nil, err}var userFiles []UserFilefor rows.Next() {ufile := UserFile{}err = rows.Scan(&ufile.FileHash, &ufile.FileName, &ufile.FileSize,&ufile.UploadAt, &ufile.LastUpdated)if err != nil {fmt.Println(err.Error())break}userFiles = append(userFiles, ufile)}return userFiles, nil
}
这里的stmt需要手动回收,rows需要吗?
对象 | 是否需要手动关闭 | 为什么 |
---|---|---|
stmt (预处理语句) | ✅ 需要 | 占用数据库连接资源,必须关闭释放资源 |
rows (查询结果集) | ✅ 也需要 | 持有数据库游标或连接,必须关闭释放资源 |
🔍 原因详解
🔹1. stmt.Close()
stmt 是通过 Prepare() 创建的预编译语句句柄。
它通常会绑定数据库连接资源,即使查询结束也不会自动释放。
不关闭会导致连接池资源泄漏,最终使数据库拒绝新的连接。
🔹2. rows.Close()
rows 是执行查询后返回的结果集,底层其实是一个游标。
它也持有数据库连接,直到你显式地关闭或完全遍历完所有行。
如果不 Close():
连接不会释放给连接池(可能一直挂起)
资源泄漏:数据库连接数会越来越多,直到超出限制
✅ 总结
stmt.Close() 和 rows.Close() 都必须显式调用,通常用 defer 来管理。
否则会引起连接池资源耗尽、数据库连接泄漏等问题。
Go 的数据库接口(database/sql)设计得很灵活,但需要你手动管理生命周期。
疑问?为什么不会随着函数的调用结束被内存自动回收了呢?
🧠 背后原理:内存释放 ≠ 资源释放
Go 的垃圾回收器(GC)能做什么?
GC 只能自动清理内存对象(如你创建的结构体、切片、字符串等),前提是这些对象不再被引用。
GC 无法自动释放 “非内存资源”,比如:
- 网络连接(TCP socket).
- 数据库连接
- 文件句柄
- goroutine(除非主动退出)
- 系统级游标、IO句柄等
✅ Go GC 是“内存管理器”,而非“资源管理器”。
🔍 对比:内存 vs 非内存资源
类型 | 例子 | GC 能自动释放? | 需要手动释放? |
---|---|---|---|
内存对象 | []byte , string , map | ✅ 是 | ❌ 否 |
数据库连接 | rows , stmt , tx | ❌ 否 | ✅ 是 |
网络连接 | net.Conn | ❌ 否 | ✅ 是 |
文件句柄 | os.File | ❌ 否 | ✅ 是 |
Go 的垃圾回收器只会自动释放内存,但不会自动关闭文件、数据库连接等 “非内存资源”。这些资源你必须手动关闭。