在Go中关闭/重新打开MySQL连接时出现意外的“no such host error”

dkqlctbz  于 2023-06-21  发布在  Mysql
关注(0)|答案(1)|浏览(222)

我正处于学习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

我发现,如果我从调用的第一个函数中删除“deferdb.Close()”行,这个错误就会消失。我很惊讶这起作用了,这让我有很多问题,首先是什么抛出了这个错误。

  • 在引擎盖下,是否有一些并发工作正在发生(即listCardsTest()在addCardsTest()完成之前运行)?在Go中使用外部数据库时,这是预期的吗?
  • 一个Go程序只能创建一个连接池到任何给定的数据库吗?当我尝试打开另一个时会发生什么?这是一个有意义的限制,但“没有这样的主机”错误似乎是一个奇怪/不直观的方式来强制执行。
  • 访问数据库的函数是否应该在假设连接已经打开的情况下编写,并接受数据库信息作为参数?将数据库连接的打开/关闭 Package 到一个执行其他操作的函数中是不是不好?感觉就像是这个错误把我推向了这个方向,但我很难找到任何明显的建议记录。

完整代码在下面。MySQL数据库的主机名是rpi.local,请原谅硬编码的凭据(我正在学习)。

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)
}
35g0bw71

35g0bw711#

我已经对您的代码进行了一点重构,以便您更好地理解我通常是如何编写这段代码的。首先,让我展示一下代码,然后,我将带您浏览所有相关部分。出于演示的目的,所有代码都被写入了一个名为main.go的文件中:

package main

import (
    "database/sql"
    "fmt"

    _ "github.com/go-sql-driver/mysql"
)

type card struct {
    Id   int
    Name string
}

func addCardTest(sql *sql.DB, card card) error {
    res, err := sql.Exec(`INSERT INTO cards (name) VALUES (?)`, card.Name)
    if err != nil {
        return fmt.Errorf("err while inserting a card: %v", err)
    }
    lastId, err := res.LastInsertId()
    if err != nil {
        return fmt.Errorf("err while getting the last ID: %v", err)
    }
    fmt.Println("inserted card with id:", lastId)
    return nil
}

func listCardsTest(sql *sql.DB) ([]card, error) {
    rows, err := sql.Query("select id, name from cards")
    if err != nil {
        return nil, fmt.Errorf("err while getting cards: %v", err)
    }
    defer rows.Close()
    var currentCard card
    res := make([]card, 0)
    for rows.Next() {
        if err := rows.Scan(&currentCard.Id, &currentCard.Name); err != nil {
            return nil, fmt.Errorf("err while scanning a row: %v", err)
        }
        res = append(res, currentCard)
    }
    return res, nil
}

func main() {
    dsn := "root:root@tcp(127.0.0.1:3306)/card"
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        panic(err)
    }
    defer db.Close()
    if err := addCardTest(db, card{Name: "lorem ipsum"}); err != nil {
        panic(err)
    }
    if err := addCardTest(db, card{Name: "second card"}); err != nil {
        panic(err)
    }
    cards, err := listCardsTest(db)
    if err != nil {
        panic(err)
    }
    for _, v := range cards {
        fmt.Println(v)
    }

}

现在,让我们仔细看看每个部分。
请注意,为了通过Docker运行MySQL示例,我使用了以下命令:
docker run --name some-mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql

addCardTest函数

在这里,有几个改进:

  • 函数参数。您应该期待一个指向SQL客户端的指针(稍后将详细介绍)和一个要插入的卡
  • 你应该使用Exec方法来执行命令(例如:INSERT语句)而不是Query
  • 您不应该硬编码数据,即使它是一个示例问题或POC
  • 始终检查错误

现在,让我们切换到阅读逻辑。

listCardsTest函数

在这里,代码或多或少与以前相同。

main函数

在这个函数中,您应该示例化一个从现在开始必须使用的MySQL DB的句柄。您只需要调用函数sql.Open()一次,defer对方法Close()进行一次调用。所有这些都必须在main函数中完成。然后,您应该在与DB相关的函数中期望这种类型的参数(例如上面提到的listCardsTestaddCardTest)。
我强烈建议你查看这个链接,它解释了sql.DB结构体应该如何使用。
让我知道如果我的程序可以帮助你解决这个问题,谢谢!

相关问题