TiDB分表唯一主键ID——sequence 与gorm无法获取主键的解决

如何在 TiDB 上高效运行序列号生成服务 TiDB 从 v4.0 版本开始正式支持序列功能,而除了序列之外还有多种序列号生成方案,这些方案在没有对 TiDB 优化的时候一般会产生写入热点问题。本文将介绍如何应对写入热点问题高效运行序列号服务。 阅读详情

按理说用了TiDB后就不用去考虑分库分表的问题了,但是由于业务场景上同时也要支持MySQL,且确实数据上存在一定隔离性,因此通过数据类型进行水平分表了。

MySQL水平分表后的主键id通过snowflake来写入,而TiDB的主键id决定通过tidb的sequenceID来实现

Sequence

Sequence 是数据库系统按照一定规则自增的数字序列,具有唯一和单调递增的特性。在官方 SQL 2003 标准中,其被定义为"生成连续数值的一种机制,Sequence 既可以是内部对象,也可以是外部对象"。因为原生 MySQL 中并未支持 Sequence,所以 TiDB Sequence 的语法参考了 MariaDB、Oracle 和 IBM Db2

  • Create Sequence 语法
CREATE [TEMPORARY] SEQUENCE [IF NOT EXISTS] sequence_name 
[ INCREMENT [ BY | = ] INCREMENT ] 
[ MINVALUE [=] minvalue | NO MINVALUE | NOMINVALUE ] 
[ MAXALUE [=] maxvalue | NO MAXVALUE | NOMAXVALUE ] 
[ START [ WITH | = ] start ] 
[ CACHE [=] cache | NOCACHE | NO CACHE] 
[ CYCLE | NOCYCLE | NO CYCLE] 
[ ORDER | NOORDER | NO ORDER] 
[table_options]
  • Show Create Sequence 语法
SHOW CREATE SEQUENCE sequence_name
  • Drop Sequence
DROP [TEMPORARY] SEQUENCE [IF NOT EXISTS] sequence_name
  • 获取下一个值
SELECT NEXT VALUE FOR sequence_name; 
SELECT NEXTVAL(sequence_name);

示例

创建同一结构的三张表,表根据业务类型进行分表

CREATE SEQUENCE seq_for_autoid START WITH 1 INCREMENT BY 1 CACHE 1000 NOCYCLE;

SHOW CREATE SEQUENCE seq_for_autoid;

CREATE TABLE `user_1` ( 
 	`autoid` int(11) DEFAULT nextval(seq_for_autoid),
  `userid` varchar(32) NOT NULL,  
 	PRIMARY KEY (`autoid`) 
);
 
CREATE TABLE user_2 LIKE user_1;
CREATE TABLE user_3 LIKE user_1;
  • 插入测试
INSERT INTO user_1(userid) VALUES ('a1'),('b1'),('c1'),('d1'),('e1'),('f1');
INSERT INTO user_2(userid) VALUES ('a1'),('b1'),('c1'),('d1'),('e1'),('f1');
INSERT INTO user_3(userid) VALUES ('a1'),('b1'),('c1'),('d1'),('e1'),('f1');

user_1表:
img
user_2表:
img
user_3表:
img

gorm的批量插入接口插入,无法返回获取到的主键值
import (
	"github.com/sirupsen/logrus"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
)

type User_1 struct {
	AutoId int    `gorm:"column:autoid;primarykey"`
	UserId string `gorm:"column:userid"`
}

func main() {
	db, err := gorm.Open(mysql.Open("test:test@tcp(127.0.0.1:4000)/apiserver?charset=utf8mb4&parseTime=true&loc=Local")})
	if err != nil {
		return
	}
	var users []User_1
	users = append(users, User_1{UserId: "6"})
	users = append(users, User_1{UserId: "7"})
	users = append(users, User_1{UserId: "8"})
	users = append(users, User_1{UserId: "9"})
	users = append(users, User_1{UserId: "10"})
	db.Debug().Create(&users)
	logrus.Info(users)
}

//运行结果
[14.247ms] [rows:5] INSERT INTO `user_1` (`userid`) VALUES ('6'),('7'),('8'),('9'),('10')
time="2022-04-20T16:47:57+08:00" level=info msg="[{0 6} {0 7} {0 8} {0 9} {0 10}]"
  • 解决方法1:写一个BeforeCreate钩子函数
