在 Go 语言的数据库操作中,查询和扫描是最常用的操作。本章将深入介绍如何使用 database/sql 包进行各种类型的查询操作,以及如何正确地扫描查询结果到 Go 的数据结构中。
1. 查询类型概述
1.1 查询方法分类
Go 的 database/sql 包提供了三种主要的查询方法:
- Query: 执行返回多行结果的查询
- QueryRow: 执行返回单行结果的查询
- Exec: 执行不返回结果的语句(INSERT、UPDATE、DELETE)
1.2 基本查询示例
go
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
)
func basicQueryExamples(db *sql.DB) {
// 1. Query - 多行查询
rows, err := db.Query("SELECT id, name, email FROM users WHERE age > $1", 18)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var id int
var name, email string
if err := rows.Scan(&id, &name, &email); err != nil {
log.Fatal(err)
}
fmt.Printf("ID: %d, Name: %s, Email: %s\n", id, name, email)
}
// 2. QueryRow - 单行查询
var userCount int
err = db.QueryRow("SELECT COUNT(*) FROM users").Scan(&userCount)
if err != nil {
log.Fatal(err)
}
fmt.Printf("用户总数: %d\n", userCount)
// 3. Exec - 执行语句
result, err := db.Exec("UPDATE users SET last_login = NOW() WHERE id = $1", 1)
if err != nil {
log.Fatal(err)
}
rowsAffected, _ := result.RowsAffected()
fmt.Printf("更新了 %d 行\n", rowsAffected)
}
2. 查询操作详解
2.1 多行查询(Query)
go
package database
import (
"database/sql"
"fmt"
"time"
)
// User 用户结构体
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Age int `json:"age"`
CreatedAt time.Time `json:"created_at"`
IsActive bool `json:"is_active"`
}
// QueryUsers 查询用户列表
func QueryUsers(db *sql.DB, minAge int, limit int) ([]*User, error) {
query := `
SELECT id, username, email, age, created_at, is_active
FROM users
WHERE age >= $1
ORDER BY created_at DESC
LIMIT $2`
rows, err := db.Query(query, minAge, limit)
if err != nil {
return nil, fmt.Errorf("查询失败: %w", err)
}
defer rows.Close()
var users []*User
for rows.Next() {
user := &User{}
err := rows.Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Age,
&user.CreatedAt,
&user.IsActive,
)
if err != nil {
return nil, fmt.Errorf("扫描行失败: %w", err)
}
users = append(users, user)
}
// 检查迭代过程中的错误
if err = rows.Err(); err != nil {
return nil, fmt.Errorf("行迭代错误: %w", err)
}
return users, nil
}
// QueryUsersWithPagination 分页查询用户
func QueryUsersWithPagination(db *sql.DB, page, pageSize int) ([]*User, int, error) {
// 计算偏移量
offset := (page - 1) * pageSize
// 查询总数
var total int
countQuery := "SELECT COUNT(*) FROM users"
err := db.QueryRow(countQuery).Scan(&total)
if err != nil {
return nil, 0, fmt.Errorf("查询总数失败: %w", err)
}
// 查询分页数据
query := `
SELECT id, username, email, age, created_at, is_active
FROM users
ORDER BY id
LIMIT $1 OFFSET $2`
rows, err := db.Query(query, pageSize, offset)
if err != nil {
return nil, 0, fmt.Errorf("查询失败: %w", err)
}
defer rows.Close()
var users []*User
for rows.Next() {
user := &User{}
err := rows.Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Age,
&user.CreatedAt,
&user.IsActive,
)
if err != nil {
return nil, 0, fmt.Errorf("扫描行失败: %w", err)
}
users = append(users, user)
}
if err = rows.Err(); err != nil {
return nil, 0, fmt.Errorf("行迭代错误: %w", err)
}
return users, total, nil
}
2.2 单行查询(QueryRow)
go
package database
import (
"database/sql"
"errors"
"fmt"
)
// GetUserByID 根据ID获取用户
func GetUserByID(db *sql.DB, id int) (*User, error) {
query := `
SELECT id, username, email, age, created_at, is_active
FROM users
WHERE id = $1`
user := &User{}
err := db.QueryRow(query, id).Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Age,
&user.CreatedAt,
&user.IsActive,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("用户不存在: ID=%d", id)
}
return nil, fmt.Errorf("查询用户失败: %w", err)
}
return user, nil
}
// GetUserByEmail 根据邮箱获取用户
func GetUserByEmail(db *sql.DB, email string) (*User, error) {
query := `
SELECT id, username, email, age, created_at, is_active
FROM users
WHERE email = $1`
user := &User{}
err := db.QueryRow(query, email).Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Age,
&user.CreatedAt,
&user.IsActive,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("用户不存在: Email=%s", email)
}
return nil, fmt.Errorf("查询用户失败: %w", err)
}
return user, nil
}
// GetUserStats 获取用户统计信息
func GetUserStats(db *sql.DB) (map[string]interface{}, error) {
query := `
SELECT
COUNT(*) as total_users,
COUNT(CASE WHEN is_active = true THEN 1 END) as active_users,
COUNT(CASE WHEN is_active = false THEN 1 END) as inactive_users,
AVG(age) as average_age,
MIN(created_at) as first_user_date,
MAX(created_at) as last_user_date
FROM users`
var totalUsers, activeUsers, inactiveUsers int
var averageAge float64
var firstUserDate, lastUserDate time.Time
err := db.QueryRow(query).Scan(
&totalUsers,
&activeUsers,
&inactiveUsers,
&averageAge,
&firstUserDate,
&lastUserDate,
)
if err != nil {
return nil, fmt.Errorf("查询统计信息失败: %w", err)
}
stats := map[string]interface{}{
"total_users": totalUsers,
"active_users": activeUsers,
"inactive_users": inactiveUsers,
"average_age": averageAge,
"first_user_date": firstUserDate,
"last_user_date": lastUserDate,
}
return stats, nil
}
3. 扫描技术
3.1 基本扫描
go
package database
import (
"database/sql"
"database/sql/driver"
"fmt"
"time"
)
// 基本类型扫描示例
func BasicScanExamples(db *sql.DB) {
// 扫描到基本类型
var id int
var name string
var age sql.NullInt64
var email sql.NullString
var createdAt time.Time
query := "SELECT id, name, age, email, created_at FROM users WHERE id = $1"
err := db.QueryRow(query, 1).Scan(&id, &name, &age, &email, &createdAt)
if err != nil {
fmt.Printf("扫描失败: %v\n", err)
return
}
fmt.Printf("ID: %d\n", id)
fmt.Printf("Name: %s\n", name)
// 处理可空字段
if age.Valid {
fmt.Printf("Age: %d\n", age.Int64)
} else {
fmt.Println("Age: NULL")
}
if email.Valid {
fmt.Printf("Email: %s\n", email.String)
} else {
fmt.Println("Email: NULL")
}
fmt.Printf("Created At: %v\n", createdAt)
}
3.2 可空类型处理
go
package database
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"fmt"
"time"
)
// NullableUser 支持可空字段的用户结构体
type NullableUser struct {
ID int `json:"id"`
Username string `json:"username"`
Email sql.NullString `json:"email"`
Age sql.NullInt64 `json:"age"`
Phone sql.NullString `json:"phone"`
Avatar sql.NullString `json:"avatar"`
IsActive sql.NullBool `json:"is_active"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"`
}
// ToMap 转换为 map 格式,处理 NULL 值
func (u *NullableUser) ToMap() map[string]interface{} {
result := map[string]interface{}{
"id": u.ID,
"username": u.Username,
"created_at": u.CreatedAt,
}
if u.Email.Valid {
result["email"] = u.Email.String
} else {
result["email"] = nil
}
if u.Age.Valid {
result["age"] = u.Age.Int64
} else {
result["age"] = nil
}
if u.Phone.Valid {
result["phone"] = u.Phone.String
} else {
result["phone"] = nil
}
if u.Avatar.Valid {
result["avatar"] = u.Avatar.String
} else {
result["avatar"] = nil
}
if u.IsActive.Valid {
result["is_active"] = u.IsActive.Bool
} else {
result["is_active"] = nil
}
if u.UpdatedAt.Valid {
result["updated_at"] = u.UpdatedAt.Time
} else {
result["updated_at"] = nil
}
return result
}
// QueryNullableUser 查询支持可空字段的用户
func QueryNullableUser(db *sql.DB, id int) (*NullableUser, error) {
query := `
SELECT id, username, email, age, phone, avatar, is_active, created_at, updated_at
FROM users
WHERE id = $1`
user := &NullableUser{}
err := db.QueryRow(query, id).Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Age,
&user.Phone,
&user.Avatar,
&user.IsActive,
&user.CreatedAt,
&user.UpdatedAt,
)
if err != nil {
return nil, fmt.Errorf("查询用户失败: %w", err)
}
return user, nil
}
3.3 自定义扫描类型
go
package database
import (
"database/sql/driver"
"encoding/json"
"fmt"
"strings"
)
// JSONMap 自定义 JSON 类型
type JSONMap map[string]interface{}
// Scan 实现 sql.Scanner 接口
func (j *JSONMap) Scan(value interface{}) error {
if value == nil {
*j = make(JSONMap)
return nil
}
var bytes []byte
switch v := value.(type) {
case []byte:
bytes = v
case string:
bytes = []byte(v)
default:
return fmt.Errorf("无法扫描 %T 到 JSONMap", value)
}
return json.Unmarshal(bytes, j)
}
// Value 实现 driver.Valuer 接口
func (j JSONMap) Value() (driver.Value, error) {
if j == nil {
return nil, nil
}
return json.Marshal(j)
}
// StringSlice 自定义字符串切片类型
type StringSlice []string
// Scan 实现 sql.Scanner 接口
func (s *StringSlice) Scan(value interface{}) error {
if value == nil {
*s = nil
return nil
}
switch v := value.(type) {
case []byte:
*s = strings.Split(string(v), ",")
case string:
*s = strings.Split(v, ",")
default:
return fmt.Errorf("无法扫描 %T 到 StringSlice", value)
}
return nil
}
// Value 实现 driver.Valuer 接口
func (s StringSlice) Value() (driver.Value, error) {
if s == nil {
return nil, nil
}
return strings.Join(s, ","), nil
}
// UserProfile 用户配置文件
type UserProfile struct {
ID int `json:"id"`
Username string `json:"username"`
Settings JSONMap `json:"settings"`
Tags StringSlice `json:"tags"`
}
// QueryUserProfile 查询用户配置文件
func QueryUserProfile(db *sql.DB, id int) (*UserProfile, error) {
query := `
SELECT id, username, settings, tags
FROM user_profiles
WHERE id = $1`
profile := &UserProfile{}
err := db.QueryRow(query, id).Scan(
&profile.ID,
&profile.Username,
&profile.Settings,
&profile.Tags,
)
if err != nil {
return nil, fmt.Errorf("查询用户配置失败: %w", err)
}
return profile, nil
}
4. 动态查询构建
4.1 查询构建器
go
package database
import (
"fmt"
"strings"
)
// QueryBuilder 查询构建器
type QueryBuilder struct {
table string
columns []string
conditions []string
args []interface{}
orderBy []string
limit int
offset int
}
// NewQueryBuilder 创建查询构建器
func NewQueryBuilder(table string) *QueryBuilder {
return &QueryBuilder{
table: table,
columns: []string{"*"},
}
}
// Select 设置查询列
func (qb *QueryBuilder) Select(columns ...string) *QueryBuilder {
qb.columns = columns
return qb
}
// Where 添加 WHERE 条件
func (qb *QueryBuilder) Where(condition string, args ...interface{}) *QueryBuilder {
qb.conditions = append(qb.conditions, condition)
qb.args = append(qb.args, args...)
return qb
}
// WhereIn 添加 IN 条件
func (qb *QueryBuilder) WhereIn(column string, values []interface{}) *QueryBuilder {
if len(values) == 0 {
return qb
}
placeholders := make([]string, len(values))
for i := range values {
placeholders[i] = fmt.Sprintf("$%d", len(qb.args)+i+1)
}
condition := fmt.Sprintf("%s IN (%s)", column, strings.Join(placeholders, ","))
qb.conditions = append(qb.conditions, condition)
qb.args = append(qb.args, values...)
return qb
}
// WhereLike 添加 LIKE 条件
func (qb *QueryBuilder) WhereLike(column, pattern string) *QueryBuilder {
condition := fmt.Sprintf("%s LIKE $%d", column, len(qb.args)+1)
qb.conditions = append(qb.conditions, condition)
qb.args = append(qb.args, "%"+pattern+"%")
return qb
}
// OrderBy 添加排序
func (qb *QueryBuilder) OrderBy(column, direction string) *QueryBuilder {
qb.orderBy = append(qb.orderBy, fmt.Sprintf("%s %s", column, direction))
return qb
}
// Limit 设置限制
func (qb *QueryBuilder) Limit(limit int) *QueryBuilder {
qb.limit = limit
return qb
}
// Offset 设置偏移
func (qb *QueryBuilder) Offset(offset int) *QueryBuilder {
qb.offset = offset
return qb
}
// Build 构建查询语句
func (qb *QueryBuilder) Build() (string, []interface{}) {
query := fmt.Sprintf("SELECT %s FROM %s", strings.Join(qb.columns, ", "), qb.table)
if len(qb.conditions) > 0 {
query += " WHERE " + strings.Join(qb.conditions, " AND ")
}
if len(qb.orderBy) > 0 {
query += " ORDER BY " + strings.Join(qb.orderBy, ", ")
}
if qb.limit > 0 {
query += fmt.Sprintf(" LIMIT %d", qb.limit)
}
if qb.offset > 0 {
query += fmt.Sprintf(" OFFSET %d", qb.offset)
}
return query, qb.args
}
// 使用示例
func QueryUsersWithBuilder(db *sql.DB, filters map[string]interface{}) ([]*User, error) {
qb := NewQueryBuilder("users").
Select("id", "username", "email", "age", "created_at", "is_active")
// 动态添加条件
if minAge, ok := filters["min_age"].(int); ok {
qb.Where("age >= $"+fmt.Sprintf("%d", len(qb.args)+1), minAge)
}
if username, ok := filters["username"].(string); ok {
qb.WhereLike("username", username)
}
if isActive, ok := filters["is_active"].(bool); ok {
qb.Where("is_active = $"+fmt.Sprintf("%d", len(qb.args)+1), isActive)
}
if ids, ok := filters["ids"].([]interface{}); ok {
qb.WhereIn("id", ids)
}
// 添加排序和分页
qb.OrderBy("created_at", "DESC")
if limit, ok := filters["limit"].(int); ok {
qb.Limit(limit)
}
if offset, ok := filters["offset"].(int); ok {
qb.Offset(offset)
}
query, args := qb.Build()
rows, err := db.Query(query, args...)
if err != nil {
return nil, fmt.Errorf("查询失败: %w", err)
}
defer rows.Close()
var users []*User
for rows.Next() {
user := &User{}
err := rows.Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Age,
&user.CreatedAt,
&user.IsActive,
)
if err != nil {
return nil, fmt.Errorf("扫描行失败: %w", err)
}
users = append(users, user)
}
return users, rows.Err()
}
4.2 条件构建器
go
package database
import (
"fmt"
"strings"
)
// Condition 查询条件
type Condition struct {
SQL string
Args []interface{}
}
// ConditionBuilder 条件构建器
type ConditionBuilder struct {
conditions []Condition
operator string
}
// NewConditionBuilder 创建条件构建器
func NewConditionBuilder(operator string) *ConditionBuilder {
return &ConditionBuilder{
operator: operator,
}
}
// And 创建 AND 条件构建器
func And() *ConditionBuilder {
return NewConditionBuilder("AND")
}
// Or 创建 OR 条件构建器
func Or() *ConditionBuilder {
return NewConditionBuilder("OR")
}
// Add 添加条件
func (cb *ConditionBuilder) Add(sql string, args ...interface{}) *ConditionBuilder {
cb.conditions = append(cb.conditions, Condition{
SQL: sql,
Args: args,
})
return cb
}
// AddIf 条件性添加条件
func (cb *ConditionBuilder) AddIf(condition bool, sql string, args ...interface{}) *ConditionBuilder {
if condition {
cb.Add(sql, args...)
}
return cb
}
// Equal 添加等于条件
func (cb *ConditionBuilder) Equal(column string, value interface{}) *ConditionBuilder {
return cb.Add(fmt.Sprintf("%s = ?", column), value)
}
// NotEqual 添加不等于条件
func (cb *ConditionBuilder) NotEqual(column string, value interface{}) *ConditionBuilder {
return cb.Add(fmt.Sprintf("%s != ?", column), value)
}
// GreaterThan 添加大于条件
func (cb *ConditionBuilder) GreaterThan(column string, value interface{}) *ConditionBuilder {
return cb.Add(fmt.Sprintf("%s > ?", column), value)
}
// LessThan 添加小于条件
func (cb *ConditionBuilder) LessThan(column string, value interface{}) *ConditionBuilder {
return cb.Add(fmt.Sprintf("%s < ?", column), value)
}
// Like 添加 LIKE 条件
func (cb *ConditionBuilder) Like(column, pattern string) *ConditionBuilder {
return cb.Add(fmt.Sprintf("%s LIKE ?", column), "%"+pattern+"%")
}
// In 添加 IN 条件
func (cb *ConditionBuilder) In(column string, values []interface{}) *ConditionBuilder {
if len(values) == 0 {
return cb
}
placeholders := strings.Repeat("?,", len(values))
placeholders = placeholders[:len(placeholders)-1] // 移除最后的逗号
return cb.Add(fmt.Sprintf("%s IN (%s)", column, placeholders), values...)
}
// IsNull 添加 IS NULL 条件
func (cb *ConditionBuilder) IsNull(column string) *ConditionBuilder {
return cb.Add(fmt.Sprintf("%s IS NULL", column))
}
// IsNotNull 添加 IS NOT NULL 条件
func (cb *ConditionBuilder) IsNotNull(column string) *ConditionBuilder {
return cb.Add(fmt.Sprintf("%s IS NOT NULL", column))
}
// Build 构建条件
func (cb *ConditionBuilder) Build() (string, []interface{}) {
if len(cb.conditions) == 0 {
return "", nil
}
var sqlParts []string
var args []interface{}
for _, condition := range cb.conditions {
sqlParts = append(sqlParts, condition.SQL)
args = append(args, condition.Args...)
}
sql := strings.Join(sqlParts, " "+cb.operator+" ")
return "(" + sql + ")", args
}
// 使用示例
func QueryUsersWithConditions(db *sql.DB, searchParams map[string]interface{}) ([]*User, error) {
baseQuery := "SELECT id, username, email, age, created_at, is_active FROM users"
// 构建复杂条件
mainCondition := And()
// 年龄范围
if minAge, ok := searchParams["min_age"].(int); ok {
mainCondition.GreaterThan("age", minAge)
}
if maxAge, ok := searchParams["max_age"].(int); ok {
mainCondition.LessThan("age", maxAge)
}
// 用户名或邮箱搜索
if search, ok := searchParams["search"].(string); ok {
searchCondition := Or().
Like("username", search).
Like("email", search)
searchSQL, searchArgs := searchCondition.Build()
if searchSQL != "" {
mainCondition.Add(searchSQL, searchArgs...)
}
}
// 状态过滤
if isActive, ok := searchParams["is_active"].(bool); ok {
mainCondition.Equal("is_active", isActive)
}
// ID 列表
if ids, ok := searchParams["ids"].([]interface{}); ok {
mainCondition.In("id", ids)
}
// 构建最终查询
whereSQL, args := mainCondition.Build()
if whereSQL != "" {
baseQuery += " WHERE " + whereSQL
}
baseQuery += " ORDER BY created_at DESC"
// 分页
if limit, ok := searchParams["limit"].(int); ok {
baseQuery += fmt.Sprintf(" LIMIT %d", limit)
}
if offset, ok := searchParams["offset"].(int); ok {
baseQuery += fmt.Sprintf(" OFFSET %d", offset)
}
rows, err := db.Query(baseQuery, args...)
if err != nil {
return nil, fmt.Errorf("查询失败: %w", err)
}
defer rows.Close()
var users []*User
for rows.Next() {
user := &User{}
err := rows.Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Age,
&user.CreatedAt,
&user.IsActive,
)
if err != nil {
return nil, fmt.Errorf("扫描行失败: %w", err)
}
users = append(users, user)
}
return users, rows.Err()
}
5. 预编译语句
5.1 基本预编译语句
go
package database
import (
"database/sql"
"fmt"
)
// PreparedStatements 预编译语句管理器
type PreparedStatements struct {
db *sql.DB
stmts map[string]*sql.Stmt
}
// NewPreparedStatements 创建预编译语句管理器
func NewPreparedStatements(db *sql.DB) *PreparedStatements {
return &PreparedStatements{
db: db,
stmts: make(map[string]*sql.Stmt),
}
}
// Prepare 预编译语句
func (ps *PreparedStatements) Prepare(name, query string) error {
stmt, err := ps.db.Prepare(query)
if err != nil {
return fmt.Errorf("预编译语句失败 [%s]: %w", name, err)
}
// 如果已存在,先关闭旧的
if oldStmt, exists := ps.stmts[name]; exists {
oldStmt.Close()
}
ps.stmts[name] = stmt
return nil
}
// Get 获取预编译语句
func (ps *PreparedStatements) Get(name string) (*sql.Stmt, error) {
stmt, exists := ps.stmts[name]
if !exists {
return nil, fmt.Errorf("预编译语句不存在: %s", name)
}
return stmt, nil
}
// Close 关闭所有预编译语句
func (ps *PreparedStatements) Close() error {
var lastErr error
for name, stmt := range ps.stmts {
if err := stmt.Close(); err != nil {
lastErr = fmt.Errorf("关闭预编译语句失败 [%s]: %w", name, err)
}
}
ps.stmts = make(map[string]*sql.Stmt)
return lastErr
}
// UserRepository 使用预编译语句的用户仓库
type UserRepository struct {
db *sql.DB
stmts *PreparedStatements
}
// NewUserRepository 创建用户仓库
func NewUserRepository(db *sql.DB) (*UserRepository, error) {
stmts := NewPreparedStatements(db)
// 预编译常用语句
queries := map[string]string{
"insert_user": `
INSERT INTO users (username, email, age, is_active)
VALUES ($1, $2, $3, $4)
RETURNING id, created_at`,
"get_user_by_id": `
SELECT id, username, email, age, created_at, is_active
FROM users WHERE id = $1`,
"get_user_by_email": `
SELECT id, username, email, age, created_at, is_active
FROM users WHERE email = $1`,
"update_user": `
UPDATE users
SET username = $2, email = $3, age = $4
WHERE id = $1`,
"delete_user": `
DELETE FROM users WHERE id = $1`,
"list_users": `
SELECT id, username, email, age, created_at, is_active
FROM users
ORDER BY created_at DESC
LIMIT $1 OFFSET $2`,
}
for name, query := range queries {
if err := stmts.Prepare(name, query); err != nil {
stmts.Close()
return nil, err
}
}
return &UserRepository{
db: db,
stmts: stmts,
}, nil
}
// CreateUser 创建用户
func (ur *UserRepository) CreateUser(username, email string, age int, isActive bool) (*User, error) {
stmt, err := ur.stmts.Get("insert_user")
if err != nil {
return nil, err
}
user := &User{
Username: username,
Email: email,
Age: age,
IsActive: isActive,
}
err = stmt.QueryRow(username, email, age, isActive).Scan(&user.ID, &user.CreatedAt)
if err != nil {
return nil, fmt.Errorf("创建用户失败: %w", err)
}
return user, nil
}
// GetUserByID 根据ID获取用户
func (ur *UserRepository) GetUserByID(id int) (*User, error) {
stmt, err := ur.stmts.Get("get_user_by_id")
if err != nil {
return nil, err
}
user := &User{}
err = stmt.QueryRow(id).Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Age,
&user.CreatedAt,
&user.IsActive,
)
if err != nil {
return nil, fmt.Errorf("获取用户失败: %w", err)
}
return user, nil
}
// UpdateUser 更新用户
func (ur *UserRepository) UpdateUser(id int, username, email string, age int) error {
stmt, err := ur.stmts.Get("update_user")
if err != nil {
return err
}
result, err := stmt.Exec(id, username, email, age)
if err != nil {
return fmt.Errorf("更新用户失败: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("获取影响行数失败: %w", err)
}
if rowsAffected == 0 {
return fmt.Errorf("用户不存在: ID=%d", id)
}
return nil
}
// Close 关闭仓库
func (ur *UserRepository) Close() error {
return ur.stmts.Close()
}
5.2 批量操作
go
package database
import (
"database/sql"
"fmt"
)
// BatchInsertUsers 批量插入用户
func BatchInsertUsers(db *sql.DB, users []User) error {
// 预编译插入语句
stmt, err := db.Prepare(`
INSERT INTO users (username, email, age, is_active)
VALUES ($1, $2, $3, $4)`)
if err != nil {
return fmt.Errorf("预编译语句失败: %w", err)
}
defer stmt.Close()
// 开始事务
tx, err := db.Begin()
if err != nil {
return fmt.Errorf("开始事务失败: %w", err)
}
defer tx.Rollback()
// 在事务中使用预编译语句
txStmt := tx.Stmt(stmt)
for i, user := range users {
_, err := txStmt.Exec(user.Username, user.Email, user.Age, user.IsActive)
if err != nil {
return fmt.Errorf("插入第 %d 个用户失败: %w", i+1, err)
}
}
// 提交事务
if err := tx.Commit(); err != nil {
return fmt.Errorf("提交事务失败: %w", err)
}
return nil
}
// BatchUpdateUsers 批量更新用户
func BatchUpdateUsers(db *sql.DB, updates []struct {
ID int
Username string
Email string
Age int
}) error {
stmt, err := db.Prepare(`
UPDATE users
SET username = $2, email = $3, age = $4
WHERE id = $1`)
if err != nil {
return fmt.Errorf("预编译语句失败: %w", err)
}
defer stmt.Close()
tx, err := db.Begin()
if err != nil {
return fmt.Errorf("开始事务失败: %w", err)
}
defer tx.Rollback()
txStmt := tx.Stmt(stmt)
for i, update := range updates {
result, err := txStmt.Exec(update.ID, update.Username, update.Email, update.Age)
if err != nil {
return fmt.Errorf("更新第 %d 个用户失败: %w", i+1, err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("获取第 %d 个用户影响行数失败: %w", i+1, err)
}
if rowsAffected == 0 {
return fmt.Errorf("第 %d 个用户不存在: ID=%d", i+1, update.ID)
}
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("提交事务失败: %w", err)
}
return nil
}
6. 结果集处理
6.1 流式处理
go
package database
import (
"database/sql"
"fmt"
)
// StreamProcessor 流式处理器
type StreamProcessor struct {
db *sql.DB
}
// NewStreamProcessor 创建流式处理器
func NewStreamProcessor(db *sql.DB) *StreamProcessor {
return &StreamProcessor{db: db}
}
// ProcessUsersStream 流式处理用户数据
func (sp *StreamProcessor) ProcessUsersStream(
query string,
args []interface{},
processor func(*User) error,
) error {
rows, err := sp.db.Query(query, args...)
if err != nil {
return fmt.Errorf("查询失败: %w", err)
}
defer rows.Close()
for rows.Next() {
user := &User{}
err := rows.Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Age,
&user.CreatedAt,
&user.IsActive,
)
if err != nil {
return fmt.Errorf("扫描行失败: %w", err)
}
// 处理单个用户
if err := processor(user); err != nil {
return fmt.Errorf("处理用户失败 [ID=%d]: %w", user.ID, err)
}
}
return rows.Err()
}
// ExportUsersToCSV 导出用户到 CSV(流式处理示例)
func (sp *StreamProcessor) ExportUsersToCSV(filename string) error {
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("创建文件失败: %w", err)
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
// 写入 CSV 头部
if err := writer.Write([]string{"ID", "Username", "Email", "Age", "Created At", "Is Active"}); err != nil {
return fmt.Errorf("写入 CSV 头部失败: %w", err)
}
// 流式处理用户数据
query := "SELECT id, username, email, age, created_at, is_active FROM users ORDER BY id"
return sp.ProcessUsersStream(query, nil, func(user *User) error {
record := []string{
fmt.Sprintf("%d", user.ID),
user.Username,
user.Email,
fmt.Sprintf("%d", user.Age),
user.CreatedAt.Format("2006-01-02 15:04:05"),
fmt.Sprintf("%t", user.IsActive),
}
return writer.Write(record)
})
}
6.2 分页处理
go
package database
import (
"database/sql"
"fmt"
)
// PageProcessor 分页处理器
type PageProcessor struct {
db *sql.DB
pageSize int
}
// NewPageProcessor 创建分页处理器
func NewPageProcessor(db *sql.DB, pageSize int) *PageProcessor {
return &PageProcessor{
db: db,
pageSize: pageSize,
}
}
// ProcessAllUsers 分页处理所有用户
func (pp *PageProcessor) ProcessAllUsers(processor func([]*User) error) error {
offset := 0
for {
users, err := pp.getUsersPage(offset)
if err != nil {
return fmt.Errorf("获取用户页面失败 [offset=%d]: %w", offset, err)
}
// 如果没有更多数据,退出循环
if len(users) == 0 {
break
}
// 处理当前页面的用户
if err := processor(users); err != nil {
return fmt.Errorf("处理用户页面失败 [offset=%d]: %w", offset, err)
}
// 如果返回的数据少于页面大小,说明已经是最后一页
if len(users) < pp.pageSize {
break
}
offset += pp.pageSize
}
return nil
}
// getUsersPage 获取用户页面
func (pp *PageProcessor) getUsersPage(offset int) ([]*User, error) {
query := `
SELECT id, username, email, age, created_at, is_active
FROM users
ORDER BY id
LIMIT $1 OFFSET $2`
rows, err := pp.db.Query(query, pp.pageSize, offset)
if err != nil {
return nil, err
}
defer rows.Close()
var users []*User
for rows.Next() {
user := &User{}
err := rows.Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Age,
&user.CreatedAt,
&user.IsActive,
)
if err != nil {
return nil, err
}
users = append(users, user)
}
return users, rows.Err()
}
// 使用示例:统计用户数据
func (pp *PageProcessor) CalculateUserStatistics() (map[string]interface{}, error) {
stats := map[string]interface{}{
"total_users": 0,
"active_users": 0,
"total_age": 0,
"age_groups": map[string]int{
"18-25": 0,
"26-35": 0,
"36-45": 0,
"46+": 0,
},
}
err := pp.ProcessAllUsers(func(users []*User) error {
for _, user := range users {
// 总用户数
stats["total_users"] = stats["total_users"].(int) + 1
// 活跃用户数
if user.IsActive {
stats["active_users"] = stats["active_users"].(int) + 1
}
// 年龄统计
stats["total_age"] = stats["total_age"].(int) + user.Age
// 年龄组统计
ageGroups := stats["age_groups"].(map[string]int)
switch {
case user.Age >= 18 && user.Age <= 25:
ageGroups["18-25"]++
case user.Age >= 26 && user.Age <= 35:
ageGroups["26-35"]++
case user.Age >= 36 && user.Age <= 45:
ageGroups["36-45"]++
case user.Age >= 46:
ageGroups["46+"]++
}
}
return nil
})
if err != nil {
return nil, err
}
// 计算平均年龄
if totalUsers := stats["total_users"].(int); totalUsers > 0 {
stats["average_age"] = float64(stats["total_age"].(int)) / float64(totalUsers)
}
return stats, nil
}
7. 性能优化
7.1 查询优化
go
package database
import (
"database/sql"
"fmt"
"time"
)
// QueryOptimizer 查询优化器
type QueryOptimizer struct {
db *sql.DB
}
// NewQueryOptimizer 创建查询优化器
func NewQueryOptimizer(db *sql.DB) *QueryOptimizer {
return &QueryOptimizer{db: db}
}
// ExplainQuery 分析查询执行计划
func (qo *QueryOptimizer) ExplainQuery(query string, args ...interface{}) (string, error) {
explainQuery := "EXPLAIN (ANALYZE, BUFFERS) " + query
row := qo.db.QueryRow(explainQuery, args...)
var plan string
err := row.Scan(&plan)
if err != nil {
return "", fmt.Errorf("分析查询失败: %w", err)
}
return plan, nil
}
// BenchmarkQuery 基准测试查询
func (qo *QueryOptimizer) BenchmarkQuery(query string, args []interface{}, iterations int) (time.Duration, error) {
start := time.Now()
for i := 0; i < iterations; i++ {
rows, err := qo.db.Query(query, args...)
if err != nil {
return 0, fmt.Errorf("查询失败: %w", err)
}
// 消费所有结果
for rows.Next() {
// 不做任何处理,只是消费结果
}
rows.Close()
if err := rows.Err(); err != nil {
return 0, fmt.Errorf("行迭代错误: %w", err)
}
}
duration := time.Since(start)
return duration / time.Duration(iterations), nil
}
// OptimizedUserQueries 优化的用户查询
type OptimizedUserQueries struct {
db *sql.DB
}
// NewOptimizedUserQueries 创建优化的用户查询
func NewOptimizedUserQueries(db *sql.DB) *OptimizedUserQueries {
return &OptimizedUserQueries{db: db}
}
// GetActiveUsersWithIndex 使用索引的活跃用户查询
func (ouq *OptimizedUserQueries) GetActiveUsersWithIndex(limit int) ([]*User, error) {
// 确保 is_active 字段有索引
// CREATE INDEX idx_users_active ON users(is_active) WHERE is_active = true;
query := `
SELECT id, username, email, age, created_at, is_active
FROM users
WHERE is_active = true
ORDER BY created_at DESC
LIMIT $1`
rows, err := ouq.db.Query(query, limit)
if err != nil {
return nil, fmt.Errorf("查询活跃用户失败: %w", err)
}
defer rows.Close()
var users []*User
for rows.Next() {
user := &User{}
err := rows.Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Age,
&user.CreatedAt,
&user.IsActive,
)
if err != nil {
return nil, fmt.Errorf("扫描行失败: %w", err)
}
users = append(users, user)
}
return users, rows.Err()
}
// GetUsersByAgeRange 按年龄范围查询用户(使用复合索引)
func (ouq *OptimizedUserQueries) GetUsersByAgeRange(minAge, maxAge int) ([]*User, error) {
// 确保有复合索引
// CREATE INDEX idx_users_age_active ON users(age, is_active);
query := `
SELECT id, username, email, age, created_at, is_active
FROM users
WHERE age BETWEEN $1 AND $2
AND is_active = true
ORDER BY age`
rows, err := ouq.db.Query(query, minAge, maxAge)
if err != nil {
return nil, fmt.Errorf("按年龄范围查询用户失败: %w", err)
}
defer rows.Close()
var users []*User
for rows.Next() {
user := &User{}
err := rows.Scan(
&user.ID,
&user.Username,
&user.Email,
&user.Age,
&user.CreatedAt,
&user.IsActive,
)
if err != nil {
return nil, fmt.Errorf("扫描行失败: %w", err)
}
users = append(users, user)
}
return users, rows.Err()
}
// GetUserCountByStatus 按状态统计用户数(避免全表扫描)
func (ouq *OptimizedUserQueries) GetUserCountByStatus() (map[string]int, error) {
query := `
SELECT
is_active,
COUNT(*) as count
FROM users
GROUP BY is_active`
rows, err := ouq.db.Query(query)
if err != nil {
return nil, fmt.Errorf("统计用户状态失败: %w", err)
}
defer rows.Close()
result := make(map[string]int)
for rows.Next() {
var isActive bool
var count int
err := rows.Scan(&isActive, &count)
if err != nil {
return nil, fmt.Errorf("扫描统计结果失败: %w", err)
}
if isActive {
result["active"] = count
} else {
result["inactive"] = count
}
}
return result, rows.Err()
}
8. 完整示例
8.1 用户管理系统
go
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/lib/pq"
)
func main() {
// 连接数据库
dsn := "host=localhost port=5432 user=postgres password=password dbname=testdb sslmode=disable"
db, err := sql.Open("postgres", dsn)
if err != nil {
log.Fatal("连接数据库失败:", err)
}
defer db.Close()
// 配置连接池
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(5 * time.Minute)
// 测试连接
if err := db.Ping(); err != nil {
log.Fatal("数据库连接测试失败:", err)
}
// 创建表
if err := createTables(db); err != nil {
log.Fatal("创建表失败:", err)
}
// 演示各种查询操作
demonstrateQueries(db)
}
// createTables 创建表
func createTables(db *sql.DB) error {
createUserTable := `
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
age INTEGER,
phone VARCHAR(20),
avatar TEXT,
is_active BOOLEAN DEFAULT true,
settings JSONB,
tags TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP
)`
_, err := db.Exec(createUserTable)
if err != nil {
return fmt.Errorf("创建用户表失败: %w", err)
}
// 创建索引
indexes := []string{
"CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)",
"CREATE INDEX IF NOT EXISTS idx_users_active ON users(is_active) WHERE is_active = true",
"CREATE INDEX IF NOT EXISTS idx_users_age_active ON users(age, is_active)",
"CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at)",
}
for _, index := range indexes {
if _, err := db.Exec(index); err != nil {
return fmt.Errorf("创建索引失败: %w", err)
}
}
return nil
}
// demonstrateQueries 演示查询操作
func demonstrateQueries(db *sql.DB) {
fmt.Println("=== 数据库查询与扫描演示 ===")
// 1. 插入测试数据
fmt.Println("\n1. 插入测试数据...")
insertTestData(db)
// 2. 基本查询
fmt.Println("\n2. 基本查询演示...")
basicQueryDemo(db)
// 3. 动态查询
fmt.Println("\n3. 动态查询演示...")
dynamicQueryDemo(db)
// 4. 预编译语句
fmt.Println("\n4. 预编译语句演示...")
preparedStatementDemo(db)
// 5. 分页查询
fmt.Println("\n5. 分页查询演示...")
paginationDemo(db)
// 6. 聚合查询
fmt.Println("\n6. 聚合查询演示...")
aggregationDemo(db)
}
// insertTestData 插入测试数据
func insertTestData(db *sql.DB) {
users := []struct {
username string
email string
age int
isActive bool
}{
{"alice", "alice@example.com", 25, true},
{"bob", "bob@example.com", 30, true},
{"charlie", "charlie@example.com", 35, false},
{"diana", "diana@example.com", 28, true},
{"eve", "eve@example.com", 32, false},
}
for _, user := range users {
_, err := db.Exec(
"INSERT INTO users (username, email, age, is_active) VALUES ($1, $2, $3, $4) ON CONFLICT (username) DO NOTHING",
user.username, user.email, user.age, user.isActive)
if err != nil {
log.Printf("插入用户失败: %v", err)
}
}
fmt.Println("测试数据插入完成")
}
// basicQueryDemo 基本查询演示
func basicQueryDemo(db *sql.DB) {
// 查询所有用户
rows, err := db.Query("SELECT id, username, email, age, is_active FROM users ORDER BY id")
if err != nil {
log.Printf("查询失败: %v", err)
return
}
defer rows.Close()
fmt.Println("所有用户:")
for rows.Next() {
var id, age int
var username, email string
var isActive bool
err := rows.Scan(&id, &username, &email, &age, &isActive)
if err != nil {
log.Printf("扫描行失败: %v", err)
continue
}
fmt.Printf(" ID: %d, 用户名: %s, 邮箱: %s, 年龄: %d, 活跃: %t\n",
id, username, email, age, isActive)
}
// 单行查询
var count int
err = db.QueryRow("SELECT COUNT(*) FROM users WHERE is_active = true").Scan(&count)
if err != nil {
log.Printf("查询活跃用户数失败: %v", err)
return
}
fmt.Printf("活跃用户数: %d\n", count)
}
// dynamicQueryDemo 动态查询演示
func dynamicQueryDemo(db *sql.DB) {
// 使用查询构建器
qb := NewQueryBuilder("users").
Select("id", "username", "email", "age").
Where("age > $1", 25).
Where("is_active = $2", true).
OrderBy("age", "ASC").
Limit(3)
query, args := qb.Build()
fmt.Printf("动态构建的查询: %s\n", query)
fmt.Printf("参数: %v\n", args)
rows, err := db.Query(query, args...)
if err != nil {
log.Printf("动态查询失败: %v", err)
return
}
defer rows.Close()
fmt.Println("查询结果:")
for rows.Next() {
var id, age int
var username, email string
err := rows.Scan(&id, &username, &email, &age)
if err != nil {
log.Printf("扫描行失败: %v", err)
continue
}
fmt.Printf(" ID: %d, 用户名: %s, 年龄: %d\n", id, username, age)
}
}
// preparedStatementDemo 预编译语句演示
func preparedStatementDemo(db *sql.DB) {
// 预编译查询语句
stmt, err := db.Prepare("SELECT username, email FROM users WHERE age = $1")
if err != nil {
log.Printf("预编译语句失败: %v", err)
return
}
defer stmt.Close()
// 使用预编译语句查询不同年龄的用户
ages := []int{25, 30, 35}
for _, age := range ages {
rows, err := stmt.Query(age)
if err != nil {
log.Printf("查询年龄 %d 的用户失败: %v", age, err)
continue
}
fmt.Printf("年龄为 %d 的用户:\n", age)
for rows.Next() {
var username, email string
err := rows.Scan(&username, &email)
if err != nil {
log.Printf("扫描行失败: %v", err)
continue
}
fmt.Printf(" 用户名: %s, 邮箱: %s\n", username, email)
}
rows.Close()
}
}
// paginationDemo 分页查询演示
func paginationDemo(db *sql.DB) {
pageSize := 2
page := 1
for {
offset := (page - 1) * pageSize
rows, err := db.Query(
"SELECT id, username, email FROM users ORDER BY id LIMIT $1 OFFSET $2",
pageSize, offset)
if err != nil {
log.Printf("分页查询失败: %v", err)
return
}
var users []struct {
ID int
Username string
Email string
}
for rows.Next() {
var user struct {
ID int
Username string
Email string
}
err := rows.Scan(&user.ID, &user.Username, &user.Email)
if err != nil {
log.Printf("扫描行失败: %v", err)
continue
}
users = append(users, user)
}
rows.Close()
if len(users) == 0 {
break
}
fmt.Printf("第 %d 页 (每页 %d 条):\n", page, pageSize)
for _, user := range users {
fmt.Printf(" ID: %d, 用户名: %s, 邮箱: %s\n", user.ID, user.Username, user.Email)
}
if len(users) < pageSize {
break
}
page++
}
}
// aggregationDemo 聚合查询演示
func aggregationDemo(db *sql.DB) {
query := `
SELECT
COUNT(*) as total_users,
COUNT(CASE WHEN is_active = true THEN 1 END) as active_users,
AVG(age) as average_age,
MIN(age) as min_age,
MAX(age) as max_age
FROM users`
var totalUsers, activeUsers int
var avgAge, minAge, maxAge float64
err := db.QueryRow(query).Scan(&totalUsers, &activeUsers, &avgAge, &minAge, &maxAge)
if err != nil {
log.Printf("聚合查询失败: %v", err)
return
}
fmt.Printf("用户统计信息:\n")
fmt.Printf(" 总用户数: %d\n", totalUsers)
fmt.Printf(" 活跃用户数: %d\n", activeUsers)
fmt.Printf(" 平均年龄: %.2f\n", avgAge)
fmt.Printf(" 最小年龄: %.0f\n", minAge)
fmt.Printf(" 最大年龄: %.0f\n", maxAge)
}
9. 最佳实践总结
9.1 查询优化建议
-
使用适当的索引
- 为经常查询的列创建索引
- 使用复合索引优化多列查询
- 避免在小表上创建过多索引
-
查询语句优化
- 只查询需要的列,避免
SELECT * - 使用
LIMIT限制结果集大小 - 合理使用
WHERE条件过滤数据
- 只查询需要的列,避免
-
连接池配置
- 根据应用负载调整连接池大小
- 设置合适的连接生命周期
- 监控连接池使用情况
9.2 扫描最佳实践
-
正确处理 NULL 值
- 使用
sql.NullString、sql.NullInt64等类型 - 实现自定义扫描类型处理复杂数据
- 使用
-
错误处理
- 始终检查
rows.Err() - 正确处理
sql.ErrNoRows - 使用适当的错误包装
- 始终检查
-
资源管理
- 及时关闭
rows和stmt - 使用
defer确保资源释放 - 避免资源泄漏
- 及时关闭
9.3 性能优化技巧
-
批量操作
- 使用事务进行批量插入/更新
- 预编译语句提高重复操作性能
- 合理使用批处理大小
-
内存管理
- 流式处理大结果集
- 分页处理避免内存溢出
- 及时释放不需要的数据
-
监控和调试
- 使用
EXPLAIN分析查询计划 - 监控慢查询
- 定期分析数据库性能
- 使用
总结
本章详细介绍了 Go 语言中数据库查询与扫描的各种技术和最佳实践。通过掌握这些技术,你可以:
- 高效执行各种类型的数据库查询
- 正确处理查询结果的扫描和类型转换
- 构建灵活的动态查询系统
- 优化查询性能和资源使用
- 处理大数据集和复杂查询场景
这些技术为构建高性能的数据库应用程序提供了坚实的基础。在实际开发中,应该根据具体的业务需求和性能要求,选择合适的查询和扫描策略。