扫码订阅《 》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go学习
  • Go学院

    • Go小课
    • Go视界
    • Go小考
    • Go实战
  • Go资源

    • 优质课程
    • 在线宝典
    • 资源下载
    • 帮找资源
训练营 🔥
  • Go体系课&实战训练营
  • 升值加薪陪跑训练营
Go求职
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
  • 求职服务

    • 内推互助
    • 求职助力
    • 内推公司
Go友会
  • 城市
  • 校园
推广返佣
  • 返佣排行
  • 返佣规则
  • 推广学院
实验区
  • Go周边
  • Go宝典

    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
更多
  • 用户中心

    • 我的信息
    • 我的返佣
    • 我的消息
  • 玩转星球

    • 星球介绍
    • 星主权益
    • 吐槽专区
    • 成长记录
  • 合作交流

    • 商务合作
    • 讲师招募
    • 生态伙伴
author-avatar

GOLANG ROADMAP


首页
Go学习
  • Go学院

    • Go小课
    • Go视界
    • Go小考
    • Go实战
  • Go资源

    • 优质课程
    • 在线宝典
    • 资源下载
    • 帮找资源
训练营 🔥
  • Go体系课&实战训练营
  • 升值加薪陪跑训练营
Go求职
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
  • 求职服务

    • 内推互助
    • 求职助力
    • 内推公司
Go友会
  • 城市
  • 校园
推广返佣
  • 返佣排行
  • 返佣规则
  • 推广学院
实验区
  • Go周边
  • Go宝典

    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
更多
  • 用户中心

    • 我的信息
    • 我的返佣
    • 我的消息
  • 玩转星球

    • 星球介绍
    • 星主权益
    • 吐槽专区
    • 成长记录
  • 合作交流

    • 商务合作
    • 讲师招募
    • 生态伙伴

扫码订阅《 》或入驻星球,即可阅读文章!

4.获取数据集


GOLANG ROADMAP

有几种常用的操作可以从数据库中检索结果。

  1. 执行返回一条数据的查询操作。
  2. 准备要重复使用的语句,在多次执行该语句后进行销毁。
  3. 一次性的执行语句,并且不打算将其重复使用。
  4. 执行返回一条数据的查询,这种特殊情况有一个快捷方式。

Go 的 database/sql 包中函数名称很重要。如果一个函数名字包含 Query, 那么该函数旨在向数据库发出查询问题, 并且即使它为空,也将返回一组行。不返回行的语句不应该使用 Query 函数; 而应使用 Exec()。

# 从数据库获取数据

让我们来看一个如何查询数据库和处理数据的例子. 我们将在用户表 users 中查询 id 为 1 的用户, 那么如何打印该用户的 id 和 name 呢,我们需要使用 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

下面是此段代码所做的事情:

  1. 我们使用 db.Query() 将查询请求发送给数据库,我们通常要检查是否报错。
  2. 我们需要在程序最后使用 rows.Close()来关闭,这是非常重要的。
  3. 我们将使用 rows.Next()来遍历每行的数据
  4. 我们使用 rows.Scan()来将每一行中每一列的数据赋值给变量。
  5. 当把每一行的数据进行遍历后,检查是否出错。

这几乎是 Go 中唯一的方法。例如,您无法获得一行作为 map。那是因为所有内容都是强类型的。您需要创建正确类型的变量,并将指针传递给它们。

其中的两个部分很容易出错,并可能带来严重的后果。

  • 您应该始终检查 for rows.Next() 循环的末尾是否有错误。如果循环过程中出现错误,您需要知道它。不要只假设循环会迭代,直到您处理完所有行。
  • 其次,只要存在打开的结果集(由 rows 表示),底层连接就很忙,不能用于任何其他查询。这意味着它在连接池中不可用。如果使用 rows.Next() 迭代所有行,最终将读取最后一行,并且rows.Next() 将遇到内部 EOF 错误并为您调用 rows.Close()。但是,如果出于某种原因您退出该循环——提前返回,等等情况——那么 rows 不会关闭,连接保持打开。(不过,如果rows.Next() 由于错误返回 false,那么它会自动关闭)。这是耗尽资源的一种简单方式。
  • 如果 rows.Close() 已经关闭,那么它是一个无害的 no-op,因此您可以多次调用它。但是请注意,我们首先检查错误,只有在没有错误时才调用 rows.Close(),以避免 runtime panic。
  • 您应该 始终 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 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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在幕后,db.Query() 实际上做了预处理、执行并关闭了预处理语句。这是到数据库的三次往返。如果不小心,您的应用程序所进行的数据库交互次数可能会增加三倍!某些驱动程序在特定情况下可以避免这种情况,但并非所有驱动程序都可以。有关详细信息,请参阅 预处理语句 (opens new window)。

# 单条记录查询

查询结果多于一行的时候,这样来获取单条记录:

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

错误信息会在 Scan() 之后返回。 QueryRow() 也可以用于预处理语句:

stmt, err := db.Prepare("select name from users where id = ?")
if err != nil {
	log.Fatal(err)
}
defer stmt.Close()
var name string
err = stmt.QueryRow(1).Scan(&name)
if err != nil {
	log.Fatal(err)
}
fmt.Println(name)
1
2
3
4
5
6
7
8
9
10
11
  • 从数据库获取数据
  • Scan() 如何工作
  • 预处理查询
  • 单条记录查询