Go 语言原生 SQL 操作 MySQL 超详细全解 + 生产级项目模板(纯官方库无ORM)

在 Go 项目开发中,操作数据库是必备核心技能 。很多新手一上来就用 GORM、XORM 等 ORM 框架,但掌握原生 SQL 操作才是根基 ------ 性能更高、可控性更强、适配所有复杂查询。

Go 官方标准库 database/sql 提供了统一的数据库操作接口,搭配对应数据库驱动(如 MySQL、PostgreSQL),即可完成增删改查、事务、预处理、连接池等所有数据库操作。

本文全程使用 Go 原生标准库,无任何第三方 ORM 依赖,包含:环境搭建、增删改查、预处理语句、事务、连接池配置、注意事项、避坑指南


一、前置知识与环境准备

1.1 核心标准库

  • database/sql:Go 官方通用数据库接口,定义所有操作规范。
  • 数据库驱动 :具体数据库实现(如 github.com/go-sql-driver/mysql)。

1.2 支持的数据库

  • MySQL / MariaDB
  • PostgreSQL
  • SQLite
  • SQL Server
  • 其他兼容 SQL 标准的数据库

1.3 安装 MySQL 驱动

复制代码
go get github.com/go-sql-driver/mysql

1.4 测试数据表

复制代码
CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `age` int NOT NULL,
  `email` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

二、连接数据库(初始化连接池)

理论知识点

  • sql.Open()不会建立真实连接,仅初始化驱动配置。
  • db.Ping():真正建立连接并校验连通性。
  • *sql.DB线程安全,内部自带连接池,全局单例即可。
  • 必须设置连接池参数,防止连接泄漏。
代码示例
复制代码
package main

import (
	"database/sql"
	"fmt"
	"time"

	_ "github.com/go-sql-driver/mysql"
)

// 全局数据库对象(单例)
var db *sql.DB

// 初始化数据库
func initDB() (err error) {
	// DSN 格式:用户名:密码@tcp(IP:端口)/数据库名?charset=utf8mb4&parseTime=True
	dsn := "root:your-password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"

	// 打开数据库(仅初始化,不建立连接)
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		return err
	}

	// 真正建立连接并校验
	err = db.Ping()
	if err != nil {
		return err
	}

	// 设置连接池参数(非常重要)
	db.SetMaxOpenConns(10)    // 最大打开连接数
	db.SetMaxIdleConns(5)     // 最大空闲连接数
	db.SetConnMaxLifetime(time.Minute * 3) // 连接最大生命周期
	db.SetConnMaxIdleTime(time.Minute * 1) // 空闲最大时间

	fmt.Println("数据库连接成功!")
	return nil
}

func main() {
	if err := initDB(); err != nil {
		fmt.Printf("连接失败:%v\n", err)
		return
	}
	// 延迟关闭数据库
	defer db.Close()
}

注意事项

  1. _ 导入驱动:仅执行驱动初始化,不直接使用包内函数。
  2. parseTime=True 必须加,否则时间类型无法映射。
  3. *sql.DB 是连接池,不要每次操作都 Open/Close
  4. 必须设置连接池参数,否则会导致连接数过多崩溃。

三、增删改查(CRUD)全操作

3.1 插入数据(Create)

代码示例
复制代码
// 添加用户
func addUser(name string, age int, email string) (int64, error) {
	// SQL 语句
	sqlStr := "INSERT INTO user(name, age, email) VALUES(?, ?, ?)"
	// 执行 SQL
	result, err := db.Exec(sqlStr, name, age, email)
	if err != nil {
		return 0, err
	}
	// 获取自增 ID
	return result.LastInsertId()
}

3.2 查询单条数据(Retrieve One)

代码示例
复制代码
// User 结构体
type User struct {
	ID    int
	Name  string
	Age   int
	Email string
}

