从内存到sql的upsert

业务的upsert

​ 在写业务时,大家一开始都会以顺序流程的方式开始着手写代码,CR时再看代码,会有不一样的感觉。

1. 需求描述

​ 现有一张数据库表,表字段结构如下:

字段名称类型描述
uuidstring数据的唯一键
datastring业务数据
versionint请求时业务的版本好
checksumstringdata内容的哈希值
updated_attime.Time更新时间

​ 需求: 若干个请求方会调用接口并带有该次请求的版本号来更新数据

​ 更新规则:

  • 数据的checksum不一致时,更新数据
  • version版本号为0时,默认要更新数据; 当version版本号大于db中版本号时, 更新数据

2. 需求实现

​ 该部分会根据业务场景不断演化

2.1 简单实现

​ 一开始看到这个需求, 很简单啊, 有手就行!

type BusinessData struct {ID        int64     `gorm:"column:id;type:int(11);primary_key;AUTO_INCREMENT"`UUID      string    `gorm:"column:uuid;type:varchar(255);uniqueIndex:uniq_uuid"`Checksum  string    `gorm:"column:checksum;type:varchar(255)"`Version   int     `gorm:"column:version;type:int(11);default:0"`CreatedAt time.Time `gorm:"column:created_at;type:datetime(3);default:CURRENT_TIMESTAMP(3)"`UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3);update:CURRENT_TIMESTAMP(3)"`
}func GetDbData(uuids []string) ([]*BusinessData, error) {var (res []*BusinessDataerr error)dsn := "user:password@tcp(localhost:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"db, oErr := gorm.Open(mysql.Open(dsn), &gorm.Config{})if oErr != nil {fmt.Println(oErr)return nil, oErr}err = db.Table("business").Where("uuid IN ?", uuids).Find(&res).Errorif err != nil {fmt.Println(err)return nil, err}return res, nil
}func UpsertData(data []*BusinessData) error {dsn := "user:password@tcp(localhost:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"db, oErr := gorm.Open(mysql.Open(dsn), &gorm.Config{})if oErr != nil {fmt.Println(oErr)return oErr}err := db.Table("business").Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "uuid"},},DoUpdates: clause.AssignmentColumns([]string{"data", "version", "checksum", "updated_at"}),}).Create(data).Errorif err != nil {return err}return nil
}func main() {var (datas            []*BusinessDatatoUpsertDataList []*BusinessDatauuids            []string)for _, data := range datas {uuids = append(uuids, data.UUID)}dbDatas, err := GetDbData(uuids)if err != nil {fmt.Println(err)return}existDataMap := make(map[string]*BusinessData)for _, dbData := range dbDatas {existDataMap[dbData.UUID] = dbData}for _, data := range datas {if _, ok := existDataMap[data.UUID]; !ok {toUpsertDataList = append(toUpsertDataList, data)continue}if data.Checksum == existDataMap[data.UUID].Checksum {continue}if data.Version != 0 && data.Version < existDataMap[data.UUID].Version {continue}toUpsertDataList = append(toUpsertDataList, data)}err = UpsertData(toUpsertDataList)if err != nil {fmt.Println(err)}
}

​ 代码很简单,读取db中的数据到内存, 根据checksum和version的要求进行过滤, 符合条件的data便进行更新/插入

2.2 进阶实现

2.2.1 思考1

​ 将db中的数据读到内存后进行uuid匹配, 是不是跟upsert时利用唯一键冲突更新数据重复了呢???

2.2.2 思考2

​ 难道说利用唯一键冲突就是满足更新的所有条件吗?

​ 在db进行update时,我们使用where来过滤数据, 那么在upsert时是不是也可以通过什么手段过滤数据呢???

将两个简单思考进行实现(这里只展示UpsertData方法)

err := db.Table("business").Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "uuid"}},DoUpdates: clause.Assignments(map[string]interface{}{"checksum":   gorm.Expr("IF((version <= VALUES(version) OR VALUES(version) = 0) AND checksum != VALUES(checksum), VALUES(checksum), checksum)"),"updated_at": gorm.Expr("IF((version <= VALUES(version) OR VALUES(version) = 0) AND checksum != VALUES(checksum), VALUES(updated_at), updated_at)"),"version":    gorm.Expr("IF((version <= VALUES(version) OR VALUES(version) = 0) AND checksum != VALUES(checksum), VALUES(version), version)"),}),}).Create(data).Error

是不是大功告成! 准备完结撒花🎉了

ON DUPLICATE KEY UPDATE 语序会有影响结果

也就是说, 按顺序执行时, 先更新checksum(正常), 再更新updated_at和version(异常)

异常原因: checksum更新后, IF条件中checksum == VALUES(checksum) 成立

2.2.3 思考3

​ 那这样的话, 我改变语序不就好了吗, 直接就是完结撒花🎉!

​ 那假如,我有很多个字段,我一个一个调试看看该字段会不会影响其他字段太浪费时间了, 那有没有什么办法能够记录快照状态呢

