Go中关闭重连MySQL连接时出现意外的‘no such host’错误
问题背景
我正处于Go语言学习初期,正在熟悉Go与SQL数据库的交互方式。我有丰富的SQL数据库使用经验,但几乎从未使用过ORM。作为入门练习,我编写了addCardTest()函数向MySQL数据库的表中添加一行数据,listCardsTest()函数打印该表的简单查询结果。在main()函数中按顺序调用这两个函数时,第二个函数执行出现错误:
panic: dial tcp: lookup rpi.local on 172.24.32.1:53: no such host
goroutine 1 [running]:
main.listCardsTest()
/home/tmiku/go/src/mtg/mtg.go:23 +0x325
main.main()
/home/tmiku/go/src/mtg/mtg.go:12 +0x1c
exit status 2
移除第一个函数中的defer db.Close()行后错误消失,我对此有三个疑问:
- 底层是否存在并发操作(即
listCardsTest()在addCardsTest()完成前运行)?这在Go操作外部数据库时是否正常? - 单个Go程序是否只能创建一个指向特定数据库的连接池?尝试创建另一个会发生什么?“no such host”错误作为限制提示似乎不直观。
- 数据库访问函数是否应该假设连接已打开,并接受数据库连接作为参数?将数据库连接的开闭逻辑封装到业务函数中是否属于不良实践?
问题解答
1. 底层是否存在并发操作?
不存在并发执行的情况。Go的main()函数中函数调用是串行执行的,addCardTest()完全执行完毕后才会调用listCardsTest()。你遇到的错误和并发无关,属于DNS解析或连接池关闭后的异常表现,并非并发导致的执行顺序问题。
2. 单个Go程序是否只能创建一个连接池?
完全可以创建多个连接池,Go语言本身没有这个限制。sql.Open()每次调用都会生成一个新的连接池实例。你看到的“no such host”是误导性的表象,本质是第一个函数里的defer db.Close()关闭连接池后,触发了底层网络资源的回收,导致后续DNS解析出现偶发异常(大概率是环境或驱动的临时问题),并非创建多个连接池的限制。
正常情况下创建多个连接池是允许的,但不推荐——连接池的核心作用是复用连接、减少资源开销,多个连接池会浪费数据库连接资源,增加数据库的负载压力。
3. 数据库连接的最佳实践
推荐数据库访问函数接受已打开的连接(或连接池)作为参数,而非在函数内部重复执行连接的开闭逻辑。
将连接开闭封装到业务函数里属于不良实践:
- 每次调用函数都创建新连接池,会重复建立TCP连接,大幅降低性能,浪费数据库和网络资源。
- 连接池的配置(如最大连接数、超时时间)无法统一管理,容易出现资源耗尽的问题。
- 代码冗余,重复的
sql.Open()和db.Close()逻辑会增加维护成本。
正确的做法是:在程序初始化阶段创建一次连接池,将其传入各个业务函数使用,程序退出时再统一关闭连接池。
完整代码
package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) func main() { addCardTest() listCardsTest() } func listCardsTest() { db, err := sql.Open("mysql", "tmiku:pass@tcp(rpi.local:3306)/mtg") if err != nil { panic(err) } defer db.Close() rows, err := db.Query("select cardid, name, manacost, type from Cards") if err != nil { panic(err) } defer rows.Close() var id int var name, manacost, cardtype string // need to initialize these first... for rows.Next() { err := rows.Scan(&id, &name, &manacost, &cardtype) // ...so Scan() has somewhere to put them. if err != nil { panic(err) } //print the results fmt.Println(id, name, manacost, cardtype) } } func addCardTest() { //open the database, db is a pointer to the db object db, err := sql.Open("mysql", "tmiku:pass@tcp(rpi.local:3306)/mtg") if err != nil { panic(err) } defer db.Close() //REMOVED THIS LINE, FIXED ERROR //run our insert result, err := db.Exec("insert into mtg.Cards (name, manacost, ruletext, type, subtype, power, toughness) values ('Stormchaser Drake', '1U', 'Flying\nWhenever Stormchaser Drake becomes the target of a spell you control, draw a card.', 'Creature', 'Drake', 2, 1)") if err != nil { panic(err) } lastId, _ := result.LastInsertId() fmt.Println("Added id ", lastId) }
内容的提问来源于stack exchange,提问作者tmiku

