goLang-database/sql包及常用方法

183 阅读7分钟

关于数据库操作 database/sql 包 功能:提供了 SQL 类数据库的通用接口 前提:

  1. 必须和数据库驱动配合使用
  2. 不支持 context 取消的驱动直到查询完成后才会返回结果
  3. 支持上下文参数 context 指在执行数据库查询或其他操作时,可以使用上下文对象来设置超时时间、传递控制信号以及在需要时进行取消操作。

func(*Conn) ExecContext

定义:func (*Conn) ExecContext(ctx context.Context,query string,args ...any)(Result,error) 作用:执行不返回任何行结果的查询。 场景:通常用于执行 INSERT、UPDATE 或 DELETE 等修改数据库的操作

参数:

  1. ctx:上下文对象
  2. query: sql 查询语句
  3. args:可选,替换 SQL 查询中的占位符参数(如 ? 或 :name)

调用:

import (
	"context"
	"database/sql"
	"log"
)
var (
	ctx context.Context
	db  *sql.DB  // sql.DB 是封装好的一个数据库操作对象,包含了操作数据库的基本方法
)
func main() {
	conn, err := db.Conn(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	id := 41
	result, err := conn.ExecContext(ctx, `UPDATE balances SET balance = balance + 10 WHERE user_id = ?;`, id)
	if err != nil {
		log.Fatal(err)
	}
	rows, err := result.RowsAffected()
	if err != nil {
		log.Fatal(err)
	}
	if rows != 1 {
		log.Fatalf("expected single row affected, got %d rows affected", rows)
	}
}

func (*DB) BeginTx

定义:func (db *DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error) 作用:开始一个数据库事务,在事务内部缓存操作。通过将操作放在一个事务中可以确保他们要么全部执行成功要么全部回滚,保持数据一致性 场景:需要执行多个 SQL 操作并保持原子性的任务,比如批量插入数据、更新多个关联表 参数:

  1. ctx:上下文对象
  2. opts:用于指定事务的选项,例如隔离级别、只读等

返回值:返回一个 Tx 对象,这个对象提供了一系列方法来执行查询、提交或回滚事务等操作 注意点:在完成事务操作后及时释放相关资源,包括关闭事务和数据库连接

func main() {
  // 开始一个数据库事务,返回tx对象
	tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
	if err != nil {
		log.Fatal(err)
	}
	id := 37
  // 在事务中执行更新操作
    sqlStr1 := "insert into users(nickname, avatar) values('王五', null)" // 插入sql
    _, execErr = tx.Exec(sqlStr1)                                           // 执行插入语句
    sqlStr2 := "insert into users(nickname, avatar) values('李四', null)" // 插入sql
    _, execErr = tx.Exec(sqlStr2)

  // 操作失败则回滚
	if execErr != nil {
		_ = tx.Rollback()
		log.Fatal(execErr)
	}
  // 提交事务
	if err := tx.Commit(); err != nil {
		log.Fatal(err)
	}
}

func (*DB) ExecContext

定义:func (db *DB) ExecContext(ctx context.Context, query string, args ...any) (Result, error) 作用:通过数据库连接池(*sql.DB)来执行查询,可以处理多个并发的数据库请求。 场景:需要处理多个并发的数据库请求时,通常会使用 DB.ExecContext 方法,会自动管理连接池,从而更高效地处理并发请求 参数:同上 调用:

// 使用 DB.ExecContext 执行查询
result1, err := db.ExecContext(ctx, "INSERT INTO users(name) VALUES(?)", "John Doe")
if err != nil {
    log.Fatal(err)
}
// 使用 Conn.ExecContext 执行查询
conn, err := db.Conn(ctx)
if err != nil {
    log.Fatal(err)
}
defer conn.Close() //需要手动开启和关闭连接
result2, err := conn.ExecContext(ctx, "UPDATE users SET name = ? WHERE id = ?", "Jane Smith", 123)
if err != nil {
    log.Fatal(err)
}

func (*DB) PingContext

定义:func (db *DB) PingContext(ctx context.Context) error 作用:验证数据库连接是否可用,必要时可以建立新的连接 参数:ctx 上下文参数 调用:

// 使用 ping() 方法进行连接测试
err := db.Ping()
if err != nil {
    log.Fatal(err)
}

ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()

errCh := make(chan error, 1)
go func() {
  // 使用 pingContext() 方法进行连接测试
  errCh <- db.PingContext(ctx)
}()

select {
case err := <-errCh:
    if err != nil {
        log.Fatal(err)
    }
case <-ctx.Done():
    log.Fatal("Ping timed out")
}

func (*DB) Prepare

定义:func (db *DB) Prepare(query string) (*Stmt, error) 作用:为稍后的查询或执行创建一个预编译语句,即提供模板,可以被多次使用且支持参数化。也可以支持并发执行多个查询 注意:需要调用 Close 方法手动释放相关资源 使用场景:

  1. 需要频繁执行同一个查询,只有参数值不一样
  2. 需要执行带有参数的查询,预编译语句可以更安全和方便地处理参数。通过将查询中的占位符(如 ? 或 :name)替换为实际的参数值避免 SQL 注入攻击
  3. 需要在一个事务或者批处理操作中执行多个相关查询,如先插入一条数据,然后根据插入的结果执行其他的查询更新其他表中的数据

参数:query: sql 语句 调用:

func main() {
	projects := []struct {
		mascot  string
		release int
	}{
		{"tux", 1991},
		{"duke", 1996},
		{"gopher", 2009},
		{"dock", 2013},
	}
	stmt, err := db.Prepare("INSERT INTO projects(id, mascot, release, category) VALUES( ?, ?, ?, ? )")
	if err != nil {
		log.Fatal(err)
	}
	defer stmt.Close() //使用后手动关闭

	for id, project := range projects {
		if _, err := stmt.Exec(id+1, project.mascot, project.release, "open source"); err != nil {
			log.Fatal(err)
		}
	}
}

func (*DB) Query

定义:func (db *DB) Query(query string, args ...any) (*Rows, error) 作用:执行查询并返回行结果,比如 Select 操作。 参数:

  1. query: sql 查询语句
  2. args:可选,替换 SQL 查询中的占位符参数(如 ? 或 :name) 调用:
func main() {
	age := 27
  // 定义SQL查询语句
	q := `
create temp table uid (id bigint); -- Create temp table for queries.
insert into uid
select id from users where age < ?; -- Populate temp table.

-- First result set.
select
	users.id, name
from
	users
	join uid on users.id = uid.id
;

-- Second result set.
select
	ur.user, ur.role
from
	user_roles as ur
	join uid on uid.id = ur.user
;
	`
  // 使用 db.Query() 方法执行查询,返回*sql.Rows 对象,用于迭代结果集
	rows, err := db.Query(q, age)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
  // next()迭代第一个结果集
	for rows.Next() {//迭代结果集的每一行
		var (
			id   int64
			name string
		)
		if err := rows.Scan(&id, &name); err != nil {//scan()将当前行的列值扫描到对应的变量中
			log.Fatal(err)
		}
		log.Printf("id %d name is %s\n", id, name)
	}
  // 检查是否还有剩余的结果集
	if !rows.NextResultSet() {
		log.Fatalf("expected more result sets: %v", rows.Err())
	}
	var roleMap = map[int64]string{
		1: "user",
		2: "admin",
		3: "gopher",
	}
  // 迭代第二个结果集
	for rows.Next() {
		var (
			id   int64
			role int64
		)
		if err := rows.Scan(&id, &role); err != nil {//scan()将当前行的列值扫描到对应的变量中
			log.Fatal(err)
		}
		log.Printf("id %d has role %s\n", id, roleMap[role])
	}
  // 检查错误
	if err := rows.Err(); err != nil {
		log.Fatal(err)
	}
}

func (*DB) QueryContext

定义:func (db *DB) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error) 作用:同上 参数:同上,多一个 context 调用:

func main() {
	age := 27
  // 执行查询
	rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE age=?", age)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	names := make([]string, 0)

	for rows.Next() {
		var name string
    // 将结果即绑定到name变量上
		if err := rows.Scan(&name); err != nil {
			log.Fatal(err)
		}
		names = append(names, name)
	}
	rerr := rows.Close()
	if rerr != nil {
		log.Fatal(rerr)
	}

	// Rows.Err 会上报 Rows.Scan 遇到的最新错误
	if err := rows.Err(); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s are %d years old", strings.Join(names, ", "), age)
}

func (*DB) QueryRowContext

定义:func (db *DB) QueryRowContext(ctx context.Context, query string, args ...any) *Row 作用:执行查询,且只返回一行结果 使用场景:返回单个结果行的查询,例如根据某个条件获取用户信息、获取特定记录

调用:

func main() {
	id := 123
	var username string
	var created time.Time
  // 执行查询,在Scan执行后才会报错
	err := db.QueryRowContext(ctx, "SELECT username, created_at FROM users WHERE id=?", id).Scan(&username, &created)
	switch {
  // 没有返回行结果
	case err == sql.ErrNoRows:
		log.Printf("no user with id %d\n", id)
	case err != nil:
		log.Fatalf("query error: %v\n", err)
	default:
		log.Printf("username is %q, account created on %s\n", username, created)
	}
}

类型定义 Rows

角色:查询结果集 注意:

//有多个查询操作,返回多个结果集
rows, err := db.Query("SELECT name FROM users; SELECT role FROM roles;")
if err != nil {
    log.Fatal(err)
}
// 当调用 Next 方法并返回 false,并且没有更多的结果集时,Rows 对象会被自动关闭
defer rows.Close()
// 返回 false 只表示当前结果集中没有更多的行数据可供迭代
for rows.Next() {
    var name string
    if err := rows.Scan(&name); err != nil {
        log.Fatal(err)
    }
    fmt.Println("User:", name)
}
// 检查是否还有更多的结果集
if rows.NextResultSet() {
    // 处理下一个结果集
    for rows.Next() {
        var role string
        if err := rows.Scan(&role); err != nil {
            log.Fatal(err)
        }
        fmt.Println("Role:", role)
    }
}

类型 stmt

角色:预编译语句,多个 goroutine 并发时候使用安全

func main() {
	// 正常使用中, 当进程开始时创建一个Stmt
	stmt, err := db.PrepareContext(ctx, "SELECT username FROM users WHERE id = ?")
	if err != nil {
		log.Fatal(err)
	}
	defer stmt.Close()

	// 每次需要执行这个查询的时候可以服用
	id := 43
	var username string
	err = stmt.QueryRowContext(ctx, id).Scan(&username)
	switch {
	case err == sql.ErrNoRows:
		log.Fatalf("no user with id %d", id)
	case err != nil:
		log.Fatal(err)
	default:
		log.Printf("username is %s\n", username)
	}
}

func (*DB) Exec

定义:func (db *DB) Exec(query string, args ...any) (Result, error) 场景:同 ExecContext

func (*Tx) Rollback

定义:func (tx *Tx) Rollback() error 作用:放弃事务执行并回滚

func main() {
	tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
	if err != nil {
		log.Fatal(err)
	}
	id := 53
	_, err = tx.ExecContext(ctx, "UPDATE drivers SET status = ? WHERE id = ?;", "assigned", id)
	if err != nil {
		if rollbackErr := tx.Rollback(); rollbackErr != nil {
			log.Fatalf("update drivers: unable to rollback: %v", rollbackErr)
		}
		log.Fatal(err)
	}
	_, err = tx.ExecContext(ctx, "UPDATE pickups SET driver_id = $1;", id)
	if err != nil {
		if rollbackErr := tx.Rollback(); rollbackErr != nil {
			log.Fatalf("update failed: %v, unable to back: %v", err, rollbackErr)
		}
		log.Fatal(err)
	}
	if err := tx.Commit(); err != nil {
		log.Fatal(err)
	}
}