Golang如何解决重复提交并发问题

发布时间:2024年01月05日


前言

在实际开发中,有很多情况出现,其中用户重复提交或多个用户同时操作点击同一个server服务提交导致数据冲突脏数据的出现,从而引发问题,解决也比较简单,本文提供四种方法,如下,如有不足还请多多指教


一 .前端防止重复点击

在前端代码中,通过添加点击按钮的禁用状态或设置点击按钮的点击间隔时间来防止用户重复点击提交按钮。这样可以减少用户重复点击的可能性。

二 .后端防止重复插入

在后端代码中,可以通过使用全局锁或分布式锁来确保同一时间只有一个请求可以进行插入操作,从而防止数据的重复插入。下面是使用 sync.Mutex 实现的一个简单示例:

var mu sync.Mutex

func handleData(data Data) error {
    mu.Lock()
    defer mu.Unlock()

    // 判断数据是否已存在
    existingData, err := getDataFromDB(data.ID)
    if err != nil {
        return err
    }

    if existingData != nil {
        return fmt.Errorf("Data already exists")
    }

    // 插入数据到数据库
    err = insertDataToDB(data)
    if err != nil {
        return err
    }

    return nil
}

在上述代码中,使用 sync.Mutex 对象 mu 来实现了一个互斥锁。在处理数据的函数中,先获取锁,然后进行数据存在性检查和插入操作,最后释放锁。这样可以确保同一时间只有一个请求可以进行数据插入操作,其他请求会在锁释放后才能进行插入操作。

三. 数据库约束

在数据库中使用唯一约束(Unique Constraint)来防止数据重复插入。在 GORM 中,可以通过在模型定义中使用 gorm:“unique” 标签来设置字段的唯一约束,或者在数据库表中设置唯一索引。这样,当有重复数据插入时,数据库会返回错误,可以捕获该错误并进行相应处理。

type User struct {
    ID   uint   `gorm:"primaryKey"`
    Name string `gorm:"unique"`
    // 其他字段...
}

func createUser(user User) error {
    if err := db.Create(&user).Error; err != nil {
        if isDuplicateKeyError(err) {
            // 处理唯一性冲突的错误
            return fmt.Errorf("User already exists")
        }
        // 其他错误处理...
        return err
    }
    
    return nil
}

func isDuplicateKeyError(err error) bool {
    // 检查错误是否为唯一索引冲突的错误
    if err != nil {
        var mysqlError *mysql.MySQLError
        if errors.As(err, &mysqlError) && mysqlError.Number == 1062 {
            return true
        }
    }
    return false
}

在上述代码中,通过给字段添加 gorm:“unique” 标签,设置字段的唯一约束。然后,在插入数据时,如果发生唯一性冲突,会返回特定的错误,通过检查错误类型和错误代码来判断是否已经存在重复的数据。

四 通过事物db.Transaction进行处理

注意:该方法只针对同一事物中处理多个逻辑,如果是用户点击重复提交,需结合唯一索引或mutex局部锁的方法执行才有效

import (
    "fmt"

    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

type Model struct {
    ID uint `gorm:"primaryKey"`
}

type User struct {
    Model
    Name string
}

func main() {
    // 连接数据库
    dsn := "user:password@tcp(localhost:3306)/database?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        fmt.Println("数据库连接失败:", err)
        return
    }
    sqlDB, _ := db.DB()
    defer sqlDB.Close()

    // 定义事务闭包函数
    txFunc := func(tx *gorm.DB) error {
        // 执行插入操作
        user1 := User{Name: "John"}
        err := tx.Create(&user1).Error
        if err != nil {
            return err
        }

        // 在同一个事务中重复插入相同数据
        user2 := User{Name: "John"}
        err = tx.Create(&user2).Error
        if err != nil {
            return err
        }

        return nil
    }

    // 开始事务
    err = db.Transaction(func(tx *gorm.DB) error {
        // 调用事务闭包函数
        err := txFunc(tx)
        if err != nil {
            tx.Rollback()
            return fmt.Errorf("事务执行失败: %w", err)
        }
        return nil
    })
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println("插入成功")
}

五 redies防重复点击

要实现防止重复点击提交功能,可以使用 Redis 实现一个简单的幂等性验证机制。以下是一个示例代码:

import (
    "fmt"
    "time"

    "github.com/go-redis/redis"
)

func main() {
    // 连接 Redis
    client := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // 如果需要密码
        DB:       0,  // 使用默认数据库
    })

    // 检查是否已经存在重复提交的标识
    exists, err := client.Exists("submit_key").Result()
    if err != nil {
        fmt.Println("Redis 查询失败:", err)
        return
    }

    if exists == 1 {
        fmt.Println("请勿重复提交")
        return
    }

    // 设置标识并设置过期时间
    err = client.Set("submit_key", "submitted", 5*time.Minute).Err()
    if err != nil {
        fmt.Println("Redis 设置失败:", err)
        return
    }

    // 执行提交操作
    err = submit()
    if err != nil {
        fmt.Println("提交失败:", err)
        return
    }

    fmt.Println("提交成功")
}

func submit() error {
    // 模拟提交操作
    time.Sleep(3 * time.Second)
    return nil
}

在以上示例代码中,我们使用了 Go Redis 客户端库(github.com/go-redis/redis)连接到 Redis,并提供了 Redis 服务器的地址和认证信息(如果需要)。

在提交之前,我们首先检查一个名为 “submit_key” 的键是否存在于 Redis 中。如果存在,说明已经提交过,我们会阻止重复提交。

如果键不存在,我们使用 client.Set 来设置一个键为 “submit_key” 的标识,并设置了过期时间为 5 分钟
在执行具体的提交操作之前,可以根据需要编写自己的提交函数 submit()。
这样,在每次提交之前,先检查 Redis 中的标识是否存在,可以有效防止重复点击提交。

请注意,你需要在本地安装 Redis 服务器,并将地址和认证信息(如果需要)正确配置到示例代码中。同时,你也需要安装 Go Redis 客户端库,可以使用 go get 命令进行安装(如:go get -u github.com/go-redis/redis)。

你可以根据实际需求和业务逻辑进行修改和扩展以上示例代码。

提示:综上所述,可以使用前端和后端的方式来防止用户重复点击导致的数据重复插入问题。同时,也可以利用数据库的唯一约束,或者redies机制来避免数据的重复插入。如有不足或误区,还请多多指教

文章来源:https://blog.csdn.net/MatChen/article/details/135406095
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。