func (user *User_1) BeforeCreate(tx *gorm.DB) error {
	var sequenceId uint
	if err := tx.Raw("SELECT NEXT VALUE FOR seq_for_autoid;").First(&sequenceId).Error; err != nil {
		return err
	}
	user.AutoId = sequenceId
	return nil
}

func main() {

	db, err := gorm.Open(mysql.Open("test:test@tcp(127.0.0.1:4000)/apiserver?charset=utf8mb4&parseTime=true&loc=Local"), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: false,
		},
	})
	if err != nil {
		return
	}

	//db.AutoMigrate(&User{})

	var users []User_1
	users = append(users, User_1{UserId: "aaa"})
	users = append(users, User_1{UserId: "bbb"})

	db.Debug().Table("user_1").Create(&users)
	db.Debug().Table("user_2").Create(&users)
	db.Debug().Table("user_3").Create(&users)

	logrus.Info(users)
}

//执行结果,但是每次插入都要select一次,写频繁时会很慢
[1.288ms] [rows:1] SELECT NEXT VALUE FOR seq_for_autoid;

[0.999ms] [rows:1] SELECT NEXT VALUE FOR seq_for_autoid;

[15.168ms] [rows:2] INSERT INTO `user_3` (`userid`,`autoid`) VALUES ('aaa',48),('bbb',49)
time="2022-04-20T17:24:14+08:00" level=info msg="[{48 aaa} {49 bbb}]"
  • 解决方法2:批量插入完后查询last_sequence

这种方法只需要查询一次,但是插入和查询lastId一定要放到同一个事务当中,否则会导致lastid获取异常

func main() {

	var users []User
	users = append(users, User{UserId: "oopoo"})
	users = append(users, User{UserId: "nomonno"})

	var lastId uint64
	if err = db.Debug().Transaction(func(tx *gorm.DB) error {
		if err := tx.Table("user_1").Create(&users).Error; err != nil {
			return err
		}
        //这一步查询上一次插入的最后一个sequence_id
		if err := tx.Raw("SELECT lastval(seq_for_autoid);").First(&lastId).Error; err != nil {
			return err
		}
        //手动赋值
		for i := 0; i < len(users); i++ {
			users[len(users)-i-1].AutoId = uint(lastId) - uint(i)
		}
		return nil
	}); err != nil {
		logrus.Error(err)
		return
	}

	logrus.Info(users)
}
TiDB实战篇-表结构设计 阅读详情

相关推荐

推荐一个直接用于项目开发的PID库!很好用,很稳定

关注、星标公众号,不错过精彩内容大家好,我是小麦。最近有朋友问到有没有现成的C语言PID库。当然有了!现在我就准备给大家安利一下了。一般同学会去某度上搜,看到各种各样版本的PID示例,或者...

一名热爱技术的工程师的博客。分享技术干货,记录美好生活。永远相信美好的事情即将发生。 8985

数据分析者必备:TiDB数据库的性能优化和调优实践

作者:禅计算机程序设计艺术 《39. 数据分析者必备: TiDB 数据库的性能优化和调优实践》 1. 引言 1.1. 背景介绍 随着大数据时代的到来,数据已成为企业核心资产之一。数据存储处理

AI天才研究院 4631

Google 二次签名 导致 Facebook , Google 登录失败

google 授权登录

xq610928的博客 1932

Tidb分布式ID

Tidb分布式ID

weixin_42700169的博客 2270

GORM基本使用

GORM创建,更新,删除,查询基本操作。结构体和表字段的对应规则,和自定义规则。软删除的查询。

qq_25100257的博客 2347

Golang基础面试题汇总

channel、goroutine、slice、map、反射、内存、GC、Musql、Redis、ELK、K8s、Http、Https

weixin_41760738的博客 5656

数据库主键一定要自增的吗?有哪些场景下不建议自增?

建表sql里主键边上的,可以让主键自增,去掉它是可以的,但这就需要你在insert的时候自己设置主键的值。建表sql里的是用来声明主键的,如果去掉,那也能建表成功,但mysql内部会给你偷偷建一个ROW_ID的隐藏列作为主键。由于mysql使用B+树索引,叶子节点是从小到大排序的,如果使用自增id主键,这样每次数据都加在B+树的最后,比起每次加在B+树中间的方式,加在最后可以有效减少页分裂的问题。在分库分表的场景下,我们可以通过redis等第三方组件来获得严格自增的主键id

