Skip to content

MySQL 基础

MySQL 是最流行的关系型数据库之一,本教程介绍如何在 Go 中使用 MySQL 进行数据库操作。

📋 学习目标

  • 理解 MySQL 数据库基础
  • 掌握 Go 连接 MySQL 的方法
  • 学会执行 SQL 查询和操作
  • 理解连接池的使用
  • 掌握事务处理
  • 了解数据库最佳实践

🎯 MySQL 简介

为什么选择 MySQL

  • 流行度高: 使用广泛,社区活跃
  • 性能优秀: 适合 Web 应用
  • 易于使用: 学习曲线平缓
  • 功能完整: 支持事务、索引等

Go MySQL 驱动

Go 标准库提供了 database/sql 接口,常用的 MySQL 驱动:

  • github.com/go-sql-driver/mysql - 官方推荐
  • github.com/go-gorm/mysql - GORM 驱动

🚀 快速开始

安装驱动

bash
go get github.com/go-sql-driver/mysql

基本连接

go
package main

import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
)

func main() {
	// 数据源名称格式: username:password@tcp(host:port)/dbname?charset=utf8mb4
	dsn := "root:password@tcp(127.0.0.1:3306)/mydb?charset=utf8mb4&parseTime=True&loc=Local"
	
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		panic(err)
	}
	defer db.Close()
	
	// 测试连接
	if err := db.Ping(); err != nil {
		panic(err)
	}
	
	fmt.Println("数据库连接成功")
}

📊 数据库操作

创建表

go
func createTable(db *sql.DB) error {
	query := `
	CREATE TABLE IF NOT EXISTS users (
		id INT AUTO_INCREMENT PRIMARY KEY,
		name VARCHAR(100) NOT NULL,
		email VARCHAR(100) UNIQUE NOT NULL,
		age INT DEFAULT 0,
		created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
		updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
	) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
	`
	
	_, err := db.Exec(query)
	return err
}

插入数据

go
func insertUser(db *sql.DB, name, email string, age int) (int64, error) {
	query := "INSERT INTO users (name, email, age) VALUES (?, ?, ?)"
	
	result, err := db.Exec(query, name, email, age)
	if err != nil {
		return 0, err
	}
	
	id, err := result.LastInsertId()
	return id, err
}

// 批量插入
func insertUsers(db *sql.DB, users []User) error {
	query := "INSERT INTO users (name, email, age) VALUES (?, ?, ?)"
	
	stmt, err := db.Prepare(query)
	if err != nil {
		return err
	}
	defer stmt.Close()
	
	for _, user := range users {
		_, err := stmt.Exec(user.Name, user.Email, user.Age)
		if err != nil {
			return err
		}
	}
	
	return nil
}

查询数据

go
type User struct {
	ID        int
	Name      string
	Email     string
	Age       int
	CreatedAt time.Time
	UpdatedAt time.Time
}

// 查询单条记录
func getUserByID(db *sql.DB, id int) (*User, error) {
	query := "SELECT id, name, email, age, created_at, updated_at FROM users WHERE id = ?"
	
	var user User
	err := db.QueryRow(query, id).Scan(
		&user.ID,
		&user.Name,
		&user.Email,
		&user.Age,
		&user.CreatedAt,
		&user.UpdatedAt,
	)
	
	if err != nil {
		return nil, err
	}
	
	return &user, nil
}

// 查询多条记录
func getUsers(db *sql.DB, limit, offset int) ([]User, error) {
	query := "SELECT id, name, email, age, created_at, updated_at FROM users LIMIT ? OFFSET ?"
	
	rows, err := db.Query(query, limit, offset)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	
	var users []User
	for rows.Next() {
		var user User
		err := rows.Scan(
			&user.ID,
			&user.Name,
			&user.Email,
			&user.Age,
			&user.CreatedAt,
			&user.UpdatedAt,
		)
		if err != nil {
			return nil, err
		}
		users = append(users, user)
	}
	
	return users, rows.Err()
}

// 条件查询
func searchUsers(db *sql.DB, keyword string) ([]User, error) {
	query := `
		SELECT id, name, email, age, created_at, updated_at 
		FROM users 
		WHERE name LIKE ? OR email LIKE ?
	`
	
	keyword = "%" + keyword + "%"
	rows, err := db.Query(query, keyword, keyword)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	
	var users []User
	for rows.Next() {
		var user User
		err := rows.Scan(
			&user.ID,
			&user.Name,
			&user.Email,
			&user.Age,
			&user.CreatedAt,
			&user.UpdatedAt,
		)
		if err != nil {
			return nil, err
		}
		users = append(users, user)
	}
	
	return users, rows.Err()
}

更新数据

go
func updateUser(db *sql.DB, id int, name, email string, age int) error {
	query := "UPDATE users SET name = ?, email = ?, age = ? WHERE id = ?"
	
	result, err := db.Exec(query, name, email, age, id)
	if err != nil {
		return err
	}
	
	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return err
	}
	
	if rowsAffected == 0 {
		return fmt.Errorf("用户不存在")
	}
	
	return nil
}

删除数据

go
func deleteUser(db *sql.DB, id int) error {
	query := "DELETE FROM users WHERE id = ?"
	
	result, err := db.Exec(query, id)
	if err != nil {
		return err
	}
	
	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return err
	}
	
	if rowsAffected == 0 {
		return fmt.Errorf("用户不存在")
	}
	
	return nil
}

🔄 事务处理