// 根据 ID 查询单个用户
func getUserByID(id int) (*User, error) {
	sqlStr := "SELECT id, name, age, email FROM user WHERE id = ?"
	// 查询单行
	row := db.QueryRow(sqlStr, id)

	// 定义变量接收数据
	var u User
	// 扫描数据(必须和查询字段顺序一致)
	err := row.Scan(&u.ID, &u.Name, &u.Age, &u.Email)
	if err != nil {
		return nil, err
	}
	return &u, nil
}

3.3 查询多条数据(Retrieve List)

代码示例
复制代码
// 查询所有用户
func getUserList() ([]*User, error) {
	sqlStr := "SELECT id, name, age, email FROM user"
	// 查询多行
	rows, err := db.Query(sqlStr)
	if err != nil {
		return nil, err
	}
	// 延迟关闭行对象(非常重要)
	defer rows.Close()

	// 定义结果切片
	var userList []*User

	// 循环遍历结果
	for rows.Next() {
		var u User
		// 扫描
		err := rows.Scan(&u.ID, &u.Name, &u.Age, &u.Email)
		if err != nil {
			return nil, err
		}
		userList = append(userList, &u)
	}
	// 返回遍历错误
	return userList, rows.Err()
}

3.4 更新数据(Update)

代码示例
复制代码
// 更新用户年龄
func updateUserAge(id, age int) (int64, error) {
	sqlStr := "UPDATE user SET age = ? WHERE id = ?"
	result, err := db.Exec(sqlStr, age, id)
	if err != nil {
		return 0, err
	}
	// 返回受影响行数
	return result.RowsAffected()
}

3.5 删除数据(Delete)

代码示例
复制代码
// 删除用户
func deleteUser(id int) (int64, error) {
	sqlStr := "DELETE FROM user WHERE id = ?"
	result, err := db.Exec(sqlStr, id)
	if err != nil {
		return 0, err
	}
	return result.RowsAffected()
}

四、预处理语句(Prepared Statement)

理论知识点

  • 预编译 SQL,防止 SQL 注入
  • 多次执行相同 SQL 时,性能更高
  • 服务器只编译一次,多次执行。
代码示例
复制代码
// 预处理插入
func prepareAddUser(name string, age int, email string) (int64, error) {
	sqlStr := "INSERT INTO user(name, age, email) VALUES(?, ?, ?)"
	// 预处理
	stmt, err := db.Prepare(sqlStr)
	if err != nil {
		return 0, err
	}
	defer stmt.Close()

	// 执行
	result, err := stmt.Exec(name, age, email)
	if err != nil {
		return 0, err
	}
	return result.LastInsertId()
}

五、数据库事务(Transaction)

理论知识点

  • 原子性:要么全部成功,要么全部回滚。
  • 核心方法:Begin()Commit()Rollback()
  • 事务中必须使用 tx.Exec,不能用 db.Exec
代码示例
复制代码
// 事务示例:同时添加两个用户
func transactionDemo() error {
	// 开启事务
	tx, err := db.Begin()
	if err != nil {
		return err
	}

	// 延迟处理:出错回滚
	defer func() {
		if r := recover(); r != nil {
			_ = tx.Rollback()
			fmt.Println("事务回滚:", r)
		}
	}()

	// 执行 SQL1
	sql1 := "INSERT INTO user(name, age) VALUES(?, ?)"
	_, err = tx.Exec(sql1, "事务用户1", 20)
	if err != nil {
		tx.Rollback()
		return err
	}

	// 执行 SQL2
	sql2 := "INSERT INTO user(name, age) VALUES(?, ?)"
	_, err = tx.Exec(sql2, "事务用户2", 21)
	if err != nil {
		tx.Rollback()
		return err
	}

	// 提交事务
	return tx.Commit()
}

六、空值处理(NULL 字段)

理论知识点

数据库字段为 NULL 时,直接用基础类型接收会报错,必须使用:

  • sql.NullString
  • sql.NullInt64
  • sql.NullBool
  • sql.NullFloat64