Huangjiazhen711的博客 1424

035、TiDB特性_没有主键的随机ID

【代码】035、TiDB特性_没有主键的随机ID

细致-专业-实操 1109

TiDB库表设计和使用规范

作者: 代晓磊_Mars 原文来源: https://tidb.net/blog/d61d28a8 ...

TiDB 社区干货传送门 1461

TiDB概念

TiDB 会在默认是类似rr级别,但是没有间隙锁的概念。事务是在begin的时候就开启了快照。不是mysql的查询的时候就开启快照了。其他的开启,回滚方式和MySQL一样。包括显示提交隐式提交。惰性检查。类似于乐观锁机制。优化了通过批处理约束检查并减少网络通信来提升性能。tidb_constraint_check_in_place = true 禁用该行为。对于运行不同节点的事务而言,不同事务启动和提交的顺序取决于从PD获取时间戳的顺序。

wangleshisei的博客 385

Mysql, TIDB获取刚刚插入的记录的ID

【代码】Mysql, TIDB获取刚刚插入的记录的ID

justlpf的专栏 443

主键自增的情况下获取主键

文章目录在主键自增的情况下获取主键对于支持主键自增的数据库得到主键主键封装到指定字段举例:对于不支持主键自增的数据库`selectKey order='BEFORE'``selectKey order='AFTER'` 在主键自增的情况下获取主键 对于支持主键自增的数据库 对于支持主键自增的数据库(例:MySQL),可以使用useGenerateKeys和keyProperty来获取主键。 得到主键 在原生的JDBC中,可以使用Statement.getGenerateKeys()方法来获取自增的主键。u

qq2071114140的博客 800

亿级表优化「TIDB 分区篇」,值得收藏

这是亿级别表优化的第二篇,对第一篇感兴趣的可以看看。

pengyachuan的博客 3059

TiDB 分布式数据库中的唯一序列号生成方案详解

在分布式数据库系统中,唯一序列号的生成是一个常见但具有挑战性的需求。本文将深入探讨 TiDB 数据库中的几种唯一序列号生成方案,帮助开发者根据业务场景选择最适合的方法。 ## 自增列方案 ### 基本概念 自增列(auto_increment)是关系型数据库中最基础的序列号生成方式。在 TiDB 中,当表字段设置为自增属性时,数据库会自动为该字段分配递增值。 ### 技术特点 - **简

gitblog_01094的博客 669

TiDB主键锁(primary key lock)问题诊断

作者: 连连看db 原文来源: https://tidb.net/blog/c1abbb6c ...

TiDB 社区干货传送门 1227

TiDB 和 Golang 的简单 CRUD 应用程序

原文来源: https://tidb.net/blog/0b5ef554 ...

TiDB 社区干货传送门 622

043、TiDB特性_缓存表和分区表

针对于优化器在索引存在时依然使⽤全表扫描的情况下,使⽤缓存表和分区表是提升查询性能的有效⼿段。

细致-专业-实操 1168

TIDB 数据库中的两种表结构

TIDB 数据库为表中每一行都分配了一个RowID(也可以叫做 _tidb_rowid).这个Rowid 表示全表内的唯一性,数据库需要将键加上表的TABLE ID 构成:表号+RowID 的唯一键。TIDB 数据库中,非聚簇索引表的RowID 类型是64 位的有符号整数(BIGINT).每一行数据都会有一个。中定位并访问响应的数据。ROWID 在整个集群中是唯一的,主键在表内是唯一的,这些ID 都是Int64类型。一般将数据库中的键值集合称为一个Region,这也是数据库进行读取的逻辑单位。

weixin_43346403的博客 419

The Six Sigma Handbook 3rd edition

关于 6 sigma 方面的权威手册,涵盖了6 sigma 的方方面面,是学习6 sigma 流程的极好教材!

上一篇: TiDB删除大数据的处理
下一篇: golang docker-compose api拉取依赖项报undefined: fileutils.MatchInfo
canger_
博客等级 码龄9年 46粉丝 99原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值