返回顶部
首页 > 资讯 > 数据库 >分库分表如何处理主键ID
  • 948
分享到

分库分表如何处理主键ID

数据库javamysql 2023-08-20 09:08:20 948人浏览 独家记忆
摘要

当关系型数据库数据量过大时,通常会采用分库分表降低数据库查表压力。分库分表有多种,有分一个库多张分表额,有分多个库多张表的。一般分库分表使用ShardingSphere分表,建分片键等。但是分库分表之后,主键ID如何处理呢?相同业务表不同分

当关系型数据库数据量过大时,通常会采用分库分表降低数据库查表压力。分库分表有多种,有分一个库多张分表额,有分多个库多张表的。一般分库分表使用ShardingSphere分表,建分片键等。但是分库分表之后,主键ID如何处理呢?相同业务表不同分表的主键ID是不可以相同的。所以要考虑一下主键ID如何赋值的问题。
有以下几种我了解或者涉及到过的主键ID的处理方式:

一、自动生成主键ID

这种方式一般会将主键设置为bitint类型,自增的。但是会存在一个问题,多张分表保证主键不冲突,因为在业务上来说,多张分表的数据组成某个业务,因此主键是不允许冲突的。
当采用自动生成主键ID的方案时,可以设置固定的几张分表,每个分表的起点不一样,每次新增的步长一样,这样就可以保证每张分表的主键不冲突。
举例,如某张表分表有10张,可以设置每张表的起始主键ID从1到10,每张分表主键ID递增步长为10。

表名起始主键ID步长
table_1110
table_2210
table_3310
table_4410
table_5510
table_6610
table_7710
table_8810
table_9910
table_101010

根据上面分表主键递增规律,每张表的行数如下递增

表名第一条数据第二条第三条第四条第五条
table_1111213141
table_2212223242
table_3313233343
table_4414243444
table_5515253545
table_6616263646
table_7717273747
table_8818283848
table_9919293949
table_101020304050

按照主键递增格式有弊端,即新增表时,不好处理主键逻辑。这种主键ID递增的方式适用于分表比较固定的情况。

2.UUID做主键

uuid获取方式:

String id = UUID.randomUUID();结果:647be5bd-a477-4eff-8e58-99a573bb14ec

在前几年的时候,uuid作为主键的表遍地都是,因为它数据范围之广,用法方便受很多人青睐。但是uuid长度为36位,即使去掉中间的“-”,长度也有32位,因此比较占用存储空间。
因为uuid是无序的,因此新增到数据库时,数据表如果采用btree索引,那么每次新增一条数据都需要重新排序,比较费时间,因此uuid作为分表主键也是不太推荐的。

3.雪花算法

采用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