对, 快照!

我们把当前的条件字段变成快照状态, 用快照进行条件判断,就不用担心语序问题了

开始实现:

	condition := "(version <= VALUES(version) OR VALUES(version) = 0) AND checksum != VALUES(checksum)"err := db.Table("business").Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "uuid"},},DoUpdates: []clause.Assignment{{Column: clause.Column{Name: "version"},Value: gorm.Expr(fmt.Sprintf("IF(@should_update := %s, VALUES(%s), %s)", condition, "version", "version")),},{Column: clause.Column{Name: "checksum"},Value: gorm.Expr(fmt.Sprintf("IF(@should_update := %s, VALUES(%s), %s)", condition, "checksum", "checksum")),},{Column: clause.Column{Name: "updated_at"},Value: gorm.Expr(fmt.Sprintf("IF(@should_update := %s, VALUES(%s), %s)", condition,  "updated_at",  "updated_at")),},},}).Create(data).Error

美化一下

// UpsertExprs _
func UpsertExprs(condition string, columns []string) clause.Set {if len(columns) < 1 {return clause.Set{}}var assignments []clause.Assignmentfor i, column := range columns {if i == 0 {assignments = append(assignments, clause.Assignment{Column: clause.Column{Name: column},Value: gorm.Expr(fmt.Sprintf("IF(@should_update := %s, VALUES(%s), %s)", condition, column, column)),})continue}assignments = append(assignments, clause.Assignment{Column: clause.Column{Name: column},Value:  gorm.Expr(fmt.Sprintf("IF(@should_update, VALUES(%s), %s)", column, column)),})}return assignments
}func UpsertData(data []*BusinessData) error {dsn := "user:password@tcp(localhost:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"db, oErr := gorm.Open(mysql.Open(dsn), &gorm.Config{})if oErr != nil {fmt.Println(oErr)return oErr}condition := "(version <= VALUES(version) OR VALUES(version) = 0) AND checksum != VALUES(checksum)"err := db.Table("business").Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "uuid"},},DoUpdates: UpsertExprs(condition, []string{"version", "checksum", "updated_at"}),}).Create(data).Errorif err != nil {return err}return nil
}

那就真的完结撒花捏🎉

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/21230.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营第四十六天|KM52. 携带研究材料、518. 零钱兑换 II、377. 组合总和 Ⅳ

代码随想录算法训练营第四十六天 KM52. 携带研究材料 题目链接&#xff1a;KM52. 携带研究材料 确定dp数组以及下标的含义&#xff1a;j的含义是当前背包的最大容量&#xff0c;dp[j]背包内物品的总价值确定递推公式&#xff1a;背包最大容量固定为j&#xff0c;每个循环尝试…

Nginx01-HTTP简介与Nginx简介(安装、命令介绍、目录介绍、配置文件介绍)

目录 HTTP简介HTTP原理查看访问网站的详细流程curl -vwget --debug 查看网站访问量HTTP协议版本HTTP协议交互HTTP 请求请求报文起始行请求头 HTTP响应响应报文起始行响应头 Nginx常见的Web服务常见网站服务 安装NginxNginx目录结构Nginx启动管理Nginx常用命令 Nginx配置文件主配…

国内外主流大模型语言技术大比拼

国内外主流大模型语言技术对比 2024 自2017年起&#xff0c;美国深度布局人工智能&#xff0c;全面融入经济、文化与社会。至2023年&#xff0c;中国凭借自研技术平台崭露头角&#xff0c;ChatGPT及其技术成国家战略焦点&#xff0c;引领未来科技浪潮。中美竞逐&#xff0c;人工…

Milvus向量数据库:开启向量搜索新纪元

Milvus向量数据库&#xff1a;开启向量搜索新纪元 随着人工智能和机器学习技术的飞速发展&#xff0c;向量数据在各个领域的应用越来越广泛&#xff0c;如推荐系统、自然语言处理、计算机视觉等。在这样的背景下&#xff0c;如何高效地存储、查询和管理向量数据成为了一个重要的…

香橙派 AI pro:AI 加速初体验

香橙派 AI pro&#xff1a;AI 加速初体验 在AI领域&#xff0c;不断涌现的硬件产品为开发者提供了前所未有的便利和可能性。今天&#xff0c;我要介绍的这款产品——香橙派 AIpro&#xff0c;就是其中的佼佼者。在昇腾 AI 芯片的加持下&#xff0c;这款开发板有着出色的算力。…

961题库 北航计算机 操作系统 附答案 选择题形式

有题目和答案&#xff0c;没有解析&#xff0c;不懂的题问大模型即可&#xff0c;无偿分享。 第1组 习题 计算机系统的组成包括&#xff08; &#xff09; A、程序和数据 B、处理器和内存 C、计算机硬件和计算机软件 D、处理器、存储器和外围设备 财务软件是一种&#xff…

