当前位置: 首页 > 工具软件 > gosql > 使用案例 >

Go语言中database/sql包操作MySQL(检索结果集)

汪辰阳
2023-12-01

有几种习惯操作可以从数据存储中检索结果。

  1. 执行一个返回行的查询。
  2. 准备重复使用的声明,多次执行并销毁它。
  3. 以一次性方式执行陈述,而不准备重复使用。
  4. 执行一个返回单行的查询。这个特例有一个捷径。

Go的database/sql函数名称很重要。如果一个函数名包含Query,它被设计为询问数据库的问题,并且将返回一组行,即使它是空的。不返回行的语句不应该使用Query函数; 应该使用Exec()

从数据库获取数据

我们来看一个如何查询数据库和使用结果的例子。我们将查询users表格中是否id为1 的用户,并打印出用户的idname我们将把结果分配给一次一行的变量rows.Scan()

var (
	id int
	name string
)
rows, err := db.Query("select id, name from users where id = ?", 1)
if err != nil {
	log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
	err := rows.Scan(&id, &name)
	if err != nil {
		log.Fatal(err)
	}
	log.Println(id, name)
}
err = rows.Err()
if err != nil {
	log.Fatal(err)
}

以上代码中发生了什么:

  1. 我们正在使用db.Query()将查询发送到数据库。像往常一样检查错误。
  2. 我们推迟rows.Close()这个非常重要。
  3. 我们通过迭代遍历行rows.Next()
  4. 我们将每行中的列读入变量中rows.Scan()
  5. 我们在遍历行之后检查错误。

这几乎是在Go中完成的唯一方法。例如,您不能将某行作为地图。那是因为一切都是强类型的。您需要创建正确类型的变量并将指针传递给它们,如图所示。

这很容易出错,并可能造成不良后果。

  • 您应该始终在for rows.Next() 循环结束时检查错误如果在循环过程中出现错误,您需要了解它。不要只假定循环迭代直到你处理了所有的行。
  • 其次,只要有一个开放的结果集(由表示rows),底层连接就是繁忙,不能用于任何其他查询。这意味着它在连接池中不可用。如果您遍历所有行rows.Next(),最终您将读取最后一行,并且 rows.Next()会遇到内部EOF错误并请求您rows.Close()但是,如果由于某种原因您退出该循环 - 提前退货等等 - 则rows不会关闭,并且连接保持打开状态。(如果rows.Next()由于错误返回错误,它会自动关闭)。这是耗尽资源的简单方法。
  • rows.Close()如果它已经关闭,它是一个无害的无操作,所以你可以多次调用它。但是,请注意,我们首先检查错误,并且只有rows.Close()在没有错误时才会调用,以避免运行时painc
  • 即使您 在循环结束时明确地调用,但您应该始终defer rows.Close()这样做rows.Close(),这不是一个坏主意。
  • defer不要在一个循环内。直到函数退出才会执行延迟语句,因此长时间运行的函数不应使用它。如果你这样做,你会慢慢积累记忆。如果您在循环中反复查询和使用结果集,则应rows.Close() 在完成每个结果时明确调用,而不要使用defer

Scan()如何工作

当您遍历行并将它们扫描到目标变量时,Go会在幕后为您执行数据类型转换。它基于目标变量的类型。意识到这可以清理你的代码并帮助避免重复的工作。

例如,假设您从用字符串列(如VARCHAR(45)或类似的)定义的表中选择一些行然而,你碰巧知道表格总是包含数字。如果你传递一个指向字符串的指针,Go会将字节复制到字符串中。现在您可以使用strconv.ParseInt()或类似的将该值转换为数字。您必须检查SQL操作中的错误以及解析整数的错误。这是混乱和乏味。

或者,您可以将Scan()一个指针传递给一个整数。Go会检测到并呼唤strconv.ParseInt()你。如果转换中出现错误,则调用Scan()将返回它。你的代码现在更整洁,更小。这是推荐database/sql的使用方式

准备查询

总体而言,您应该始终准备要多次使用的查询。准备查询的结果是一个准备好的语句,它可以为您在执行语句时提供的参数设置占位符(又名绑定值)。对于所有常见原因(例如,避免SQL注入攻击),这比串联字符串要好得多。

在MySQL中,参数占位符是?,而在PostgreSQL中它是$N,其中N是一个数字。SQLite接受这些。在Oracle占位符以冒号开头并被命名,就像:param1我们将使用,?因为我们使用MySQL作为示例。

stmt, err := db.Prepare("select id, name from users where id = ?")
if err != nil {
	log.Fatal(err)
}
defer stmt.Close()
rows, err := stmt.Query(1)
if err != nil {
	log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
	// ...
}
if err = rows.Err(); err != nil {
	log.Fatal(err)
}

引擎下,db.Query()居然准备,执行和关闭一个准备好的声明。这是对数据库的三次往返。如果你不小心,你可以将你的应用程序所做的数据库交互次数增加三倍!一些司机可以在特定情况下避免这种情况,但并非所有司机都这样做。

单行查询

如果查询最多只返回一行,则可以在一些冗长的样板代码上使用快捷方式:

var name string
err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)
if err != nil {
	log.Fatal(err)
}
fmt.Println(name)

查询中的错误被推迟直到Scan()被调用,然后从那里返回。您也可以让QueryRow()准备好的声明:

stmt, err := db.Prepare("select name from users where id = ?")
if err != nil {
	log.Fatal(err)
}
var name string
err = stmt.QueryRow(1).Scan(&name)
if err != nil {
	log.Fatal(err)
}
fmt.Println(name)

 类似资料: