【GO基础学习】Go操作数据库MySQL

文章目录


建立测试表

mysql数据库里面新建测试表person、place 表,方便后面测试:

sql 复制代码
CREATE TABLE `person` (
    `user_id` int(11) NOT NULL AUTO_INCREMENT,
    `username` varchar(260) DEFAULT NULL,
    `sex` varchar(260) DEFAULT NULL,
    `email` varchar(260) DEFAULT NULL,
    PRIMARY KEY (`user_id`)
  ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

CREATE TABLE place (
    country varchar(200),
    city varchar(200),
    telcode int
)ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

使用模版

先上怎么使用,然后解读每一步的操作,最后上一些详细的操作。

简单的操作MySQL的模版

go 复制代码
package main

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

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

// 定义一个全局对象db
var db *sql.DB

// 定义一个初始化数据库的函数
func initMySQLDB() (err error) {
	// DSN:Data Source Name
	dsn := "user:password@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
	// 不会校验账号密码是否正确
	// 注意!!!这里不要使用:=,我们是给全局变量赋值,然后在main函数中使用全局变量db
	// 打开数据库连接(但不校验)
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		return fmt.Errorf("sql.Open failed: %v", err)
	}

	// 校验数据库连接
	if err = db.Ping(); err != nil {
		return fmt.Errorf("db.Ping failed: %v", err)
	}

	// 设置数据库连接池参数
	db.SetMaxOpenConns(10)           // 最大打开连接数
	db.SetMaxIdleConns(5)            // 最大空闲连接数
	db.SetConnMaxLifetime(time.Hour) // 每个连接的生命周期

	fmt.Println("Database connection initialized successfully.")
	return nil
}


func main() {
	// 初始化数据库
	if err := initMySQLDB(); err != nil {
		log.Fatalf("initMySQLDB failed: %v\n", err)
	}
	defer db.Close() // 程序退出时关闭数据库连接

	// 使用db进行数据库的增删改查操作...

}
  1. database/sql 是 Go 的标准库,提供了与 SQL 数据库交互的接口,它以高度抽象的方式操作数据库,但需要具体的驱动来完成底层实现 ,如 go-sql-driver/mysql
  2. github.com/go-sql-driver/mysql是 MySQL 的驱动程序,它实现了 database/sql 的接口,并通过 MySQL 的 C/S 协议与数据库通信。

可以通过go get -u github.com/go-sql-driver/mysql或者go mod tidy下载库:

go-sql-driver/mysql 的导入中,使用匿名导入-,作用是调用驱动的 init 函数:

go 复制代码
func init() {
	sql.Register("mysql", &MySQLDriver{})
}

sql.Register

  • 注册驱动,建立 driverName 和驱动实现的映射关系。
  • 当调用 sql.Open("mysql", ...) 时,通过这个映射找到对应的驱动。

MySQLDriver 实现了 driver.Driver 接口:

  • Open(name string) (driver.Conn, error):建立与数据库的连接。
  1. 数据库对象

(1)sql.DB

  • 代表与数据库的连接池(不是单个连接)。
  • 提供线程安全的操作,管理连接复用、超时、连接关闭等。
  • 常用方法:
    • Open(driverName, dataSourceName string): 打开数据库连接。
    • Ping(): 测试连接是否可用。
    • Query()Exec():执行查询和非查询操作。

(2)sql.Tx

  • 代表事务,提供对事务操作的封装。

(3)sql.Rows

  • 代表查询返回的多行结果。

(4)sql.Row

  • 代表查询返回的单行结果。
  1. db, err = sql.Open("mysql", dns)

(1)sql.Open

  • 检查驱动是否注册。
  • 初始化 sql.DB 对象(不建立实际连接,仅配置连接信息)。
  • 返回一个连接池对象,连接池会在需要时与数据库建立连接。

(2)数据源名称(DSN)的解析

  • DSN 是连接信息的字符串形式:
java 复制代码
 user:password@protocol(address)/dbname?param1=value1&param2=value2
  • 驱动负责解析这些信息,构建与数据库通信的上下文。

