在本教程中,我们将连接到MySQL并创建一个数据库。我们还将ping数据库,以确保连接的正常建立。
系列索引
连接到MySQL并创建数据库
创建表并插入行
选择单个/多个行 - WIP
预备语句 - WIP
更新行 - WIP
删除行 -
WIP
导入MySQL驱动
创建MySQL数据库的第一步是下载MySQL驱动包并将其导入我们的应用程序。
让我们为我们的应用程序创建一个文件夹,然后下载MySQL包。
我已经在Documents 目录下创建了一个文件夹。请随意在你喜欢的地方创建它。
mkdir ~/Documents/mysqltutorial
cd ~/Documents/mysqltutorial
创建目录后,让我们为项目初始化一个go模块。
go mod init github.com/golangbot/mysqltutorial
上面的命令初始化了一个模块,名为github.com/golangbot/mysqltutorial
下一步是下载MySql驱动。运行下面的命令来下载MySQL驱动包。
go get github.com/go-sql-driver/mysql
让我们写一个程序来导入我们刚刚下载的MySQL驱动。
创建一个名为main.go 的文件,内容如下。
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
导入驱动程序时使用空白标识符_
"database/sql" 包提供了访问MySQL数据库的通用接口。它包含管理MySQL数据库所需的类型。
在下一行,我们导入_ "github.com/go-sql-driver/mysql" ,前缀为下划线(称为作为空白标识符)。这意味着什么呢?这意味着我们导入MySQL驱动包是为了它的副作用,我们不会在代码中明确使用它。当一个包被导入前缀为空白标识符时,该包的init函数将被调用。另外,如果在代码的任何地方没有使用包,Go编译器也不会抱怨。
这一切都很好,但为什么需要这样做呢?
原因是任何SQL驱动都必须在使用前通过调用Register函数来注册。如果我们看一下MySQL驱动的源代码,在github.com/go-sql-driv…行,我们可以看到以下init 函数
func init() {
sql.Register("mysql", &MySQLDriver{})
}
上述函数注册了名为mysql 的SQL驱动。当我们导入以空白标识符_ "github.com/go-sql-driver/mysql" 为前缀的包时,这个init 函数被调用,驱动就可以使用了。完美😃。这正是我们想要的。
连接和创建数据库
现在我们已经成功注册了驱动程序,下一步是连接到MySQL并创建数据库。
让我们为我们的数据库凭证定义常数。
const (
username = "root"
password = "password"
hostname = "127.0.0.1:3306"
dbname = "ecommerce"
)
请用你的证书替换上面的值。
通过使用sql包的Open函数可以打开数据库。 这个函数需要两个参数,驱动名称和数据源名称(DSN)。正如我们已经讨论过的,驱动程序的名称是mysql 。DSN的格式如下
username:password@protocol(address)/dbname?param=value
让我们写一个小函数,当数据库名称作为一个参数被传递时,它将返回这个DSN。
func dsn(dbName string) string {
return fmt.Sprintf("%s:%s@tcp(%s)/%s", username, password, hostname, dbName)
}
上面的函数为传递的dbName ,返回一个DSN。dbName 是可选的,它可以是空的。例如,如果传递ecommerce ,它将返回root:password@tcp(127.0.0.1:3306)/ecommerce
由于我们实际上是在这里创建DB,而不想连接一个现有的DB,一个空的dbName ,将被传递给dsn 函数。
func main() {
db, err := sql.Open("mysql", dsn(""))
if err != nil {
log.Printf("Error %s when opening DB\n", err)
return
}
defer db.Close()
}
请确保用户有访问权限来创建DB。上面几行代码打开并返回一个与数据库的连接。当函数使用defer返回时,数据库连接被关闭。
建立与数据库的连接后,下一步是创建数据库。下面的代码就是这样做的。
ctx, cancelfunc := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelfunc()
res, err := db.ExecContext(ctx, "CREATE DATABASE IF NOT EXISTS "+dbname)
if err != nil {
log.Printf("Error %s when creating DB\n", err)
return
}
no, err := res.RowsAffected()
if err != nil {
log.Printf("Error %s when fetching rows", err)
return
}
log.Printf("rows affected %d\n", no)
打开数据库后,我们使用ExecContext方法来创建数据库。这个方法用来执行一个查询而不返回任何行。因为我们正在创建一个数据库,所以它不返回任何行,ExecContext ,可以用来创建数据库。作为一个负责任的开发者,我们传递了一个超时5秒的上下文,以确保控件在创建DB时不会被卡住,以防DB中出现任何网络错误或其他错误。只有当我们想在上下文超时前取消它时,才需要cancelfunc 。这里没有使用它,因此我们只是推迟了cancelfunc 的调用。
ExecContext 调用返回一个结果类型和一个错误。我们可以通过调用RowsAffected() 方法来检查受查询影响的行数。上面的代码创建了一个名为ecommerce 的数据库。
了解连接池
创建数据库后的下一步是连接到它并开始执行查询。在其他编程语言中,你可以通过运行use ecommerce 命令来选择数据库并开始执行查询。这在Go中可以通过使用代码db.ExecContext("USE ecommerce") 来完成。
虽然这似乎是一个合乎逻辑的方法,但这在Go中会导致意外的运行时错误。让我们了解一下这背后的原因。
当我们第一次执行sql.Open("mysql", dsn("")) ,返回的DB实际上是一个底层DB连接的池子。sql包负责维护这个池,自动创建和释放连接。这个DB也是安全的,可以被多个Goroutine并发访问。
由于DB 是一个连接池,如果我们在DB 上执行use ecommerce ,它将只在池中的一个DB连接上运行。当我们在DB 上执行另一个查询时,我们可能最终在池中的其他连接上运行查询,而use ecommerce 并没有被执行。这将导致错误Error Code: 1046. No database selected 。
解决办法很简单。我们关闭现有的连接到我们创建的没有指定DB名称的DB,并打开一个新的连接,其DB名称为ecommerce ,这是刚刚创建的。
db.Close()
db, err = sql.Open("mysql", dsn(dbname))
if err != nil {
log.Printf("Error %s when opening DB", err)
return
}
defer db.Close()
在上面几行中,我们关闭了现有的连接并打开了一个新的连接到数据库。这一次,我们在第2行中指定了DB名称ecommerce ,当打开与数据库的连接时。现在我们有一个连接池连接到ecommerce DB 😃。
连接池选项
有几个重要的连接池选项需要设置,以确保网络分区和其他可能发生在数据库连接上的运行时错误得到正确处理。
SetMaxOpenConns
这个选项是用来设置我们的应用程序允许的最大开放连接数。最好是设置这个,以确保我们的应用程序不会利用所有可用的MySQL连接,而使其他应用程序饥饿。
一个MySQL服务器的最大连接数可以通过运行下面的查询来确定
show variables like 'max_connections';
在我的例子中,它返回以下输出。151 是默认允许的最大连接数。你可以根据你的要求把它改成不同的值。