猜你喜欢
  • 分库分表如何处理主键ID
    当关系型数据库数据量过大时,通常会采用分库分表降低数据库查表压力。分库分表有多种,有分一个库多张分表额,有分多个库多张表的。一般分库分表使用ShardingSphere分表,建分片键等。但是分库分表之后,主键ID如何处理呢?相同业务表不同分...
    99+
    2023-08-20
    数据库 java mysql
  • 分库分表的分布式主键ID生成方法有哪些
    本篇内容主要讲解“分库分表的分布式主键ID生成方法有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“分库分表的分布式主键ID生成方法有哪些”吧!引入任何一种技...
    99+
    2024-04-02
  • Mysql分库分表之后主键处理的几种方法
    目录数据库自增 ID设置数据库 sequence 或者表自增字段步长UUID系统当前时间戳+XXXSnowflake 算法数据库自增 ID 搞一个数据库,什么也不...
    99+
    2024-04-02
  • 数据库分库分表后如何解决主键唯一的问题
    本篇文章给大家分享的是有关数据库分库分表后如何解决主键唯一的问题,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。在此之前我们介绍了数据库的分库分...
    99+
    2024-04-02
  • 详解分库分表后非分片键如何查询
    目录正文设计一:冗余法方法二:索引表法方法三:基因法小结正文 我们知道在分库分表中对于toC业务来说,需要选择用户属性如 user_id 作为分片键,不推荐使用order_id这样...
    99+
    2023-03-10
    分库分表非分片键查询 非分片键查询
  • 如何使用MyCat分表分库原理分析
    这篇文章给大家分享的是有关如何使用MyCat分表分库原理分析的内容。小编觉得挺实用的,因此分享给大家做个参考。一起跟随小编过来看看吧。MyCat是一个开源的分布式数据库系统,是一个实现了MySQL协议的服务...
    99+
    2024-04-02
  • MySQL如何分库分表
    1. 我们为什么需要分库分表 在分库分表之前,就需要考虑为什么需要拆分。我们做一件事,肯定是有充分理由的。所以得想好分库分表的理由是什么。我们现在就从两个维度去思考它,为什么要分库?为什么要分表? 1.1 为什么要分库 如果业务量剧增,数...
    99+
    2023-08-17
    mysql 数据库
  • 如何进行分库分表中多数据源的事务处理
    这期内容当中小编将会给大家带来有关如何进行分库分表中多数据源的事务处理,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。系统经sharding改造之后,原来单一的数据库会演变...
    99+
    2024-04-02
  • Java如何实现分库分表
    一、为啥要分库分表 在大型互联网系统中,大部分都会选择mysql作为业务数据存储。一般来说,mysql单表行数超过500万行或者单表容量超过2GB,查询效率就会随着数据量的增长而下降。这个时候,就需要对表进行拆分。 那么应该怎么拆分呢? 通...
    99+
    2023-08-31
    java 开发语言
  • mysql分库分表如何实现
    MySQL分库分表可以通过以下几个步骤实现: 水平分库:将原始的单个数据库分成多个独立的数据库。每个数据库可以独立运行在不同的服务...
    99+
    2023-10-27
    mysql
  • 如何在mysql中切分分库分表
    本篇文章为大家展示了如何在mysql中切分分库分表,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。1、水平切分水平切分又称Sharding,是将同一表中的记录分割成多个结构相同的表中。Sharding...
    99+
    2023-06-15
  • 数据库分库分表后非分片键怎么查询
    这篇文章主要介绍“数据库分库分表后非分片键怎么查询”,在日常操作中,相信很多人在数据库分库分表后非分片键怎么查询问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”数据库分库分表后...
    99+
    2023-03-13
    数据库
  • MyBatis如何获取数据库自生成的主键Id
    这篇文章将为大家详细讲解有关MyBatis如何获取数据库自生成的主键Id,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。MyBatis获取数据...
    99+
    2024-04-02
  • MySQL分库分表环境下全局ID生成方案
    MySQL分库分表环境下全局ID生成方案 ...
    99+
    2024-04-02
  • MySQL 分区表,为什么分区键必须是主键的一部分?
    随着业务的不断发展,数据库中的数据会越来越多,相应地,单表的数据量也会越到越大,大到一个临界值,单表的查询性能就会下降。 这个临界值,并不能一概而论,它与硬件能力、具体业务有关。 虽然在很多 MySQL 运维规范里,都建议单表不超过 50...
    99+
    2015-06-16
    MySQL 分区表,为什么分区键必须是主键的一部分?
  • 数据库中如何实现分库分表
    这篇文章将为大家详细讲解有关数据库中如何实现分库分表,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。 分片是解决数据库存储容量限制的直接途径。分片包括垂直分片与水平分片两...
    99+
    2024-04-02
  • mysql数据处理采用的手段:分片分区分库分表
    下文我给大家简单讲讲关于mysql数据处理采用的手段:分片分区分库分表,大家之前了解过相关类似主题内容吗?感兴趣的话就一起来看看这篇文章吧,相信看完mysql数据处理采用的手段:分片分区分库分表对大家多少有...
    99+
    2024-04-02
  • mysql主从同步分库分表同步
    一、mysql数据库的安装分别在master 和slave上源码安装mysql数据库1.1 安装相关包1.1.1 cmake软件cd /home/oldboy/tools/tar xf cmake-2.8....
    99+
    2024-04-02
  • MySQL中如何实现分库分表
    本篇文章为大家展示了MySQL中如何实现分库分表,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、    背景介绍1.大数据...
    99+
    2024-04-02
  • MySQL 分区表中分区键为什么必须是主键的一部分
    目录水平拆分 VS 垂直拆分分区表MySQL 8.0 中分区表的变化为什么分区键必须是主键的一部分?本地分区索引 VS 全局索引总结前言: 分区是一种表的设计模式,通俗地讲表分区是将...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作