- 文档
- 教程
【免费下载链接】build-web-application-with-golang
A golang ebook intro how to build a web with golang
本章实战围绕仓库中 en/code/src/apps/ch.5.2 目录下的可运行示例展开。该示例与 《Build Web Application with Golang》第 5.2 章 "MySQL" 一一对应,演示了如何基于 Go 标准库database/sql接口 +github.com/go-sql-driver/mysql纯 Go 驱动,对 MySQL 执行插入、更新、查询、删除四类核心操作。读完本文,你将掌握这套示例从「安装 MySQL → 建库建表 → 拉取依赖 → 编译运行」的完整落地流程,并能读懂database/sql中sql.Open、db.Prepare、stmt.Exec、rows.Scan等关键 API 的底层行为与正确用法。
一、示例在全书中的定位
第 5 章讨论的是 Go 的数据库访问层。在 5.1 节 "database/sql interface" 中,作者详细剖析了 Go 标准库database/sql与database/sql/driver的接口体系:驱动通过sql.Register(name, driver)在init()中注册自己,driver.Driver、driver.Conn、driver.Stmt、driver.Tx、driver.Result、driver.Rows等接口共同构成了驱动与上层代码的解耦层。正因为上层代码只依赖database/sql标准接口,5.2、5.3、5.4、5.5 各节可以用完全相同的调用模式去对接 MySQL、SQLite、PostgreSQL 等不同数据库——这正是本示例值得动手跑一遍的原因:它是验证「接口标准化 + 驱动可插拔」这一设计的最佳最小实验。
本示例的目录 en/code/src/apps/ch.5.2 由三个文件组成:
| 文件 | 作用 |
|---|---|
| main.go | 完整的 CRUD 示例程序(可编译、可运行) |
| schema.sql | userinfo表的建表语句 |
| readme.md | 运行本示例的 5 步设置指南 |
二、运行准备:5 步设置指南逐条拆解
readme.md 给出的设置流程非常精炼,下面结合仓库实际文件逐条展开,保证任何环境都能照做。
Step 1:安装并启动 MySQL
示例程序通过 TCP 连接 MySQL 服务端。在 Debian/Ubuntu 系可直接:
sudo apt-get install mysql-server sudo service mysql startmacOS 可用 Homebrew:
brew install mysql brew services start mysql安装完成后可用mysql -u root -p验证服务已就绪。示例本身对 MySQL 版本没有特殊要求,标准 5.x 与 8.x 均可。
Step 2:按 main.go 中的常量创建用户与数据库
示例把连接所需的账号信息直接写成常量,见 main.go:
const ( DB_USER = "user" DB_PASSWORD = "" DB_NAME = "test" )这三个常量对应关系如下:
| 常量 | 值 | 含义 |
|---|---|---|
DB_USER | user | MySQL 登录用户名 |
DB_PASSWORD | 空字符串 | 密码(示例默认无密码) |
DB_NAME | test | 目标数据库名 |
也就是说,你需要保证 MySQL 中存在一个用户名为user、密码为空的账号,并且该账号对名为test的数据库有读写权限。若你的环境与之不同,直接修改这三个常量即可,无需改动任何业务代码——这是该示例刻意把「环境差异」收敛到常量层的设计。
以 root 身份执行:
CREATE DATABASE test; CREATE USER 'user'@'localhost' IDENTIFIED BY ''; GRANT ALL PRIVILEGES ON test.* TO 'user'@'localhost'; FLUSH PRIVILEGES;注意:示例 DSN 中未携带密码(
user@/test形式),因此密码为空是满足运行条件的最简配置。生产环境请务必使用强密码,不要把凭证硬编码进源码。
Step 3:用 schema.sql 创建 userinfo 表
在 MySQL 客户端中执行建表脚本:
mysql -u user test < schema.sql或进入交互模式后. source执行。建表语句位于 schema.sql:
CREATE TABLE `userinfo` ( `uid` INT(10) NOT NULL AUTO_INCREMENT, `username` VARCHAR(64) NULL DEFAULT NULL, `departname` VARCHAR(64) NULL DEFAULT NULL, `created` DATE NULL DEFAULT NULL, PRIMARY KEY (`uid`) );表结构要点:
uid:主键,AUTO_INCREMENT自增,插入时无需显式赋值,程序通过LastInsertId()取回新行 ID;username/departname:长度 64 的字符串列;created:DATE类型,示例插入的是字符串"2012-12-09",MySQL 会按日期格式自动转换。
一个小提醒:示例代码 main.go 与 schema.sql 使用的是departname列名,而 5.2 章节正文 中的建表 SQL 写作department,二者并不一致。以仓库内可运行的 schema.sql 为准,二者择一保持一致即可,切勿混用导致 SQL 报错。
Step 4:运行 go get 安装远程依赖
示例只依赖一个第三方包:MySQL 驱动github.com/go-sql-driver/mysql。在项目目录下执行:
go get github.com/go-sql-driver/mysqlgo get会把驱动源码下载到本地并可供编译链接。需要说明的是,仓库并未为示例提供go.mod或vendor目录,依赖管理方式取决于你使用的 Go 版本:
- 使用 Go Modules(Go 1.11+ 默认):建议先
go mod init ch5.2再执行go get,让go mod tidy生成go.mod/go.sum; - 使用传统
GOPATH模式:可参考仓库根下 en/code/readme.md 的建议,把GOPATH指向代码目录,从而任意目录下都能正常 import。
Step 5:运行程序
一切就绪后,在 ch.5.2 目录 下执行:
go run main.go程序会依次打印插入、更新、查询、删除四阶段的运行日志,具体输出含义见下一节源码解读。
三、main.go 源码逐段解读
main.go 完整演示了database/sql的标准用法,下面按执行顺序拆解。
3.1 导入与驱动注册
import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" )注意_ "github.com/go-sql-driver/mysql"的空导入写法:如 5.1 节 所讲,Go 禁止导入后不使用的包,而空标识符导入的作用是只执行该包的init()而不直接引用其符号——go-sql-driver 正是利用init()调用sql.Register("mysql", ...)完成驱动注册。注册后,sql.Open("mysql", dsn)的第一个参数才能匹配到该驱动。
3.2 构造 DSN 并打开数据库句柄
dbSouce := fmt.Sprintf("%v:%v@/%v?charset=utf8", DB_USER, DB_PASSWORD, DB_NAME) db, err := sql.Open("mysql", dbSouce) checkErr(err) defer db.Close()按前面常量展开,实际 DSN 为:
user:@/test?charset=utf85.2 章节正文 给出了database/sql驱动的 DSN 支持格式全集,除本示例使用的user:password@/dbname外,还包括:
user@unix(/path/to/socket)/dbname?charset=utf8 user:password@tcp(localhost:5555)/dbname?charset=utf8 user:password@/dbname user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname可见@之前是认证信息,@之后可省略tcp(host:port)而走默认本地连接,也可显式指定 TCP 地址、Unix Socket 甚至 IPv6 地址;?charset=utf8是连接参数,保证客户端与 MySQL 之间的字符集一致。
需要特别澄清sql.Open的语义:它并不立即建立物理连接,只是初始化一个带连接池的DB句柄并校验 DSN 格式,真正的连接在首次执行 SQL 时才建立。db.Close()负责归还并关闭池中连接。从源码结构看,这正是 5.1 节 中database/sql包在DB结构上维护freeConn连接池(defer db.putConn(ci, err)回收连接、复用空闲连接)的高层封装。
3.3 插入:Prepare + Exec + LastInsertId
stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?") checkErr(err) res, err := stmt.Exec("astaxie", "software developement", "2012-12-09") checkErr(err) id, err := res.LastInsertId() checkErr(err) fmt.Println("id of last inserted row =", id)调用链为:db.Prepare让数据库预先编译 SQL 得到Stmt;stmt.Exec传入三个占位符参数执行插入;res.LastInsertId()取回自增主键uid的值,供后续 update/delete 使用。?占位符由驱动在客户端做参数化绑定,避免字符串拼接注入风险(详见本文第四节)。
3.4 更新:Exec + RowsAffected
stmt, err = db.Prepare("update userinfo set username=? where uid=?") checkErr(err) res, err = stmt.Exec("astaxieupdate", id) checkErr(err) affect, err := res.RowsAffected() checkErr(err) fmt.Println(affect, "row(s) changed")注意这里复用了同一个stmt变量(重新赋值),where uid=?中的?绑定到上一步取回的id。RowsAffected()返回本次 UPDATE 实际影响的行数,程序据此打印1 row(s) changed。
3.5 查询:Query + rows.Next/Scan
rows, err := db.Query("SELECT * FROM userinfo") checkErr(err) for rows.Next() { var uid int var username, department, created string err = rows.Scan(&uid, &username, &department, &created) checkErr(err) fmt.Println("uid | username | department | created") fmt.Printf("%3v | %6v | %6v | %6v\n", uid, username, department, created) }与前面「先 Prepare 再 Exec」不同,db.Query直接执行 SELECT 并返回*sql.Rows结果集。惯用模式是:rows.Next()在循环中逐行推进,每次调用rows.Scan(&uid, &username, &department, &created)把当前行的各列按顺序扫描进对应变量地址。示例中局部变量名department与表列departname略有出入,但 Scan 是按列顺序取值的,与变量名无关。
按 5.1 节 对driver.Rows.Next的说明,行数据在驱动层会被转换为[]byte等driver.Value类型,再由Scan转换并填充到目标变量。
3.6 删除:Prepare + Exec + RowsAffected
stmt, err = db.Prepare("delete from userinfo where uid=?") checkErr(err) res, err = stmt.Exec(id) checkErr(err) affect, err = res.RowsAffected() checkErr(err) fmt.Println(affect, "row(s) changed")按插入时拿到的id精准删除该行,整个示例形成「插入 → 更新 → 查询 → 删除」的完整闭环。程序结束后defer db.Close()自动回收连接。
3.7 错误处理:checkErr
func checkErr(err error) { if err != nil { panic(err) } }示例以panic快速暴露任何一步的错误(连接失败、SQL 语法错误、驱动缺失等),适合教程演示场景;真实业务中应改为返回 error 或记录日志,而不是让程序崩溃。
四、为什么必须用=?占位符
5.2 章节正文 特别强调:传参必须使用?占位符格式,这是防止 SQL 注入攻击的必要手段。示例中所有用户可控或动态生成的值(用户名、ID 等)都通过stmt.Exec(..., id)绑定,而不是拼进 SQL 字符串。驱动会在协议层对参数做转义与类型处理,恶意输入即使包含引号、分号也无法改变 SQL 结构。这是使用database/sql接口时的硬性安全规范,直接照搬本示例的写法即可获得正确且安全的默认行为。
五、预期运行输出
在环境正确的前提下运行go run main.go,输出大致为:
Inserting id of last inserted row = 1 Updating 1 row(s) changed Querying uid | username | department | created 1 | astaxieupdate | software developement | 2012-12-09 Deleting 1 row(s) changed各阶段含义:插入返回新行自增 ID;更新报告影响 1 行;查询回显表中全部记录(此时应看到更新后的用户名);删除再次报告影响 1 行。若某一步panic,请按错误信息回查:驱动是否go get、MySQL 是否启动、user/test库是否创建、schema.sql 是否执行。
六、延伸:同一套代码跑多种数据库
本示例的骨架(Open → Prepare/Exec → Query/Scan)完全建立在database/sql标准接口之上,因此只需更换驱动与 DSN 即可移植到其他数据库。仓库中同章节的姊妹示例正好提供了对照:
- ch.5.3(SQLite):使用
sqlite3 foo.db建库、.read schema.sql建表,同样go get后go run main.go; - ch.5.4 / ch.5.5 目录(PostgreSQL、Redis)沿用同构流程;
- 章节正文可参见 5.2 节(MySQL 驱动选型与 DSN 格式)与 5.1 节(
database/sql接口体系与连接池原理)。
对比这些示例即可直观感受到「接口标准化、驱动可插拔」带来的迁移成本优势——业务代码几乎零改动。
七、小结与常见问题速查
本文从 readme.md 的 5 步设置出发,完整展开了 MySQL 示例的运行流程与 main.go 的 CRUD 实现,并对照 schema.sql 与 5.1 节、5.2 节 章节正文讲解了sql.Open、Prepare/Exec、RowsAffected、LastInsertId、Query/Scan等 API 的语义与底层行为。最后给出高频问题的快速排查表:
| 现象 | 常见原因 |
|---|---|
panic: dial tcp ... connection refused | MySQL 未启动,或 DSN 未指定tcp(host:port)且服务不在默认 3306 |
panic: Access denied for user 'user' | user账号未创建、密码不符或未授权test库 |
panic: driver: bad connection | 驱动未通过go get安装,空导入注册失败 |
Error 1146: Table 'test.userinfo' doesn't exist | 未执行 schema.sql |
| SQL 报列名不存在 | 章节正文用department、示例代码用departname,请与 schema.sql 保持一致 |
按本文步骤走完一遍,你就拥有了一个可直接复用的 Go + MySQL CRUD 最小范例,可在此基础上平滑扩展为 Web 应用的数据库访问层。
- 文档
- 教程
【免费下载链接】build-web-application-with-golang
A golang ebook intro how to build a web with golang
相关推荐
使用 Go 的 database/sql 与 MySQL 驱动完成增删改查:build-web-application-with-golang 第 5.2 章实战
使用 Go 的 database/sql 与 MySQL 驱动完成增删改查:build web application with golang 第 5.2 章实
文档教程Build Web Application with Golang 第 5.2 节:使用 Go 的 MySQL 驱动实现 database/sql 增删改查实战
Build Web Application with Golang 第 5.2 节:使用 Go 的 MySQL 驱动实现 database/sql 增删改查实战
文档教程Go 操作 MySQL 实战:基于 database/sql 的增删改查完整指南(Build Web Application with Golang 第 5.2 节)
Go 操作 MySQL 实战:基于 database/sql 的增删改查完整指南(Build Web Application with Golang 第 5.2
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考