代码示例
复制代码
// 处理 NULL 字段
func getUserWithNullEmail(id int) (*User, error) {
	sqlStr := "SELECT id, name, age, email FROM user WHERE id = ?"
	row := db.QueryRow(sqlStr, id)

	var u User
	// email 可能为 NULL
	var email sql.NullString

	err := row.Scan(&u.ID, &u.Name, &u.Age, &email)
	if err != nil {
		return nil, err
	}

	// 判断是否为 NULL
	if email.Valid {
		u.Email = email.String
	} else {
		u.Email = "无邮箱"
	}
	return &u, nil
}

七、核心 API 总结

方法 作用
db.Exec() 执行增删改,返回受影响行数
db.QueryRow() 查询单行数据
db.Query() 查询多行数据
db.Prepare() 创建预处理语句
db.Begin() 开启事务
tx.Commit() 提交事务
tx.Rollback() 回滚事务
rows.Scan() 将结果扫描到变量
rows.Close() 关闭结果集(必须)

八、避坑指南(生产环境必看)

  1. 禁止字符串拼接 SQL ,必须使用 ? 占位符,防止 SQL 注入。
  2. rows.Close() 必须 defer,否则连接泄漏。
  3. *sql.DB 全局单例,不要频繁 Open/Close。
  4. 必须设置连接池参数,否则高并发下连接耗尽。
  5. 时间字段必须加 parseTime=True
  6. NULL 值必须用 sql.NullXXX 接收。
  7. 事务中所有操作必须使用 tx 对象,不能用 db
  8. 上线前必须用 go vet 检查未关闭的资源。

九、完整 main 函数测试

复制代码
func main() {
	// 1. 初始化数据库
	if err := initDB(); err != nil {
		fmt.Printf("连接失败:%v\n", err)
		return
	}
	defer db.Close()

	// 2. 添加用户
	id, _ := addUser("张三", 18, "zhangsan@qq.com")
	fmt.Println("新增ID:", id)

	// 3. 查询单个
	user, _ := getUserByID(1)
	fmt.Println("单个用户:", user)

	// 4. 查询列表
	list, _ := getUserList()
	fmt.Println("用户列表:", list)

	// 5. 更新
	rows, _ := updateUserAge(1, 20)
	fmt.Println("更新行数:", rows)

	// 6. 删除
	delRows, _ := deleteUser(1)
	fmt.Println("删除行数:", delRows)

	// 7. 事务
	_ = transactionDemo()
	fmt.Println("事务执行完成")
}

十、高频面试题

  1. sql.Open() 会建立连接吗? 答:不会,仅初始化配置,Ping() 才会真正建立连接。

  2. *sql.DB 是连接吗? 答:不是,是连接池,线程安全,全局单例使用。

  3. 为什么要关闭 rows 答:不关闭会占用数据库连接,导致连接泄漏

  4. **预处理语句的好处?**答:防 SQL 注入、提升多次执行性能。

  5. **Go 原生 SQL 对比 ORM 优势?**答:性能更高、可控性强、无学习成本、支持复杂 SQL。


知识图谱(文字版)

复制代码
Go 原生 SQL 操作数据库
├── 标准库:database/sql + 数据库驱动
├── 核心对象:*sql.DB(连接池)
├── 基础操作:增删改查
├── 高级特性:预处理、事务、NULL处理
├── 性能优化:连接池配置
├── 安全规范:防SQL注入、连接泄漏
└── 避坑指南

十一、Go 原生 SQL + MySQL 生产级项目模板(可直接上线)

11.1 前言

前面讲解了 Go 原生 database/sql 操作 MySQL 的基础用法,本章节给出可用于生产环境的完整项目模板 ,包含:配置分离、数据库连接池封装、CRUD 通用封装、事务封装、NULL 空值处理、连接泄漏防护、全局单例、日志打印、错误统一处理

全程无 ORM、无第三方框架,纯原生标准库,适配企业项目规范,可直接用于毕业设计、后端项目开发。

11.2 项目目录结构

