南来的风23 2024-05-16 17:47 采纳率: 0%
浏览 114

gorm的mysql保存数据保存主键冲突Duplicate entry for key PRIMARY

代码如下:

func (s *MonitorDiskService) SyncData(c *SyncConfig) (err error) {
    data, err := s.getData(c, nil, nil)
    if err != nil {
        return err
    }

    diskInfo := table.GetMonitorDiskInfos(data, c.relation)

    now := time.Now()
    var disks []*model.MonitorDiskInfo
    for _, info := range diskInfo {
        info.CloudUUID = c.cloud.UUID
        info.UpdateTime = now
        if info.ID == "" {
            continue
        }
        disks = append(disks, info)
    }

    q := query.Use(s.db.GetDb())
    tx := q.Begin()
    defer func() {
        if recover() != nil || err != nil {
            _ = tx.Rollback()
        }
    }()
    vi := tx.MonitorDiskInfo
    _, err = vi.WithContext(context.TODO()).Where(vi.CloudUUID.Eq(c.cloud.UUID)).Delete()
    if err != nil {
        return err
    }
    // 注意:现场有的云环境同步monitor_disk_info数据时delete后create数据会报主键冲突,其它monitor_disk_info正常同步,问题根因不明,只能将create换为save
    for i := 0; true; i++ {
        if (i+1)*configs.SyncCreateLimit >= len(data) {
            err = vi.WithContext(context.TODO()).Save(disks[i*configs.SyncCreateLimit:]...)
            if err != nil {
                s.logger.Error(fmt.Sprintf("table monitor_disk_info create index from:%v to:%v of data failed,index,err:%v",i*configs.SyncCreateLimit,len(data),err))
                return err
            }
            break
        } else {
            err = vi.WithContext(context.TODO()).Save(disks[i*configs.SyncCreateLimit : (i+1)*configs.SyncCreateLimit]...)
            if err != nil {
                s.logger.Error(fmt.Sprintf("table monitor_disk_info create index from:%v to:%v of data failed,index,err:%v",i*configs.SyncCreateLimit,(i+1)*configs.SyncCreateLimit,err))
                return err
            }
        }
    }
    return tx.Commit()
}

代码业务逻辑为:
这是一段同步disks数据到数据库的代码,其中 disks为需要保存到表的数据,代码逻辑为:先delete表的数据,再分批次insert数据到表。
问题:
本地环境测试没有问题,数据正常同步保存。但是在客户环境,客户有两台环境A和B,其中A可以正常同步数据,B不行,会报错主键冲突的问题。
怀疑数据delete的时候没有成功,导致后面insert报错主键冲突,但是通过日志打断点方式,确认delete那里的err为空,不知道该怎么排查了。
背景:同步的这个服务是一个k8s微服务,只有一个pod,没有启用多副本。环境的mysql是3节点,1主2从

  • 写回答

2条回答 默认 最新

  • 阿里嘎多学长 2024-05-16 17:47
    关注

    以下内容由CHATGPT及阿里嘎多学长共同生成、有用望采纳:


    您想要解决的问题

    您想要解决的问题是在使用GORM操作MySQL数据库时,遇到“Duplicate entry for key PRIMARY”错误,即主键冲突问题。这个问题在客户环境B中出现,而在本地环境和客户环境A中则没有出现。

    问题出现原因

    1. 环境差异:本地环境和客户环境A可以正常同步数据,而客户环境B出现主键冲突,表明问题可能与环境B的数据库配置或状态有关。
    2. 事务处理:代码中使用了事务来处理删除和插入操作,如果在执行过程中出现异常,可能导致事务未能正确提交或回滚。
    3. 数据删除不彻底:尽管日志显示err为空,但可能存在删除操作未能彻底执行的情况,导致尝试插入时发生主键冲突。
    4. 并发问题:如果存在其他操作同时对数据库进行修改,可能会造成数据冲突。

    问题解决方案

    1. 检查环境B的数据库配置:确认环境B的MySQL配置与A一致,特别是与事务和锁相关的设置。
    2. 增强日志记录:在删除和插入操作前后添加更详细的日志记录,以便更准确地捕捉到操作的状态。
    3. 检查事务的完整性:确保事务在所有分支路径上都能正确提交或回滚。
    4. 避免批量插入时的主键冲突:在插入数据前,可以检查数据的主键是否存在,如果存在则更新或跳过。
    5. 使用锁机制:在删除和插入操作期间,使用适当的锁机制来避免并发写入导致的数据冲突。

    提供代码

    以下是修改后的代码示例,增加了检查主键是否存在的逻辑:

    func (s *MonitorDiskService) SyncData(c *SyncConfig) (err error) {
        // ...省略其他代码...
    
        tx := q.Begin()
        defer func() {
            if recover() != nil || err != nil {
                _ = tx.Rollback()
            }
        }()
        
        // ...省略删除操作代码...
        
        // 检查并保存数据
        for _, disk := range disks {
            // 检查主键是否存在
            exist, err := vi.WithContext(context.TODO()).Where(vi.ID.Eq(disk.ID)).Exists()
            if err != nil {
                return err
            }
            if exist {
                // 如果存在,则更新数据
                err = vi.WithContext(context.TODO()).Updates(disk).Error
            } else {
                // 如果不存在,则插入数据
                err = vi.WithContext(context.TODO()).Create(disk).Error
            }
            if err != nil {
                s.logger.Error(fmt.Sprintf("Failed to save monitor_disk_info data, err: %v", err))
                return err
            }
        }
        
        return tx.Commit()
    }
    

    代码运行方式

    1. 确保您的开发环境已经安装了Go语言环境和MySQL数据库。
    2. 将上述代码集成到您的服务中,并确保您的服务可以连接到客户环境B的MySQL数据库。
    3. 运行您的服务,并观察是否还会出现主键冲突的错误。

    代码预期运行结果

    预期中,修改后的代码将不再尝试插入已经存在的主键,而是检查主键是否存在,并根据情况选择更新或插入数据。这应该可以避免“Duplicate entry for key PRIMARY”错误。

    推荐相关链接

    评论 编辑记录

报告相同问题?

问题事件

  • 创建了问题 5月16日