從內存到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.pswp.cn/bicheng/21230.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/21230.shtml
英文地址,請注明出處:http://en.pswp.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…