第一章:Go语言与数据库基础
1.1 Go语言发展史:从“C++太复杂”说起
说起Go语言,我总想起2007年的那个秋天。Google内部,几位大佬——Rob Pike、Ken Thompson、Robert Griesemer——坐在一起吐槽。吐槽什么呢?C++编译太慢,Java太啰嗦,Python在大规模系统里又不够稳。说白了,他们想要一门新语言:编译快、写起来爽、天生支持并发。
嗯,这就是Go的起点。2009年正式对外发布,2012年推出1.0版本。我记得那时候国内用Go的人还很少,大家觉得“Google又搞了个玩具”。结果呢?Docker、Kubernetes、etcd这些重量级项目全用Go写的。你想想看,一个语言能撑起整个云原生生态,这本身就说明问题了。
我个人习惯把Go的发展分成三个阶段:
- 2007-2012:孕育期——内部使用,解决Google的痛点
- 2012-2018:爆发期——Docker和Kubernetes带火Go,国内大厂开始跟进
- 2018至今:成熟期——Go 1.11引入模块,1.18加入泛型,生态越来越完善
我在项目中遇到过不少从Python转Go的团队。刚开始大家都不适应,觉得写个接口怎么这么麻烦。但一旦上了生产环境,那个稳定性和性能,真香。
1.2 Go语言核心特性:为什么它适合做后端
Go的核心特性,我总结为四个字:简单、高效。别小看这两个词,能做到的编程语言真不多。
1.2.1 并发模型:goroutine + channel
这是Go最亮眼的地方。传统的多线程编程,锁来锁去,死锁、竞态条件,头都大了。Go换了个思路:用goroutine代替线程,用channel通信代替共享内存。
goroutine有多轻量?启动时只占几KB栈空间,可以轻松创建上万个。我做过一个压测,单机开了10万个goroutine,CPU和内存都还扛得住。换成Java线程试试?早就OOM了。
核心思想:不要通过共享内存来通信,而应该通过通信来共享内存。
1.2.2 编译速度:快到离谱
Go的编译速度,我用一个词形容:丝滑。一个中等规模的项目,从改代码到跑起来,基本就是秒级。对比Java的Maven或Gradle,你懂的。我曾经在团队里推广Go,就靠这一条说服了很多人——谁不想省下喝咖啡等编译的时间呢?
1.2.3 静态类型 + 垃圾回收
静态类型保证了代码的健壮性,编译期就能发现很多低级错误。垃圾回收又解放了程序员,不用手动管理内存。有人担心GC会影响性能,其实Go的GC一直在优化,1.8版本之后延迟已经降到毫秒级了。
1.2.4 标准库:够用且好用
Go的标准库非常强大。HTTP服务器、JSON解析、加密、压缩、模板引擎……开箱即用。我刚开始用Go写Web服务时,连第三方框架都没用,光靠标准库就撑起了一个日活百万的API网关。
个人建议:新手入门Go,先别急着上框架。把标准库玩熟了,你才能真正理解Go的设计哲学。
1.3 数据库基本概念:从“存数据”说起
数据库,说白了就是存数据的地方。但怎么存、怎么取、怎么保证不丢、怎么支持高并发,这里面的门道就多了。
1.3.1 关系型数据库 vs NoSQL
| 特性 | 关系型数据库(如MySQL) | NoSQL(如MongoDB) |
|---|---|---|
| 数据模型 | 表、行、列,严格Schema | 文档、键值、图,灵活Schema |
| 事务支持 | ACID,强一致性 | BASE,最终一致性 |
| 扩展方式 | 垂直扩展为主,水平扩展复杂 | 天然支持水平扩展 |
| 适用场景 | 金融、电商、ERP等强一致性场景 | 日志、社交、IoT等海量数据场景 |
我个人习惯:核心业务数据用MySQL,日志和缓存用NoSQL。别想着一个数据库打天下,那是不现实的。
1.3.2 SQL:和数据库对话的语言
SQL(Structured Query Language)是操作关系型数据库的标准语言。你不需要精通它,但增删改查(CRUD)必须手到擒来。
-- 创建表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入数据
INSERT INTO users (name, age) VALUES ('张三', 28);
-- 查询数据
SELECT * FROM users WHERE age > 18;
-- 更新数据
UPDATE users SET age = 29 WHERE name = '张三';
-- 删除数据
DELETE FROM users WHERE id = 1;
避坑指南:我曾经在生产环境执行过不带WHERE条件的UPDATE……嗯,那是一次刻骨铭心的教训。从那以后,我写任何UPDATE和DELETE之前,都会先写SELECT确认范围。
1.3.3 事务:要么全做,要么全不做
转账这个例子大家都懂:A扣钱,B加钱。如果A扣完钱系统挂了,B没加上,那钱就丢了。事务就是解决这个问题的。
事务的四个特性(ACID):
- 原子性(Atomicity):事务里的操作,要么全部成功,要么全部回滚
- 一致性(Consistency):事务前后,数据状态保持一致
- 隔离性(Isolation):并发事务之间互不干扰
- 持久性(Durability):事务提交后,数据永久保存
你想想看,没有事务的数据库,谁敢用来做支付系统?
1.4 Go连接MySQL初体验:动手写第一行代码
好了,理论说够了,咱们来点实际的。用Go连接MySQL,其实就三步:装驱动、建连接、写查询。
1.4.1 安装MySQL驱动
Go连接MySQL需要第三方驱动。最常用的是 go-sql-driver/mysql。安装命令:
go get -u github.com/go-sql-driver/mysql
这里要注意:Go的数据库接口是标准化的(database/sql),驱动只是实现这个接口。所以不管你用MySQL、PostgreSQL还是SQLite,操作方式几乎一样。这就是Go设计的高明之处。
1.4.2 写一个简单的连接示例
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql" // 匿名导入,注册驱动
)
func main() {
// 数据源名称格式:用户名:密码@tcp(主机:端口)/数据库名?参数
dsn := "root:123456@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True"
// 打开连接(注意:Open不会真正建立连接,只是验证参数)
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatal("打开数据库失败:", err)
}
defer db.Close() // 函数结束时关闭连接
// Ping一下,真正建立连接
err = db.Ping()
if err != nil {
log.Fatal("连接数据库失败:", err)
}
fmt.Println("连接MySQL成功!")
// 查询数据库版本
var version string
err = db.QueryRow("SELECT VERSION()").Scan(&version)
if err != nil {
log.Fatal("查询失败:", err)
}
fmt.Println("MySQL版本:", version)
}
小技巧:parseTime=True这个参数很重要。不加的话,Go会把MySQL的DATETIME类型当成字符串返回,你还要手动解析。加上之后,直接映射成time.Time,省心多了。
1.4.3 连接池:别每次都新建连接
数据库连接是个昂贵的资源。每次请求都新建连接,性能会非常差。Go的database/sql自带连接池,你只需要配置几个参数:
// 设置最大打开连接数
db.SetMaxOpenConns(100)
// 设置最大空闲连接数
db.SetMaxIdleConns(10)
// 设置连接最大存活时间
db.SetConnMaxLifetime(5 * time.Minute)
我在项目中遇到过一个问题:连接池设置得太小,高并发时请求排队,接口响应时间飙升。后来把MaxOpenConns从20调到100,问题就解决了。但也不能设得太大,否则数据库扛不住。
经验之谈:连接池的大小不是越大越好。一般建议 MaxOpenConns 设为 (CPU核心数 × 2 + 有效磁盘数) 这个公式算出来的值。当然,最终还是要靠压测来调优。
1.4.4 常见错误与排查
第一次连接MySQL,你可能会遇到这些问题:
- Access denied for user:用户名或密码错了,检查一下
- dial tcp 127.0.0.1:3306: connect: connection refused:MySQL没启动,或者端口不对
- unknown database:数据库不存在,先CREATE DATABASE一下
- packet too large:查询结果太大,调大MySQL的
max_allowed_packet参数
嗯,这些坑我都踩过。尤其是第一个,有一次我熬夜排查,最后发现是密码里有个特殊字符没转义……从那以后,我密码只用字母和数字。
小结
这一章我们从Go的诞生聊到了它的核心特性,又回顾了数据库的基本概念,最后亲手用Go连上了MySQL。你可能会觉得内容有点多,但这些都是后面章节的基础。尤其是连接池和事务的概念,后面讲ORM框架时会反复用到。
下一章,我们会深入database/sql标准库,看看Go是怎么抽象数据库操作的。到时候你会明白,为什么我说Go的接口设计是“少即是多”的典范。
课后练习:动手写一个Go程序,连接你自己的MySQL数据库,执行一条INSERT语句插入一条用户记录,再查询出来。试试看,你会发现Go操作数据库其实很简单。