复制代码
gosql-demo/
├── config/
│   └── db.go        # 数据库配置
├── dao/
│   ├── user.go      # 用户表 CRUD
│   └── common.go    # 通用数据库方法封装
├── model/
│   └── user.go      # 结构体模型
├── db/
│   └── mysql.go     # 数据库初始化、连接池、事务
├── main.go          # 入口测试
└── go.mod

11.3 go.mod 依赖

复制代码
module gosql-demo

go 1.21

require github.com/go-sql-driver/mysql v1.7.0

安装驱动

复制代码
go get github.com/go-sql-driver/mysql

11.4 config/db.go 数据库配置(解耦配置)

复制代码
package config

import "time"

// MySQLConfig MySQL配置结构体
type MySQLConfig struct {
	User     string
	Passwd   string
	Host     string
	Port     string
	DBName   string
	Charset  string
	ParseTme bool
	Loc      string

	// 连接池配置
	MaxOpenConns    int           // 最大打开连接
	MaxIdleConns    int           // 最大空闲连接
	ConnMaxLifetime time.Duration // 连接最大生命周期
	ConnMaxIdleTime time.Duration // 空闲连接最大时间
}

// DefaultMySQLConfig 默认配置
func DefaultMySQLConfig() *MySQLConfig {
	return &MySQLConfig{
		User:            "root",
		Passwd:          "root",
		Host:            "127.0.0.1",
		Port:            "3306",
		DBName:          "testdb",
		Charset:         "utf8mb4",
		ParseTme:        true,
		Loc:             "Local",
		MaxOpenConns:    20,
		MaxIdleConns:    10,
		ConnMaxLifetime: 3 * time.Minute,
		ConnMaxIdleTime: 1 * time.Minute,
	}
}

// DSN 拼接DSN连接串
func (c *MySQLConfig) DSN() string {
	return c.User + ":" + c.Passwd + "@tcp(" + c.Host + ":" + c.Port + ")/" +
		c.DBName + "?charset=" + c.Charset + "&parseTime=" + bool2Str(c.ParseTme) + "&loc=" + c.Loc
}

func bool2Str(b bool) string {
	if b {
		return "true"
	}
	return "false"
}

11.5 model/user.go 模型定义(含 NULL 空值兼容)

复制代码
package model

import "database/sql"

// User 用户表模型
type User struct {
	ID    int            `db:"id"`
	Name  string         `db:"name"`
	Age   int            `db:"age"`
	Email sql.NullString `db:"email"` // 允许为NULL
}

// GetEmail 处理NULL,返回默认值
func (u *User) GetEmail() string {
	if u.Email.Valid {
		return u.Email.String
	}
	return ""
}

11.6 db/mysql.go 数据库底层封装(全局单例、连接池、事务)

复制代码
package db