【Qt 学习笔记】Qt窗口 | 对话框 | Qt对话框的分类及介绍

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt窗口 | 对话框 | 模态对话框 文章编号&#xff1a;Qt 学习笔记 / 51…

Java反序列化漏洞与URLDNS利用链分析

前言 前面学习过 Java 反序列化漏洞的部分知识&#xff0c;总结过几篇文章&#xff1a; 文章发布日期内容概括《渗透测试-JBoss 5.x/6.x反序列化漏洞》2020-07-08JBoss 反序列化漏洞 CVE-2017-12149 的简单复现&#xff0c;使用了 ysoserial 和 CC5 链&#xff0c;未分析漏洞…

easy-captcha生成验证码

引入依赖 <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>…

[力扣题解] 404. 左叶子之和

题目&#xff1a;404. 左叶子之和 思路 前序遍历&#xff08;随便怎么遍历&#xff09;&#xff1b; 在遇到左叶子时处理数据&#xff0c;选择中、左、右里面的左的时候再判断这个节点是不是叶子&#xff1b; 代码 /*** Definition for a binary tree node.* struct TreeNo…

Unity2D游戏开发-玩家控制

在Unity2D游戏开发中&#xff0c;玩家控制是游戏互动性的核心。本文将解析一个典型的Unity2D玩家控制脚本&#xff0c;探讨如何实现流畅的玩家移动、跳跃和动画切换。以下是一个Unity脚本示例&#xff0c;实现了这些基础功能。 1. 脚本结构 using System.Collections; using …

机械设计手册第一册:公差

形位公差的标注&#xff1a; 形位公差框格中&#xff0c;不仅要表达形位公差的特征项目、基准代号和其他符号&#xff0c;还要正确给出公差带的大小、形状等内容。 1.形位公差框格&#xff1a; 形位公差框格由两个框格或多个格框组成&#xff0c;框格中的主要内容从左到右按…

(2024,扩散,去噪调度,维度,误差,收敛速度)适应基于分数的扩散模型中的未知低维结构

Adapting to Unknown Low-Dimensional Structures in Score-Based Diffusion Models 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 1. 引言 1.1 扩散模型 1.2 现有结果的不…

服务器硬件基础知识学习

服务器硬件基础知识涵盖了从CPU到存储&#xff0c;再到网络连接和总线技术等关键组件。 1. 处理器 - 两大流派&#xff1a;我们常用的处理器主要分为Intel和AMD两大阵营。Intel的Xeon系列和AMD的EPYC系列都是专为服务器设计的&#xff0c;它们支持多核处理&#xff0c;能够应对…

语言模型的校准技术:增强概率评估

​ 使用 DALLE-3 模型生成的图像 目录 一、说明 二、为什么校准对 LLM 模型至关重要 三、校准 LLM 概率的挑战 四、LLM 的高级校准方法 4.1 语言置信度 4.2 增强语言自信的先进技术 4.3 基于自一致性的置信度 4.4 基于 Logit 的方法 五、代理模型或微调方法 5.1 使用代…

集成算法实验与分析(软投票与硬投票)

概述 目的&#xff1a;让机器学习效果更好&#xff0c;单个不行&#xff0c;集成多个 集成算法 Bagging&#xff1a;训练多个分类器取平均 f ( x ) 1 / M ∑ m 1 M f m ( x ) f(x)1/M\sum^M_{m1}{f_m(x)} f(x)1/M∑m1M​fm​(x) Boosting&#xff1a;从弱学习器开始加强&am…

排序-插入排序与选择排序

插入排序 基本思想 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中&#xff0c;直到所有的记录插入完为止&#xff0c;得到一个新的有序序列 。 打扑克牌整理手牌用的就是插入排序的思想 代码实现 void InsertSort(int* a, int n) { assert(a); …

C语言自定义类型

在C语言中&#xff0c;自定义类型可以通过typedef关键字来实现。typedef用于为现有的数据类型创建新的名称&#xff08;别名&#xff09;&#xff0c;使代码更清晰易读。自定义类型的一个常见用途是简化复杂的类型声明&#xff0c;特别是在使用结构体、枚举和函数指针时。 使用…

52、有边数限制的最短路

有边数限制的最短路 题目描述 给定一个n个点m条边的有向图&#xff0c;图中可能存在重边和自环&#xff0c; 边权可能为负数。 请你求出从1号点到n号点的最多经过k条边的最短距离&#xff0c;如果无法从1号点走到n号点&#xff0c;输出impossible。 注意&#xff1a;图中可…

查看 WSL2 (Windows Subsystem for Linux 2) IP 地址

查看 WSL2 [Windows Subsystem for Linux 2] IP 地址 1. ipconfig2. ping $(hostname).local3. cat /etc/resolv.conf4. ip route show5. ip addrReferences 1. ipconfig Windows 系统上与 WSL2 (Windows Subsystem for Linux 2) 接口的地址 172.31.32.1。 Microsoft Windows…