go
func transferMoney(db *sql.DB, fromID, toID int, amount float64) error {
	tx, err := db.Begin()
	if err != nil {
		return err
	}
	
	defer func() {
		if err != nil {
			tx.Rollback()
		} else {
			tx.Commit()
		}
	}()
	
	// 扣除转出账户金额
	_, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID)
	if err != nil {
		return err
	}
	
	// 增加转入账户金额
	_, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toID)
	if err != nil {
		return err
	}
	
	return nil
}

🔧 连接池配置

go
func setupDB(dsn string) (*sql.DB, error) {
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return nil, err
	}
	
	// 设置最大打开连接数
	db.SetMaxOpenConns(25)
	
	// 设置最大空闲连接数
	db.SetMaxIdleConns(5)
	
	// 设置连接最大生存时间
	db.SetConnMaxLifetime(5 * time.Minute)
	
	// 测试连接
	if err := db.Ping(); err != nil {
		return nil, err
	}
	
	return db, nil
}

🏃‍♂️ 实践应用

完整的 CRUD 示例

go
package main

import (
	"database/sql"
	"fmt"
	"log"
	"time"
	_ "github.com/go-sql-driver/mysql"
)

type User struct {
	ID        int       `json:"id"`
	Name      string    `json:"name"`
	Email     string    `json:"email"`
	Age       int       `json:"age"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

type UserService struct {
	db *sql.DB
}

func NewUserService(dsn string) (*UserService, error) {
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return nil, err
	}
	
	if err := db.Ping(); err != nil {
		return nil, err
	}
	
	// 配置连接池
	db.SetMaxOpenConns(25)
	db.SetMaxIdleConns(5)
	db.SetConnMaxLifetime(5 * time.Minute)
	
	return &UserService{db: db}, nil
}

func (s *UserService) Create(name, email string, age int) (*User, error) {
	query := "INSERT INTO users (name, email, age) VALUES (?, ?, ?)"
	
	result, err := s.db.Exec(query, name, email, age)
	if err != nil {
		return nil, err
	}
	
	id, err := result.LastInsertId()
	if err != nil {
		return nil, err
	}
	
	return s.GetByID(int(id))
}

func (s *UserService) GetByID(id int) (*User, error) {
	query := "SELECT id, name, email, age, created_at, updated_at FROM users WHERE id = ?"
	
	var user User
	err := s.db.QueryRow(query, id).Scan(
		&user.ID,
		&user.Name,
		&user.Email,
		&user.Age,
		&user.CreatedAt,
		&user.UpdatedAt,
	)
	
	if err != nil {
		return nil, err
	}
	
	return &user, nil
}

func (s *UserService) List(limit, offset int) ([]User, error) {
	query := "SELECT id, name, email, age, created_at, updated_at FROM users LIMIT ? OFFSET ?"
	
	rows, err := s.db.Query(query, limit, offset)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	
	var users []User
	for rows.Next() {
		var user User
		err := rows.Scan(
			&user.ID,
			&user.Name,
			&user.Email,
			&user.Age,
			&user.CreatedAt,
			&user.UpdatedAt,
		)
		if err != nil {
			return nil, err
		}
		users = append(users, user)
	}
	
	return users, rows.Err()
}

func (s *UserService) Update(id int, name, email string, age int) error {
	query := "UPDATE users SET name = ?, email = ?, age = ? WHERE id = ?"
	
	result, err := s.db.Exec(query, name, email, age, id)
	if err != nil {
		return err
	}
	
	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return err
	}
	
	if rowsAffected == 0 {
		return fmt.Errorf("用户不存在")
	}
	
	return nil
}

func (s *UserService) Delete(id int) error {
	query := "DELETE FROM users WHERE id = ?"
	
	result, err := s.db.Exec(query, id)
	if err != nil {
		return err
	}
	
	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return err
	}
	
	if rowsAffected == 0 {
		return fmt.Errorf("用户不存在")
	}
	
	return nil
}

func main() {
	dsn := "root:password@tcp(127.0.0.1:3306)/mydb?charset=utf8mb4&parseTime=True&loc=Local"
	
	service, err := NewUserService(dsn)
	if err != nil {
		log.Fatal(err)
	}
	
	// 创建用户
	user, err := service.Create("张三", "zhangsan@example.com", 25)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("创建用户: %+v\n", user)
	
	// 查询用户
	user, err = service.GetByID(user.ID)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("查询用户: %+v\n", user)
	
	// 更新用户
	err = service.Update(user.ID, "李四", "lisi@example.com", 30)
	if err != nil {
		log.Fatal(err)
	}
	
	// 查询列表
	users, err := service.List(10, 0)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("用户列表: %+v\n", users)
}

⚠️ 注意事项

1. SQL 注入防护

go
// ✅ 使用参数化查询
db.Query("SELECT * FROM users WHERE id = ?", id)

// ❌ 不要拼接 SQL
db.Query(fmt.Sprintf("SELECT * FROM users WHERE id = %d", id))

2. 连接管理

go
// ✅ 总是关闭连接和结果集
rows, err := db.Query(...)
defer rows.Close()

// ✅ 使用连接池
db.SetMaxOpenConns(25)

3. 错误处理

go
// ✅ 检查所有错误
if err != nil {
	return err
}

// ✅ 检查查询结果
if rowsAffected == 0 {
	return fmt.Errorf("记录不存在")
}

📚 扩展阅读

⏭️ 下一章节

GORM 框架 → 学习更高级的 ORM 操作


💡 提示: 掌握原生 SQL 操作是理解 ORM 框架的基础,建议先熟悉 database/sql 的使用!

基于 VitePress 构建