import (
	"database/sql"
	"fmt"
	"gosql-demo/config"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

// DB 全局数据库连接池单例
var DB *sql.DB

// InitMySQL 初始化数据库
func InitMySQL(cfg *config.MySQLConfig) error {
	var err error
	DB, err = sql.Open("mysql", cfg.DSN())
	if err != nil {
		return fmt.Errorf("open mysql failed: %w", err)
	}

	// 校验连通性
	if err = DB.Ping(); err != nil {
		return fmt.Errorf("ping mysql failed: %w", err)
	}

	// 设置连接池
	DB.SetMaxOpenConns(cfg.MaxOpenConns)
	DB.SetMaxIdleConns(cfg.MaxIdleConns)
	DB.SetConnMaxLifetime(cfg.ConnMaxLifetime)
	DB.SetConnMaxIdleTime(cfg.ConnMaxIdleTime)

	log.Println("MySQL 连接池初始化成功")
	return nil
}

// Close 关闭数据库
func Close() {
	if DB != nil {
		_ = DB.Close()
		log.Println("MySQL 连接池已关闭")
	}
}

// BeginTx 开启事务
func BeginTx() (*sql.Tx, error) {
	return DB.Begin()
}

11.7 dao/common.go 通用数据库方法封装(复用 CRUD)

复制代码
package dao

import (
	"database/sql"
	"gosql-demo/db"
)

// Exec 通用执行增删改
func Exec(sqlStr string, args ...interface{}) (int64, error) {
	result, err := db.DB.Exec(sqlStr, args...)
	if err != nil {
		return 0, err
	}
	return result.RowsAffected()
}

// Insert 通用插入,返回自增ID
func Insert(sqlStr string, args ...interface{}) (int64, error) {
	result, err := db.DB.Exec(sqlStr, args...)
	if err != nil {
		return 0, err
	}
	return result.LastInsertId()
}

// QueryRow 通用查询单行
func QueryRow(sqlStr string, args ...interface{}) *sql.Row {
	return db.DB.QueryRow(sqlStr, args...)
}

// Query 通用查询多行
func Query(sqlStr string, args ...interface{}) (*sql.Rows, error) {
	return db.DB.Query(sqlStr, args...)
}

// Prepare 预处理语句
func Prepare(sqlStr string) (*sql.Stmt, error) {
	return db.DB.Prepare(sqlStr)
}

11.8 dao/user.go 用户表业务层 CRUD(完整封装)

复制代码
package dao

import (
	"gosql-demo/model"
)

// AddUser 新增用户
func AddUser(name string, age int, email string) (int64, error) {
	sqlStr := "INSERT INTO user(name,age,email) VALUES(?,?,?)"
	return Insert(sqlStr, name, age, email)
}

// GetUserByID 根据ID查询
func GetUserByID(id int) (*model.User, error) {
	sqlStr := "SELECT id,name,age,email FROM user WHERE id=?"
	row := QueryRow(sqlStr, id)

	var u model.User
	err := row.Scan(&u.ID, &u.Name, &u.Age, &u.Email)
	if err != nil {
		return nil, err
	}
	return &u, nil
}

// ListUser 查询所有用户
func ListUser() ([]model.User, error) {
	sqlStr := "SELECT id,name,age,email FROM user"
	rows, err := Query(sqlStr)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var list []model.User
	for rows.Next() {
		var u model.User
		if err := rows.Scan(&u.ID, &u.Name, &u.Age, &u.Email); err != nil {
			return nil, err
		}
		list = append(list, u)
	}
	return list, rows.Err()
}

// UpdateUserAge 更新年龄
func UpdateUserAge(id int, age int) (int64, error) {
	sqlStr := "UPDATE user SET age=? WHERE id=?"
	return Exec(sqlStr, age, id)
}

// DeleteUser 删除用户
func DeleteUser(id int) (int64, error) {
	sqlStr := "DELETE FROM user WHERE id=?"
	return Exec(sqlStr, id)
}

11.9 事务封装示例(dao/tx.go 可选)

复制代码
package dao

import (
	"gosql-demo/db"
	"log"
)

// TxAddTwoUser 事务:同时新增两个用户,原子性
func TxAddTwoUser(name1 string, age1 int, name2 string, age2 int) error {
	tx, err := db.BeginTx()
	if err != nil {
		return err
	}
	defer func() {
		if r := recover(); r != nil {
			_ = tx.Rollback()
			log.Println("事务异常回滚:", r)
		}
	}()

	// 执行第一条
	_, err = tx.Exec("INSERT INTO user(name,age) VALUES(?,?)", name1, age1)
	if err != nil {
		_ = tx.Rollback()
		return err
	}

	// 执行第二条
	_, err = tx.Exec("INSERT INTO user(name,age) VALUES(?,?)", name2, age2)
	if err != nil {
		_ = tx.Rollback()
		return err
	}

	return tx.Commit()
}

11.10 main.go 入口测试(完整调用示例)

复制代码
package main

import (
	"fmt"
	"gosql-demo/config"
	"gosql-demo/dao"
	"gosql-demo/db"
)

func main() {
	// 1. 初始化数据库
	cfg := config.DefaultMySQLConfig()
	if err := db.InitMySQL(cfg); err != nil {
		fmt.Println("数据库初始化失败:", err)
		return
	}
	defer db.Close()

	// 2. 新增用户
	uid, err := dao.AddUser("李四", 22, "lisi@qq.com")
	if err != nil {
		fmt.Println("新增失败:", err)
	} else {
		fmt.Println("新增用户ID:", uid)
	}

	// 3. 查询单个
	user, _ := dao.GetUserByID(int(uid))
	fmt.Printf("查询用户:%+v,邮箱:%s\n", user, user.GetEmail())

	// 4. 查询列表
	list, _ := dao.ListUser()
	fmt.Println("用户列表:", list)

	// 5. 更新
	affect, _ := dao.UpdateUserAge(int(uid), 25)
	fmt.Println("更新行数:", affect)

	// 6. 事务测试
	err = dao.TxAddTwoUser("事务A", 20, "事务B", 21)
	if err != nil {
		fmt.Println("事务失败:", err)
	} else {
		fmt.Println("事务执行成功")
	}
}

11.11 生产环境核心规范(必看)

1. 连接池配置规范

  • MaxOpenConns:一般设置 20--50,不要过大
  • MaxIdleConns:为最大连接数一半
  • 必须设置连接超时,防止僵死连接

2. 安全规范

  • 禁止字符串拼接 SQL,全部使用 ? 占位符,防 SQL 注入
  • 敏感配置(账号密码)不要硬编码,使用环境变量 / 配置文件

3. 资源释放规范

  • 所有 rows 对象必须 defer Close ()
  • 预处理语句 stmt 必须延迟关闭
  • 程序退出时必须关闭数据库连接池

4. 错误处理规范

  • 所有数据库操作必须判断错误,禁止忽略
  • 错误信息需携带上下文,方便排查(使用fmt.Errorf("xxx: %w", err)

十二、知识图谱(文字版)

复制代码
Go 原生 SQL 操作 MySQL
├── 标准库:database/sql + 数据库驱动
├── 核心对象:*sql.DB(连接池)
├── 基础操作:增删改查
├── 高级特性:预处理、事务、NULL处理
├── 性能优化:连接池配置
├── 安全规范:防SQL注入、连接泄漏
├── 避坑指南
└── 生产级项目模板
    ├── 配置分离(config)
    ├── 模型定义(model)
    ├── 数据库封装(db)
    ├── 业务CRUD(dao)
    └── 入口测试(main)

版权声明

本文为原创 Go 后端技术文章,CSDN 首发,纯原生标准库实战教程 + 生产级项目模板,禁止未经授权转载、抄袭与搬运,侵权必究!

相关推荐
六月雨滴1 小时前
Oracle 数据库 ASM 自动存储管理
数据库·oracle·dba
老年DBA1 小时前
ZFS存储池配置终极指南
运维·数据库
CableTech_SQH1 小时前
江苏理工学院武进绿建区协同创新园智能化建设 F5G 全光方案百盛分析报告
大数据·网络·数据库·5g·信息与通信
楼田莉子2 小时前
Linux网络:网络多路IO模型详解
linux·网络·数据库
wen_zhufeng2 小时前
python-dotenv 使用文档
数据库·python·oracle
phltxy2 小时前
Redis Java 集成到 Spring Boot
数据库·redis·git
dadaobusi2 小时前
PCIe的ATS和PRS
java·网络·数据库
汽车仪器仪表相关领域2 小时前
HORIBA MEXA-584L 全功能汽车排放废气分析仪:便携精准排放检测 + 多参数同步测量 + 国六 / 欧 7 合规适配,汽车检测与调校的黄金标准
服务器·数据库·人工智能·功能测试·汽车·压力测试·可用性测试
qq_366086222 小时前
SQL 中 OR 与 UNION ALL选择指南
数据库·sql