基于gorm.io/sharding分表中间件使用案例

项目背景

项目中需要用到mysql的分表场景,调研了一些常用的分库分表中间件,比如,mycat,小米的Gaea,这两个中间件太重了,学习成本较大,另外mycat不是go写的。我们需要一个轻量级的go版本的分表中间件。所以,把目光放在了如下这个开源组件上。go-gorm/sharding: High performance table sharding plugin for Gorm. (github.com)icon-default.png?t=O83Ahttps://github.com/go-gorm/sharding

案例

自定义分表函数以及主键生成自定义函数。该案例仅用于测试,分表的逻辑通常不会基于时间进行分表。测试中使用的主键生成方法也仅仅是用于测试,实际项目中并不推荐使用,该方式对于高并发场景并不友好,实际场景中使用预先生成的方式或是其他的分布式id生成器更好。

事实上,这个库有一些默认的主键id生成方式。

const (// Use Snowflake primary key generatorPKSnowflake = iota// Use PostgreSQL sequence primary key generatorPKPGSequence// Use MySQL sequence primary key generatorPKMySQLSequence// Use custom primary key generatorPKCustom
)

但是,在这个自定义分表逻辑场景中,使用默认的主键id生成方式,出现了bug。

 比如,使用KMySQLSequence这种主键生成方式,第二次执行插入sql操作就报了Error 1062 (23000): Duplicate entry '2' for key 'orders_2025.PRIMARY'这个错误。我大概看了一下导致错误的原因在于如下这个方法,这个方法的意思是主键id获取数据库中最后一次插入操作所生成的自增ID值,这就导致,第二次执行插入sql操作时,主键取的是已经存在的,导致报了上面的错误。

初步分析下来是这个原因,目前尚未验证这个错误原因。

func (s *Sharding) genMySQLSequenceKey(tableName string, index int64) int64 {var id int64err := s.DB.Exec("UPDATE `" + mySQLSeqName(tableName) + "` SET id = LAST_INSERT_ID(id + 1)").Errorif err != nil {panic(err)}err = s.DB.Raw("SELECT LAST_INSERT_ID()").Scan(&id).Errorif err != nil {panic(err)}return id
}

 使用PKSnowflake这种主键生成方式,导致panic。

 初步分析下来的原因是这个index源码中默认设定的最大值是1024,但是,在我们这个场景中,分表的后缀超过了1024,来到了2024,2025等。

同样,该错误原因尚未深入分析,可能不是这个原因,尚待进一步分析。

func (s *Sharding) genSnowflakeKey(index int64) int64 {return s.snowflakeNodes[index].Generate().Int64()
}

测试案例如下: 