(3)连接建立

  • 当执行 db.Ping() 或第一次 Query 时,sql.DB 会通过驱动的 Open 方法与数据库建立物理连接。
  • go-sql-driver/mysql 解析 DSN 后,通过 TCP 或 Unix 套接字与 MySQL 服务器通信。
  1. 数据库连接池的管理
    sql.DB 管理了一个连接池,提供连接复用、超时管理等功能:
  • 最大连接数

    • SetMaxOpenConns(n int):设置最大打开连接数。如果n大于0且小于最大闲置连接数,会将最大闲置连接数减小到匹配最大开启连接数的限制。 如果n<=0,不会限制最大开启连接数,默认为0(无限制)。
  • 最大空闲连接数

    • SetMaxIdleConns(n int):设置连接池中的最大空闲连接数。如果n大于最大开启连接数,则新的最大闲置连接数会减小到匹配最大开启连接数的限制。 如果n<=0,不会保留闲置连接。
  • 连接生命周期

    • SetConnMaxLifetime(d time.Duration):设置连接的最大生存时间。

insert操作

插入、更新和删除操作都使用Exec方法。

go 复制代码
func main() {
	// 初始化数据库
	if err := initMySQLDB(); err != nil {
		log.Fatalf("initMySQLDB failed: %v\n", err)
	}
	defer db.Close() // 程序退出时关闭数据库连接

	// 执行数据库操作示例
	insertRowDemo()
}

// insertRowDemo 插入数据示例
func insertRowDemo() {
	sqlStr := "INSERT INTO person(username, sex, email) VALUES (?, ?, ?)"
	result, err := db.Exec(sqlStr, "王五", "男", "XXX@qq.com")
	if err != nil {
		log.Printf("Insert failed: %v\n", err)
		return
	}

	// 获取新插入数据的 ID
	lastInsertID, err := result.LastInsertId()
	if err != nil {
		log.Printf("Get LastInsertId failed: %v\n", err)
		return
	}
	fmt.Printf("Insert success, new ID is %d.\n", lastInsertID)
}

update操作

go 复制代码
func main() {
	// 初始化数据库
	if err := initMySQLDB(); err != nil {
		log.Fatalf("initMySQLDB failed: %v\n", err)
	}
	defer db.Close() // 程序退出时关闭数据库连接

	// 执行数据库操作示例
	updateRow(2)
}

// 更新数据
func updateRow(userID int) {
	sqlStr := "UPDATE person SET username = ?, email = ? WHERE user_id = ?"
	result, err := db.Exec(sqlStr, "李四", "XXX@162.com", userID)
	if err != nil {
		log.Printf("Update failed: %v\n", err)
		return
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		log.Printf("Get RowsAffected failed: %v\n", err)
		return
	}
	fmt.Printf("Update success, %d rows affected.\n", rowsAffected)
}

delete操作

go 复制代码
func main() {
	// 初始化数据库
	if err := initMySQLDB(); err != nil {
		log.Fatalf("initMySQLDB failed: %v\n", err)
	}
	defer db.Close() // 程序退出时关闭数据库连接

	// 执行数据库操作示例
	deleteRow(2)
}


// 删除数据
func deleteRow(userID int) {
	sqlStr := "DELETE FROM person WHERE user_id = ?"
	result, err := db.Exec(sqlStr, userID)
	if err != nil {
		log.Printf("Delete failed: %v\n", err)
		return
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		log.Printf("Get RowsAffected failed: %v\n", err)
		return
	}
	fmt.Printf("Delete success, %d rows affected.\n", rowsAffected)
}

select操作

  1. 单条记录查询
go 复制代码
// Person 定义用户信息结构体
type Person struct {
	UserID   int    `db:"user_id"`
	Username string `db:"username"`
	Sex      string `db:"sex"`
	Email    string `db:"email"`
}
// main 主函数
func main() {
	// 初始化数据库
	if err := initMySQLDB(); err != nil {
		log.Fatalf("initMySQLDB failed: %v\n", err)
	}
	defer db.Close() // 程序退出时关闭数据库连接

	// 执行数据库操作示例
	queryOneRow(3)
}

// 查询单条数据
func queryOneRow(userID int) {
	sqlStr := "SELECT user_id, username, sex, email FROM person WHERE user_id = ?"
	var person Person
	err := db.QueryRow(sqlStr, userID).Scan(&person.UserID, &person.Username, &person.Sex, &person.Email)
	if err != nil {
		if err == sql.ErrNoRows {
			log.Printf("No record found for userId: %d\n", userID)
			return
		}
		log.Printf("Query failed: %v\n", err)
		return
	}
	fmt.Printf("Query result: %+v\n", person)
}

Scan方法,已经关闭rows释放持有的数据库链接,不需要我们自己关闭了:

运行结果:

  1. 多条记录查询:
go 复制代码
// 查询多条数据
func queryMultipleRows() {
	sqlStr := "SELECT userId, username, sex, email FROM person"
	rows, err := db.Query(sqlStr)
	if err != nil {
		log.Printf("Query failed: %v\n", err)
		return
	}
	defer rows.Close()

	for rows.Next() {
		var person Person
		err := rows.Scan(&person.UserID, &person.Username, &person.Sex, &person.Email)
		if err != nil {
			log.Printf("Row scan failed: %v\n", err)
			continue
		}
		fmt.Printf("Row: %+v\n", person)
	}

	// 检查遍历时是否遇到错误
	if err := rows.Err(); err != nil {
		log.Printf("Rows iteration error: %v\n", err)
	}
}

小结

  1. 插入、更新和删除操作都使用Exec方法,传入sql语句,以及sql里面需要的参数:
go 复制代码
result, err := db.Exec(sql, args...)
  1. 单条记录查询:db.QueryRow(sql, args...).Scan(),使用了scan方法,会自动关闭连接,不需要手动关闭。
  2. 多条记录查询:rows, err := db.Query(sql),在使用rows.Next()遍历所有记录前,需要defer rows.Close(),防止遍历时中断,不能关闭所有链接。

SQL预处理

上面的示例中所有 SQL 参数均使用占位符 (?),避免了 SQL 注入的风险。

不正确的操作,sql注入示例:

go 复制代码
// sql注入示例
func sqlInjectDemo(name string) {
	sqlStr := fmt.Sprintf("select id, name, age from user where name='%s'", name)
	fmt.Printf("SQL:%s\n", sqlStr)
	var u user
	err := db.QueryRow(sqlStr).Scan(&u.id, &u.name, &u.age)
	if err != nil {
		fmt.Printf("exec failed, err:%v\n", err)
		return
	}
	fmt.Printf("user:%#v\n", u)
}

传入下面字段都会引发sql注入:

go 复制代码
sqlInjectDemo("xxx' or 1=1#")
sqlInjectDemo("xxx' union select * from user #")
sqlInjectDemo("xxx' and (select count(*) from user) <10 #")

关于预防sql注入,除了采用直接在 db.Execdb.Query 中使用占位符:

go 复制代码
sqlStr := "SELECT * FROM person WHERE username = ? AND email = ?"
rows, err := db.Query(sqlStr, "Alice", "alice@example.com")

还可以采用db.Prepare,它是为多次执行相似语句设计的,它可以提前编译和缓存 SQL 语句:

go 复制代码
stmt, err := db.Prepare("INSERT INTO person(username, sex, email) VALUES (?, ?, ?)")
if err != nil {
    log.Fatalf("Prepare failed: %v", err)
}
defer stmt.Close()

// 多次执行相似的 SQL 语句
_, err = stmt.Exec("Alice", "Female", "alice@example.com")
_, err = stmt.Exec("Bob", "Male", "bob@example.com")

关于这两种的选择:

单次操作: 如果只执行一次 SQL 语句,直接使用占位符(?)更方便。
多次操作: 如果需要多次执行 相似的 SQL 语句(如批量插入或更新),使用 Prepare 可以提高性能。
安全性: 无论使用哪种方式,参数绑定的本质是由数据库驱动安全地处理的,因此都可以防止 SQL 注入。


事务

在数据库操作中,事务(Transaction)是一组操作的集合,这些操作要么全部成功,要么全部失败,确保数据库处于一致性状态。

事务的特性 (ACID)

  1. 原子性 (Atomicity):

    • 事务中的所有操作要么全部完成,要么全部不执行。
  2. 一致性 (Consistency):

    • 事务完成后,数据库状态从一个一致状态转变为另一个一致状态。
  3. 隔离性 (Isolation):

    • 一个事务的执行不能被其他事务干扰,事务之间互不影响。
  4. 持久性 (Durability):

    • 事务完成后,其对数据库的更改是永久性的。

Go 中的事务操作

在 Go 的 database/sql 包中,可以通过以下方式使用事务:

  1. 开启事务 : db.Begin()
  2. 提交事务 : tx.Commit()
  3. 回滚事务 : tx.Rollback()

事务操作的基本流程

  1. 开启事务 : 使用 db.Begin() 创建一个事务对象。

  2. 执行事务中的多条 SQL 操作 : 使用事务对象 tx 的方法执行语句。

  3. 提交事务或回滚事务:

    • 如果所有操作成功,调用 tx.Commit() 提交事务。
    • 如果任何操作失败,调用 tx.Rollback() 回滚事务。

完整示例代码:

go 复制代码
package main

import (
	"database/sql"
	"fmt"
	"log"

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

// 定义一个全局对象 db
var db *sql.DB

// 初始化数据库连接
func initMySQLDB() error {
	var err error
	dsn := "user:password@tcp(127.0.0.1:3306)/bank?charset=utf8mb4&parseTime=True"
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		return fmt.Errorf("sql.Open failed: %v", err)
	}

	if err = db.Ping(); err != nil {
		db.Close()
		return fmt.Errorf("db.Ping failed: %v", err)
	}

	fmt.Println("Database connected successfully.")
	return nil
}

// 转账操作
func transferMoney(fromAccountID, toAccountID int, amount float64) error {
	// 开启事务
	tx, err := db.Begin()
	if err != nil {
		return fmt.Errorf("begin transaction failed: %v", err)
	}

	// 执行操作:扣减转出账户余额
	sqlStr1 := "UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?"
	_, err = tx.Exec(sqlStr1, amount, fromAccountID, amount)
	if err != nil {
		tx.Rollback() // 回滚事务
		return fmt.Errorf("deduct from account failed: %v", err)
	}

	// 执行操作:增加转入账户余额
	sqlStr2 := "UPDATE accounts SET balance = balance + ? WHERE id = ?"
	_, err = tx.Exec(sqlStr2, amount, toAccountID)
	if err != nil {
		tx.Rollback() // 回滚事务
		return fmt.Errorf("add to account failed: %v", err)
	}

	// 提交事务
	err = tx.Commit()
	if err != nil {
		return fmt.Errorf("commit transaction failed: %v", err)
	}

	fmt.Println("Transfer successful.")
	return nil
}

func main() {
	// 初始化数据库
	if err := initMySQLDB(); err != nil {
		log.Fatalf("Database initialization failed: %v", err)
	}
	defer db.Close()

	// 转账操作
	err := transferMoney(1, 2, 100.00)
	if err != nil {
		log.Printf("Transfer failed: %v\n", err)
	} else {
		fmt.Println("Transfer completed.")
	}
}

sqlx

sqlx 是 Go 社区流行的一个库,基于 Go 的标准库 database/sql 封装而成。sqlx 提供了许多增强功能,例如结构体绑定、便捷的查询操作等,使开发者更容易操作数据库。

sqlx 的优势

  1. 简化查询结果映射:

    • 支持将查询结果直接映射到结构体,省去手动 rows.Scan 的繁琐操作。
  2. Named Queries:

    • 支持命名参数,避免了大量占位符的混乱。
  3. 事务支持 :

    • 简化事务处理,与 database/sql 一致。
  4. 预加载/预处理:

    • 提供便捷的批量插入和动态查询构建。

增删改成操作

可以通过go get github.com/jmoiron/sqlx或者go mod tidy进行下载。

示例代码:

go 复制代码
package main

