有几种习惯操作可以从数据存储中检索结果。
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) }
以上代码中发生了什么:
db.Query()
将查询发送到数据库。像往常一样检查错误。rows.Close()
。这个非常重要。rows.Next()
。rows.Scan()
。这几乎是在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()
,这不是一个坏主意。rows.Close()
在完成每个结果时明确调用,而不要使用defer
。当您遍历行并将它们扫描到目标变量时,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)