package testimport ("fmt""testing""time""gorm.io/driver/mysql""gorm.io/gorm""gorm.io/sharding"
)var globalDB *gorm.DBtype Order struct {ID        int64  `gorm:"primaryKey"`OrderId   string `gorm:"sharding:order_id"` // 指明 OrderId 是分片键UserID    int64ProductID int64OrderDate time.Time
}// 自定义 ShardingAlgorithm
func customShardingAlgorithm(value any) (suffix string, err error) {if orderId, ok := value.(string); ok {// 截取字符串,截取前8位,获取年份orderId = orderId[0:8]orderDate, err := time.Parse("20060102", orderId)if err != nil {return "", fmt.Errorf("invalid order_date")}year := orderDate.Year()return fmt.Sprintf("_%d", year), nil}return "", fmt.Errorf("invalid order_date")
}// customePrimaryKeyGeneratorFn 自定义主键生成函数
func customePrimaryKeyGeneratorFn(tableIdx int64) int64 {var id int64seqTableName := "gorm_sharding_orders_id_seq" // 序列表名db := globalDB// 使用事务来确保主键生成的原子性tx := db.Begin()defer func() {if r := recover(); r != nil {tx.Rollback()}}()// 锁定序列表以确保并发安全(可选,取决于你的 MySQL 配置和并发级别)// 注意:在某些 MySQL 版本和配置中,使用 LOCK TABLES 可能不是最佳选择// 这里仅作为示例,实际应用中可能需要更精细的并发控制策略tx.Exec("LOCK TABLES " + seqTableName + " WRITE")// 查询当前的最大 IDtx.Raw("SELECT id FROM " + seqTableName + " ORDER BY id DESC LIMIT 1").Scan(&id)// 更新序列表(这里直接递增 1,实际应用中可能需要更复杂的逻辑)newID := id + 1tx.Exec("INSERT INTO "+seqTableName+" (id) VALUES (?)", newID) // 这里假设序列表允许插入任意 ID,实际应用中可能需要其他机制来确保 ID 的唯一性和连续性// 释放锁定tx.Exec("UNLOCK TABLES")// 提交事务if err := tx.Commit().Error; err != nil {panic(err) // 实际应用中应该使用更优雅的错误处理机制}return newID
}// Test_Gorm_Sharding 用于测试 Gorm Sharding 插件
func Test_Gorm_Sharding(t *testing.T) {// 连接到 MySQL 数据库dsn := "***:******@tcp(ip:port)/sharding_db2?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.New(mysql.Config{DSN: dsn,}), &gorm.Config{})if err != nil {panic("failed to connect database")}globalDB = db// 配置 Gorm Sharding 中间件,使用自定义的分片算法middleware := sharding.Register(sharding.Config{ShardingKey:           "order_id",ShardingAlgorithm:     customShardingAlgorithm, // 使用自定义的分片算法PrimaryKeyGenerator:   sharding.PKCustom,PrimaryKeyGeneratorFn: customePrimaryKeyGeneratorFn,}, "orders") // 逻辑表名为 "orders"db.Use(middleware)// 创建 Order 表err = db.Exec(`CREATE TABLE IF NOT EXISTS orders_2024 (id BIGINT PRIMARY KEY,order_id VARCHAR(50),user_id INT,product_id INT,order_date DATETIME)`).Errorif err != nil {panic("failed to create table")}err = db.Exec(`CREATE TABLE IF NOT EXISTS orders_2025 (id BIGINT PRIMARY KEY,order_id VARCHAR(50),user_id INT,product_id INT,order_date DATETIME)`).Errorif err != nil {panic("failed to create table")}// 示例:插入订单数据order := Order{OrderId:   "20240101ORDER0001",UserID:    1,ProductID: 100,OrderDate: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),}err = db.Create(&order).Errorif err != nil {fmt.Println("Error creating order:", err)}order2 := Order{OrderId:   "20250101ORDER0001",UserID:    1,ProductID: 100,OrderDate: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),}err = db.Create(&order2).Errorif err != nil {fmt.Println("Error creating order:", err)}// 查询示例var orders []Ordererr = db.Model(&Order{}).Where("order_id=?", "20240101ORDER0001").Find(&orders).Errorif err != nil {fmt.Println("Error querying orders:", err)}fmt.Printf("Selected orders: %#v\n", orders)// 更新示例err = db.Model(&Order{}).Where("order_id=? and id=?", "20240101ORDER0001", int64(14)).Update("product_id", 102).Errorif err != nil {fmt.Println("Error updating order:", err)}// 再次查询示例,验证更新是否生效err = db.Model(&Order{}).Where("order_id=?", "20240101ORDER0001").Find(&orders).Errorif err != nil {fmt.Println("Error querying orders:", err)}fmt.Printf("Selected orders: %#v\n", orders)// 删除示例err = db.Model(&Order{}).Where("order_id=? and id =?", "20240101ORDER0001", int64(16)).Delete(&Order{}).Errorif err != nil {fmt.Println("Error deleting order:", err)}// 再次查询示例,验证删除是否生效err = db.Model(&Order{}).Where("order_id=?", "20240101ORDER0001").Find(&orders).Errorif err != nil {fmt.Println("Error querying orders:", err)}fmt.Printf("Selected orders: %#v\n", orders)
}

遗留问题 

1.上文中提到了两种默认主键生成方式报bug问题需要进一步定位以确定根因。

2.该组件的增删改查仅支持查询条件中包含分表主键的场景,对于,不包含主键需要全表扫描的场景并不支持,当然,全表扫描最优解并不是通过mysql的能力解决,最好是借助其他的方案,比如,通过es的方案。但是,对于,我们目前的场景中依然存在上述需求,所以,尚需考虑该场景如何解决。

3.IN查询也不支持。

本质上只支持,根据分表键路由到对应的表进行单表查询。

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

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

相关文章

Tomcat 乱码问题彻底解决

1. 终端乱码问题 找到 tomcat 安装目录下的 conf ---> logging.properties .修改ConsoleHandler.endcoding GBK (如果在idea中设置了UTF-8字符集,这里就不需要修改) 2. CMD命令窗口设置编码 参考:WIN10的cmd查看编码方式&am…

网络安全的方方面面

目录 一、网络安全概述二、数据加密三、消息完整性与数字签名四、身份认证五、密钥分发中心(KDC)与证书认证(CA)六、防火墙与入侵检测系统七、网络安全协议八、网络安全攻防 -- 黑客攻击简要流程九、网络安全常用术语 一、网络安全概述 网络安全的基本特征:相对性、…

稳了,搭建Docker国内源图文教程

大家好,之前分享了我的开源作品 Cloudflare Workers Proxy,它的作用是代理被屏蔽的地址,理论上支持代理任何被屏蔽的域名,使用方式也很简单,只需要设置环境变量 PROXY_HOSTNAME 为被屏蔽的域名,最后通过你的…

关于LlamaIndex 的几种索引方式介绍

每个索引的工作原理 本指南介绍每个索引如何与图表配合使用。 一些术语: Node:对应于 Document 中的一段文本。LlamaIndex 接收 Document 对象,并在内部将它们解析/分块为 Node 对象。Response Synthesis:我们的模块&#xff0…

案例研究丨国控星鲨利用DataEase释放数据潜能,重塑业务视野

国药控股星鲨制药(厦门)有限公司(以下简称为国控星鲨)始创于1952年,前身为厦门鱼肝油厂,距今已经有70余年历史,是国家商务部认定的“中华老字号”企业。2011年,国药控股与厦门轻工集…

ChatGPT Sidebar 浏览器插件配置指南

随着聊天机器人技术的不断进步,越来越多的人开始依赖这些强大的工具来提高工作效率、获取信息和解决问题。OpenAI 的 ChatGPT 是其中最受欢迎的聊天机器人之一。为了方便用户在浏览网页时随时与 ChatGPT 互动,开发者们设计了一款名为 ChatGPT Sidebar 的…

Maven的详细解读和配置

目录 一、Maven 1.1 引言 1.2 介绍 1.3 下载安装 1.3.1 解压 1.3.2 配置环境变量 1.3.3 测试 1.4 仓库[了解] 1.5 Maven配置 1.5.1 修改仓库位置 1.5.2 设置镜像 二、IDEA - MAVEN 2.1 idea关联maven 2.2 为新项目设置 2.2 创建java项目[重点] 2.3 java项目结构…

打靶记录18——narak

靶机: https://download.vulnhub.com/ha/narak.ova 推荐使用 VM Ware 打开靶机 难度:中 目标:取得 root 权限 2 Flag 攻击方法: 主机发现端口扫描信息收集密码字典定制爆破密码Webdav 漏洞PUT 方法上传BF 语言解码MOTD 注入CVE-2021-3…

施耐德EcoStruxure Machine SCADA Expert(EMSE)数据监测-趋势图(十九)

利用EMSE的趋势图控件可实时显示当前的过程监视数据值 1.添加趋势图 2.关连数据库 定义X轴显示时间 3.选择sql表单 4.xy轴设定 5.选择Y轴 6.运行–结合治上一届节的数据监控,可看到趋势图在实时调用数据库内容并显示出来。

如何进行“服务器内部错误”的诊断 | OceanBase诊断案例

本文作者:任仲禹,爱可生数据库高级工程师,擅长故障分析和性能优化。 的OMS迁移工具具备丰富的功能。但在实际运维场景中,我们可能会遇到各种问题,其中“服务器内部错误”便是一个较为棘手的问题,因为界面上…

五子棋双人对战项目(1)——WebSocket介绍

目录 一、项目介绍 如何实现实时同步对局? 二、WebSocket 1、什么是WebSocket? 2、WebSocket的报文格式 opcode payload len payload data 3、WebSocket握手过程 4、WebSocket代码的简单编写 三、WebSocket 和 HTTP的关系 1、相同点&#xf…

【机器学习案列】基于随机森林和xgboost的二手车价格回归预测

一、项目分析 1.1 项目任务 kaggle二手车价格回归预测项目,目的根据各种属性预测二手车的价格。 1.2 评估准则 评估的标准是均方根误差: 1.3 数据介绍 数据连接https://www.kaggle.com/competitions/playground-series-s4e9/data?selecttrain.csv 其…

Linux相关概念和重要知识点(8)(操作系统、进程的概念)

1.操作系统(OS) (1)基本结构的认识 任何计算机系统都包含一个基本的程序集合,用于实现计算机最基本最底层的操作,这个软件称为操作系统。操作系统大部分使用C语言编写,少量使用汇编语言。 从…

即插即用篇 | YOLOv8 引入单头视觉Transformer模块 | CVPR 2024

本改进已同步到YOLO-Magic框架! 最近,高效的视觉Transformer在资源受限的设备上以低延迟表现出了出色的性能。传统上,它们在宏观层面上采用44的Patch嵌入和四阶段结构,而在微观层面上使用多头配置的复杂注意力机制。本文旨在通过内存高效的方式解决各个设计层面的计算冗余问…

[ IDE ] SEGGER Embedded Studio for RISC-V

一、FILE 二、Edit 三、View 四、Search 五、Navigate 六、Project 七、Build 7.1 编译 先选择一个目标类型,再选择编译。 八、Debug​​​​​​​ 九、Target 9.1 烧录代码 十、Tools 10.1 自定义快捷键 点击菜单项,通过Tools –> Options –&g…

软考中级网络工程师选择题

部分参考 软考中级网络工程师全面学习笔记第2版(5万字)配套视频及课件_软考中级网络工程师资料-CSDN博客 1.计算机网络概述 OSI七层模型:物联网淑慧试用 TCP/IP:网网(网际层)传应 高频考点: 中央处理器CPU 固态硬盘…

【rabbitmq-server】安装使用介绍

在 1050a 系统下安装 rabbitmq-server 服务以及基本配置;【注】:改方案用于A版统信服务器操作系统 文章目录 功能概述功能介绍一、安装软件包二、启动服务三、验证四、基本配置功能概述 RabbitMQ 是AMQP的实现,高性能的企业消息的新标准。RabbitMQ服务器是一个强大和可扩展…

【Oauth2整合gateway网关实现微服务单点登录】

文章目录 一.什么是单点登录?二.Oauth2整合网关实现微服务单点登录三.时序图四.代码实现思路1.基于OAuth2独立一个认证中心服务出来2.网关微服务3产品微服务4.订单微服务5.开始测试单点登录 一.什么是单点登录? 单点登录(Single Sign On&…

鸿蒙界面开发(九):列表布局 (List)

列表布局 当列表项达到一定数量,内容超过屏幕大小时,可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集,例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求(如通讯录、音乐列表、购物清单等&#xf…

ArcGIS Desktop使用入门(三)常用工具条——拓扑(下篇:地理数据库拓扑)

系列文章目录 ArcGIS Desktop使用入门(一)软件初认识 ArcGIS Desktop使用入门(二)常用工具条——标准工具 ArcGIS Desktop使用入门(二)常用工具条——编辑器 ArcGIS Desktop使用入门(二&#x…