当关系型数据库数据量过大时,通常会采用分库分表降低数据库查表压力。分库分表有多种,有分一个库多张分表额,有分多个库多张表的。一般分库分表使用ShardingSphere分表,建分片键等。但是分库分表之后,主键ID如何处理呢?相同业务表不同分
当关系型数据库数据量过大时,通常会采用分库分表降低数据库查表压力。分库分表有多种,有分一个库多张分表额,有分多个库多张表的。一般分库分表使用ShardingSphere分表,建分片键等。但是分库分表之后,主键ID如何处理呢?相同业务表不同分表的主键ID是不可以相同的。所以要考虑一下主键ID如何赋值的问题。
有以下几种我了解或者涉及到过的主键ID的处理方式:
这种方式一般会将主键设置为bitint类型,自增的。但是会存在一个问题,多张分表保证主键不冲突,因为在业务上来说,多张分表的数据组成某个业务,因此主键是不允许冲突的。
当采用自动生成主键ID的方案时,可以设置固定的几张分表,每个分表的起点不一样,每次新增的步长一样,这样就可以保证每张分表的主键不冲突。
举例,如某张表分表有10张,可以设置每张表的起始主键ID从1到10,每张分表主键ID递增步长为10。
表名 | 起始主键ID | 步长 |
---|---|---|
table_1 | 1 | 10 |
table_2 | 2 | 10 |
table_3 | 3 | 10 |
table_4 | 4 | 10 |
table_5 | 5 | 10 |
table_6 | 6 | 10 |
table_7 | 7 | 10 |
table_8 | 8 | 10 |
table_9 | 9 | 10 |
table_10 | 10 | 10 |
根据上面分表主键递增规律,每张表的行数如下递增
表名 | 第一条数据 | 第二条 | 第三条 | 第四条 | 第五条 |
---|---|---|---|---|---|
table_1 | 1 | 11 | 21 | 31 | 41 |
table_2 | 2 | 12 | 22 | 32 | 42 |
table_3 | 3 | 13 | 23 | 33 | 43 |
table_4 | 4 | 14 | 24 | 34 | 44 |
table_5 | 5 | 15 | 25 | 35 | 45 |
table_6 | 6 | 16 | 26 | 36 | 46 |
table_7 | 7 | 17 | 27 | 37 | 47 |
table_8 | 8 | 18 | 28 | 38 | 48 |
table_9 | 9 | 19 | 29 | 39 | 49 |
table_10 | 10 | 20 | 30 | 40 | 50 |
按照主键递增格式有弊端,即新增表时,不好处理主键逻辑。这种主键ID递增的方式适用于分表比较固定的情况。
uuid获取方式:
String id = UUID.randomUUID();结果:647be5bd-a477-4eff-8e58-99a573bb14ec
在前几年的时候,uuid作为主键的表遍地都是,因为它数据范围之广,用法方便受很多人青睐。但是uuid长度为36位,即使去掉中间的“-”,长度也有32位,因此比较占用存储空间。
因为uuid是无序的,因此新增到数据库时,数据表如果采用btree索引,那么每次新增一条数据都需要重新排序,比较费时间,因此uuid作为分表主键也是不太推荐的。
采用SnowFlake算法生成唯一id,包含时间戳,工作中心id,数据中心id,序列号组成,结构如下:
(1)一位占位符:默认为0。最高位代表正负,1代表负数,0代表正数,默认为正数。
(2)41位时间戳:毫秒级的时间,可以存69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
(3)5位工作中心id:十进制范围在0-31;5位数据中心id:十进制范围在0-31。两个组合在一起最多可以容纳1024个节点。
(4)序列号:占用12bit,最多可以累加到4095。自增值支持同一毫秒内同一个节点可以生成4096个ID,这个值在同一毫秒同一节点上从0开始不断累加。(最大可以支持单节点差不多四百万的并发量)
java一般使用hutool中的IdUtil类生成雪花算法id。以下是代码解析
(1)IdUtil
public class IdUtil { public IdUtil() { } @Deprecated public static Snowflake createSnowflake(long workerId, long datacenterId) { return new Snowflake(workerId, datacenterId); } public static Snowflake getSnowflake(long workerId, long datacenterId) { return (Snowflake) Singleton.get(Snowflake.class, new Object[]{workerId, datacenterId}); } public static Snowflake getSnowflake(long workerId) { return (Snowflake) Singleton.get(Snowflake.class, new Object[]{workerId}); } public static Snowflake getSnowflake() { return (Snowflake) Singleton.get(Snowflake.class, new Object[0]); } public static long getDataCenterId(long maxDatacenterId) { Assert.isTrue(maxDatacenterId > 0L, "maxDatacenterId must be > 0", new Object[0]); //9223372036854775807L转成二进制,由63位的1组成 if (maxDatacenterId == 9223372036854775807L) { --maxDatacenterId; } long id = 1L; byte[] Mac = null; try { mac = NetUtil.getLocalHardwareAddress(); } catch (UtilException var6) { } if (null != mac) { id = (255L & (long) mac[mac.length - 2] | 65280L & (long) mac[mac.length - 1] << 8) >> 6; //取余 id %= maxDatacenterId + 1L; } return id; } public static long getWorkerId(long datacenterId, long maxWorkerId) { StringBuilder mpid = new StringBuilder(); mpid.append(datacenterId); try { mpid.append(RuntimeUtil.getPid()); } catch (UtilException var6) { } return (long) (mpid.toString().hashCode() & '\uffff') % (maxWorkerId + 1L); } public static long getSnowflakeNextId() { return getSnowflake().nextId(); } public static String getSnowflakeNextIdStr() { return getSnowflake().nextIdStr(); }}
(2) Snowflake类(核心类)
public class Snowflake implements Serializable { private static final long serialVersionUID = 1L; public static long DEFAULT_TWEPOCH = 1288834974657L; //时间戳,二进制为41位,对应时间为2010-11-04 09:42:54 public static long DEFAULT_TIME_OFFSET = 2000L; //允许时钟回拨差值,两秒 private static final long WORKER_ID_BITS = 5L; //工作中心位数 private static final long MAX_WORKER_ID = 31L; //最大工作中心id值 private static final long DATA_CENTER_ID_BITS = 5L; //数据中心位数 private static final long MAX_DATA_CENTER_ID = 31L; //最大数据中心id值 private static final long SEQUENCE_BITS = 12L; //序列化位数 private static final long WORKER_ID_SHIFT = 12L; //工作中心移动位数(计算id用) private static final long DATA_CENTER_ID_SHIFT = 17L; //工作中心移动位数((计算id用)计算id用) private static final long TIMESTAMP_LEFT_SHIFT = 22L; //时间戳移动位数 private static final long SEQUENCE_MASK = 4095L; //序列化最大值 private final long twepoch; private final long workerId; private final long dataCenterId; private final boolean useSystemClock; private final long timeOffset; private final long randomSequenceLimit; private long sequence; private long lastTimestamp; public Snowflake() { this(cn.hutool.core.util.IdUtil.getWorkerId(cn.hutool.core.util.IdUtil.getDataCenterId(31L), 31L)); } public Snowflake(long workerId) { this(workerId, IdUtil.getDataCenterId(31L)); } public Snowflake(long workerId, long dataCenterId) { this(workerId, dataCenterId, false); } public Snowflake(long workerId, long dataCenterId, boolean isUseSystemClock) { this((Date) null, workerId, dataCenterId, isUseSystemClock); } public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock) { this(epochDate, workerId, dataCenterId, isUseSystemClock, DEFAULT_TIME_OFFSET); } public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset) { this(epochDate, workerId, dataCenterId, isUseSystemClock, timeOffset, 0L); } public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset, long randomSequenceLimit) { this.sequence = 0L; this.lastTimestamp = -1L; this.twepoch = null != epochDate ? epochDate.getTime() : DEFAULT_TWEPOCH; this.workerId = Assert.checkBetween(workerId, 0L, 31L); this.dataCenterId = Assert.checkBetween(dataCenterId, 0L, 31L); this.useSystemClock = isUseSystemClock; this.timeOffset = timeOffset; this.randomSequenceLimit = Assert.checkBetween(randomSequenceLimit, 0L, 4095L); } public long getWorkerId(long id) { return id >> 12 & 31L; } public long getDataCenterId(long id) { return id >> 17 & 31L; } public long getGenerateDateTime(long id) { return (id >> 22 & 2199023255551L) + this.twepoch; } public synchronized long nextId() { //获取当前时间戳,默认取项目时间 long timestamp = this.genTime(); if (timestamp < this.lastTimestamp) { //校验时间回拨差值是否大于配置的差值,若是,则报错 if (this.lastTimestamp - timestamp >= this.timeOffset) { throw new IllegalStateException(StrUtil.fORMat("Clock moved backwards. Refusing to generate id for {}ms", new Object[]{this.lastTimestamp - timestamp})); } //将上次时间戳赋值给当前时间 timestamp = this.lastTimestamp; } //设置序列化号 if (timestamp == this.lastTimestamp) { //设置序列化号,上次序列化号+1之后与4095进行与计算。 long sequence = this.sequence + 1L & 4095L; if (sequence == 0L) { // 毫秒内序列溢出(序列化号已满,说明当前秒的序列化号都已被占用过) 阻塞到下一个毫秒,获得新的时间戳 timestamp = this.tilNextMillis(this.lastTimestamp); } //记录当前的序列化号 this.sequence = sequence; } else if (this.randomSequenceLimit > 1L) { //当timestamp > this.lastTimestamp且this.randomSequenceLimit > 1L,则随便设置一个不大于等于randomSequenceLimit的值赋予sequence this.sequence = RandomUtil.randomLong(this.randomSequenceLimit); } else { //当timestamp > this.lastTimestamp且this.randomSequenceLimit <= 1时,则默认设置sequence=0 this.sequence = 0L; } //将计算完的时间戳赋予全局变量 this.lastTimestamp = timestamp; //将时间戳(当前时间戳-默认的时间,这样可以时间戳值的范围更大些),数据中心,工作中心,序列化拼接在一起组成id。 return timestamp - this.twepoch << 22 | this.dataCenterId << 17 | this.workerId << 12 | this.sequence; } public String nextIdStr() { return Long.toString(this.nextId()); } private long tilNextMillis(long lastTimestamp) { long timestamp; //计算时间,目的是算出当前时间,最大只能等于传进来的参数时间 for (timestamp = this.genTime(); timestamp == lastTimestamp; timestamp = this.genTime()) { } if (timestamp < lastTimestamp) { throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", new Object[]{lastTimestamp - timestamp})); } else { return timestamp; } } private long genTime() { //SystemClock.now() 获取当前项目计算的时间 //System.currentTimeMillis()获取的是当前系统时间 return this.useSystemClock ? SystemClock.now() : System.currentTimeMillis(); }}
上面是hutool中提供的雪花算法,与原始的雪花算法区别在于,hutool中允许一定范围的时间回拨。
hutool中的雪花算法的优缺点:
优点
(1)按照时间排序,则数据库存储时不需要重复排序变动存储位置。
(2)可使用范围长,时间戳的位数41位,可支持69年。
缺点
(1)允许时针回拨,在某些极端情况下会产生重复id
结语:除了上面的几种id生成算法,当然还有其他的主键id生成算法,具体使用哪种需要根据业务的情况来使用。
来源地址:https://blog.csdn.net/CAT_cwds/article/details/129578564
--结束END--
本文标题: 分库分表如何处理主键ID
本文链接: https://lsjlt.com/news/376351.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-10-23
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
2024-10-22
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0