import (
	"fmt"
	"log"

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

var db *sqlx.DB

// 初始化数据库连接
func initMySQLDB() error {
	dsn := "user:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True"
	var err error
	db, err = sqlx.Connect("mysql", dsn) // 使用 sqlx.Connect
	if err != nil {
		return fmt.Errorf("failed to connect to database: %w", err)
	}

	// 设置连接池参数
	db.SetMaxOpenConns(10)
	db.SetMaxIdleConns(5)
	return nil
}

func main() {
	err := initMySQLDB()
	if err != nil {
		log.Fatalf("Database connection failed: %v", err)
	}
	defer db.Close()

	fmt.Println("Database connected successfully.")
}

插入、更新、删除操作 上,基本和前面的相同,都使用Exec方法,传入sql语句,以及sql里面需要的参数:

go 复制代码
func insertPerson(username, sex, email string) error {
	_, err := db.Exec("INSERT INTO person (username, sex, email) VALUES (?, ?, ?)", username, sex, email)
	if err != nil {
		return fmt.Errorf("insert failed: %w", err)
	}
	return nil
}

func updatePersonEmail(id int, email string) error {
	_, err := db.Exec("UPDATE person SET email = ? WHERE user_id = ?", email, id)
	if err != nil {
		return fmt.Errorf("update failed: %w", err)
	}
	return nil
}

func deletePerson(id int) error {
	_, err := db.Exec("DELETE FROM person WHERE user_id = ?", id)
	if err != nil {
		return fmt.Errorf("delete failed: %w", err)
	}
	return nil
}

查询上面,可以直接通过结构体进行映射,无需自己通过Scan函数一个一个映射,与前面调用QueryRowQuery和不同的是,这里是GetSelect方法:

go 复制代码
type Person struct {
	UserID       int    `db:"user_id"`
	Username string `db:"username"`
	Sex      string `db:"sex"`
	Email    string `db:"email"`
}

// 单条记录
func getPersonByID(id int) (*Person, error) {
	var person Person
	err := db.Get(&person, "SELECT * FROM person WHERE user_id = ?", id)
	if err != nil {
		return nil, fmt.Errorf("query failed: %w", err)
	}
	return &person, nil
}
// 多条记录
func getAllPersons() ([]Person, error) {
	var persons []Person
	err := db.Select(&persons, "SELECT * FROM person")
	if err != nil {
		return nil, fmt.Errorf("query failed: %w", err)
	}
	return persons, nil
}

func main() {
	person, err := getPersonByID(1)
	if err != nil {
		log.Fatalf("Failed to get person: %v", err)
	}
	fmt.Printf("Person: %+v\n", person)

	persons, err2 := getAllPersons()
	if err2 != nil {
		log.Fatalf("Failed to get persons: %v", err2)
	}
	for _, pers := range persons {
		fmt.Printf("Person: %+v\n", pers)
	}
}

另外一个优势是:sqlx 支持命名参数(Named Queries),使代码更加可读:

(1)NamedExec方法用来绑定SQL语句与结构体或map中的同名字段。

go 复制代码
func insertPersonNamed(username, sex, email string) error {
	sqlStr := "INSERT INTO person (username, sex, email) VALUES (:username, :sex, :email)"
	_, err := db.NamedExec(sqlStr, map[string]interface{}{
		"username": username,
		"sex":      sex,
		"email":    email,
	})
	if err != nil {
		return fmt.Errorf("insert failed: %w", err)
	}
	return nil
}

(2)NamedQuery 可以将查询的参数以结构体或映射(map[string]interface{})的形式传递。

go 复制代码
type Person struct {
	UserID       int    `db:"user_id"`
	Username string `db:"username"`
	Sex      string `db:"sex"`
	Email    string `db:"email"`
}

// 使用命名参数查询单条记录
func getPersonByUsername(username string) (*Person, error) {
	sqlStr := "SELECT * FROM person WHERE username = :username"
	rows, err := db.NamedQuery(sqlStr, map[string]interface{}{
		"username": username,
	})
	if err != nil {
		return nil, fmt.Errorf("query failed: %w", err)
	}
	defer rows.Close()

	var person Person
	if rows.Next() {
		err = rows.StructScan(&person)
		if err != nil {
			return nil, fmt.Errorf("scan failed: %w", err)
		}
	}
	return &person, nil
}

func main() {
	person, err := getPersonByUsername("张三")
	if err != nil {
		log.Fatalf("Failed to get person: %v", err)
	}
	fmt.Printf("Person: %+v\n", person)
}

参数为结构体: 你也可以直接使用结构体传递参数(字段需要对应 SQL 中的命名参数):

go 复制代码
func getPersonUsingStruct(p *Person) (*Person, error) {
	sqlStr := "SELECT * FROM person WHERE username = :username AND email = :email"
	rows, err := db.NamedQuery(sqlStr, p)
	if err != nil {
		return nil, fmt.Errorf("query failed: %w", err)
	}
	defer rows.Close()

	var person Person
	if rows.Next() {
		err = rows.StructScan(&person)
		if err != nil {
			return nil, fmt.Errorf("scan failed: %w", err)
		}
	}
	return &person, nil
}

func main() {
	input := &Person{Username: "张三", Email: "zhangsan@example.com"}
	person, err := getPersonUsingStruct(input)
	if err != nil {
		log.Fatalf("Failed to get person: %v", err)
	}
	fmt.Printf("Person: %+v\n", person)
}

事务

事务操作与 database/sql 基本一样:

go 复制代码
func transferMoney(tx *sqlx.Tx, fromID, toID int, amount float64) error {
	// 扣减账户余额
	_, err := tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID)
	if err != nil {
		return fmt.Errorf("deduct failed: %w", err)
	}

	// 增加账户余额
	_, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toID)
	if err != nil {
		return fmt.Errorf("add failed: %w", err)
	}
	return nil
}