151是整个MySQL服务器允许的最大连接数,其中可能包括访问同一数据库的其他应用程序,以及对其他数据库的访问(如果存在)。
确保你设置的值低于max_connections ,这样其他应用程序和数据库就不会被饿死。我正在使用20 。请随时根据你的要求来改变它。
db.SetMaxOpenConns(20)
SetMaxIdleConns
这个选项限制了最大空闲连接数。连接池中的空闲连接数由该设置控制。
db.SetMaxIdleConns(20)
SetConnMaxLifetime
由于一些原因,连接变得无法使用是很常见的。例如,可能有一个防火墙或中间件终止了空闲的连接。这个选项可以确保驱动程序在被防火墙或中间件终止之前正确关闭空闲的连接。
db.SetConnMaxLifetime(time.Minute * 5)
请随时根据你的要求来改变上述选项。
平移数据库
Open 这个函数调用并没有与数据库建立实际的连接。它只是验证了DSN是否正确。PingContext()方法必须被调用以验证与数据库的实际连接。它对数据库进行ping并验证连接。
ctx, cancelfunc = context.WithTimeout(context.Background(), 5*time.Second)
defer cancelfunc()
err = db.PingContext(ctx)
if err != nil {
log.Printf("Errors %s pinging DB", err)
return
}
log.Printf("Connected to DB %s successfully\n", dbname)
我们创建了一个有5秒超时的上下文,以确保控件在ping DB时不会被卡住,以防出现网络错误或其他错误。
下面提供了完整的代码。
package main
import (
"context"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
const (
username = "root"
password = "password"
hostname = "127.0.0.1:3306"
dbname = "ecommerce"
)
func dsn(dbName string) string {
return fmt.Sprintf("%s:%s@tcp(%s)/%s", username, password, hostname, dbName)
}
func main() {
db, err := sql.Open("mysql", dsn(""))
if err != nil {
log.Printf("Error %s when opening DB\n", err)
return
}
defer db.Close()
ctx, cancelfunc := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelfunc()
res, err := db.ExecContext(ctx, "CREATE DATABASE IF NOT EXISTS "+dbname)
if err != nil {
log.Printf("Error %s when creating DB\n", err)
return
}
no, err := res.RowsAffected()
if err != nil {
log.Printf("Error %s when fetching rows", err)
return
}
log.Printf("rows affected %d\n", no)
db.Close()
db, err = sql.Open("mysql", dsn(dbname))
if err != nil {
log.Printf("Error %s when opening DB", err)
return
}
defer db.Close()
db.SetMaxOpenConns(20)
db.SetMaxIdleConns(20)
db.SetConnMaxLifetime(time.Minute * 5)
ctx, cancelfunc = context.WithTimeout(context.Background(), 5*time.Second)
defer cancelfunc()
err = db.PingContext(ctx)
if err != nil {
log.Printf("Errors %s pinging DB", err)
return
}
log.Printf("Connected to DB %s successfully\n", dbname)
}
运行上述代码将打印
2020/08/11 19:17:44 rows affected 1
2020/08/11 19:17:44 Connected to DB ecommerce successfully
这就是连接到MySQL和创建一个数据库的方法。
请留下您的意见和反馈。