func main() {
	tx, err := db.Beginx() // 开启事务
	if err != nil {
		log.Fatalf("Failed to begin transaction: %v", err)
	}

	err = transferMoney(tx, 1, 2, 100.00)
	if err != nil {
		tx.Rollback()
		log.Fatalf("Transaction failed: %v", err)
	}

	err = tx.Commit() // 提交事务
	if err != nil {
		log.Fatalf("Failed to commit transaction: %v", err)
	}

	fmt.Println("Transaction completed successfully.")
}

sqlx.In 的使用

sqlx.Insqlx 提供的一个工具函数,解决了 SQL 查询中 IN 子句传递动态参数的问题 。标准库 database/sql 不支持直接传递切片作为 IN 子句的参数,而 sqlx.In 自动将切片展开为占位符并处理参数绑定。

(1)使用 sqlx.In 查询多条记录【需求: 查询多个用户的信息,用户 ID 动态传入】

go 复制代码
func getPersonsByIDs(ids []int) ([]Person, error) {
	// 使用 sqlx.In 构建 SQL 语句
	query, args, err := sqlx.In("SELECT * FROM person WHERE user_id IN (?)", ids)
	if err != nil {
		return nil, fmt.Errorf("sqlx.In failed: %w", err)
	}

	// 将查询转换为数据库驱动支持的语句
	query = db.Rebind(query)

	var persons []Person
	err = db.Select(&persons, query, args...)
	if err != nil {
		return nil, fmt.Errorf("query failed: %w", err)
	}
	return persons, nil
}

func main() {
	ids := []int{1, 2, 3}
	persons, err := getPersonsByIDs(ids)
	if err != nil {
		log.Fatalf("Failed to get persons: %v", err)
	}
	for _, person := range persons {
		fmt.Printf("Person: %+v\n", person)
	}
}

(2)sqlx.In 插入数据【需求: 批量插入用户数据,使用动态参数。】

go 复制代码
func insertPersons(persons []Person) error {
	// 构建批量插入语句和参数
	values := []interface{}{}
	for _, p := range persons {
		values = append(values, p.Username, p.Sex, p.Email)
	}

	query, args, err := sqlx.In("INSERT INTO person (username, sex, email) VALUES (?, ?, ?)", values...)
	if err != nil {
		return fmt.Errorf("sqlx.In failed: %w", err)
	}

	// Rebind 将 SQL 语句转换为当前数据库驱动支持的语法
	query = db.Rebind(query)

	// 执行插入
	_, err = db.Exec(query, args...)
	if err != nil {
		return fmt.Errorf("insert failed: %w", err)
	}
	return nil
}

func main() {
	persons := []Person{
		{Username: "Alice", Sex: "女", Email: "alice@example.com"},
		{Username: "Bob", Sex: "男", Email: "bob@example.com"},
	}
	err := insertPersons(persons)
	if err != nil {
		log.Fatalf("Failed to insert persons: %v", err)
	}
	fmt.Println("Batch insert successful!")
}

相关推荐
做cv的小昊1 小时前
大语言模型系统:【CMU 11-868】课程学习笔记02——GPU编程基础1(GPU Programming Basics 1)
人工智能·笔记·学习·语言模型·llm·transformer·agent
小吴编程之路6 小时前
MySQL 索引核心特性深度解析:从底层原理到实操应用
数据库·mysql
炽烈小老头7 小时前
【每天学习一点算法 2026/03/08】相交链表
学习·算法·链表
~莫子7 小时前
MySQL集群技术
数据库·mysql
凤山老林7 小时前
SpringBoot 使用 H2 文本数据库构建轻量级应用
java·数据库·spring boot·后端
就不掉头发7 小时前
Linux与数据库进阶
数据库
与衫7 小时前
Gudu SQL Omni 技术深度解析
数据库·sql
咖啡の猫8 小时前
Redis桌面客户端
数据库·redis·缓存
oradh8 小时前
Oracle 11g数据库软件和数据库静默安装
数据库·oracle
what丶k8 小时前
如何保证 Redis 与 MySQL 数据一致性?后端必备实践指南
数据库·redis·mysql