分库分表如何处理主键ID

news2024/12/23 10:30:28

当关系型数据库数据量过大时,通常会采用分库分表降低数据库查表压力。分库分表有多种,有分一个库多张分表额,有分多个库多张表的。一般分库分表使用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 创建Snowflake对象,已废弃 */
    @Deprecated
    public static Snowflake createSnowflake(long workerId, long datacenterId) {
        return new Snowflake(workerId, datacenterId);
    }

    /**
     * 根据工作中心id和数据中心id获取数据
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    public static Snowflake getSnowflake(long workerId, long datacenterId) {
        return (Snowflake) Singleton.get(Snowflake.class, new Object[]{workerId, datacenterId});
    }

    /**
     * 根据工作中心id获取数据
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    public static Snowflake getSnowflake(long workerId) {
        return (Snowflake) Singleton.get(Snowflake.class, new Object[]{workerId});
    }

    /**
     * 无参获取Snowflake
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    public static Snowflake getSnowflake() {
        return (Snowflake) Singleton.get(Snowflake.class, new Object[0]);
    }

    /**
     * 根据数据中心id计算算法id中的数据中心值
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    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;
    }

    /**
     * 获取算法id中的工作中心id
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    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);
    }

    /**
     * 无参获取算法id
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    public static long getSnowflakeNextId() {
        return getSnowflake().nextId();
    }

    /**
     * 无参获取字符串类型算法id
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    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;

    /**
     * 无参构造
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    public Snowflake() {
        this(cn.hutool.core.util.IdUtil.getWorkerId(cn.hutool.core.util.IdUtil.getDataCenterId(31L), 31L));
    }

    /**
     * 根据工作中心id构造
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    public Snowflake(long workerId) {
        this(workerId, IdUtil.getDataCenterId(31L));
    }

    /**
     * 根据工作中心id和数据中心id构造
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    public Snowflake(long workerId, long dataCenterId) {
        this(workerId, dataCenterId, false);
    }

    /**
     * 参数:工作中心id、数据中心id、是否采用系统时间
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    public Snowflake(long workerId, long dataCenterId, boolean isUseSystemClock) {
        this((Date) null, workerId, dataCenterId, isUseSystemClock);
    }

    /**
     * 参数:时间,工作中心id、数据中心id、是否采用系统时间
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock) {
        this(epochDate, workerId, dataCenterId, isUseSystemClock, DEFAULT_TIME_OFFSET);
    }

    /**
     * 参数:工作中心id、数据中心id、是否采用系统时间, 允许时针回拨的时长
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset) {
        this(epochDate, workerId, dataCenterId, isUseSystemClock, timeOffset, 0L);
    }

    /**
     * 参数:工作中心id、数据中心id、是否采用系统时间, 允许时针回拨的时长,序列化
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    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);
    }

    /**
     * 获取工作中心id
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    public long getWorkerId(long id) {
        return id >> 12 & 31L;
    }

    /**
     * 获取数据中心id
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    public long getDataCenterId(long id) {
        return id >> 17 & 31L;
    }

    /**
     * 获取时间戳
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    public long getGenerateDateTime(long id) {
        return (id >> 22 & 2199023255551L) + this.twepoch;
    }

    /**
     * 获取id
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    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;
    }

    /**
     * 获取字符串算法id
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    public String nextIdStr() {
        return Long.toString(this.nextId());
    }

    /**
     * 计算下一个时间
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    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;
        }
    }

    /**
     * 生成时间戳
     *
     * @author zhouxy
     * @date 2023/5/12
     */
    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生成算法,具体使用哪种需要根据业务的情况来使用。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Goby 漏洞更新 |Telesquare TLR-2005Ksh 路由器 getUsernamePassword 信息泄露漏洞

漏洞名称&#xff1a;Telesquare TLR-2005Ksh 路由器 getUsernamePassword 信息泄露漏洞 English Name&#xff1a;Telesquare TLR-2005Ksh getUsernamePassword Information Disclosure CVSS core: 9.0 影响资产数&#xff1a;25826 漏洞描述&#xff1a; Telesquare Tlr…

MATLAB--控制语句--数组操作--符号运算--绘图--文件和数据的处理

控制语句 MATLAB 常用的控制语句有for, while, if, switch 等。 一、循环语句 通过循环控制语句&#xff0c;可以重复执行代码块。循环有两类&#xff1a;for 循环和while 循环。 for 循环是一组语句重复固定的&#xff0c;指定的次数&#xff0c;while 循环是满足条件执行。…

【鸿蒙应用ArkTS开发系列】- http网络库使用讲解和封装

目录 前言http网络库组件介绍http网络库封装创建Har Module创建RequestOption 配置类创建HttpCore核心类创建HttpManager核心类对外组件导出添加网络权限 http网络库依赖和使用依赖http网络库&#xff08;httpLibrary&#xff09;使用http网络库&#xff08;httpLibrary&#x…

“虐人的”双亲委派机制

这些问题&#xff0c;看看你能回答上来多少个&#xff1a; 1、什么是双亲委派&#xff1f; 2、为什么需要双亲委派&#xff0c;不委派有什么问题&#xff1f; 3、”父加载器”和”子加载器”之间的关系是继承的吗&#xff1f; 4、双亲委派是怎么实现的&#xff1f; 5、我能不能…

国外空间服务器是否有利于SEO优化?

​  购买国外空间服务器&#xff0c;这是许多人在做网站时会考虑到的选择。在国内&#xff0c;由于网络环境和限制&#xff0c;所以选择国外的空间似乎是一个不错的选择。但是&#xff0c;国外空间服务器是否对SEO优化有影响?这是一个值得讨论的问题。 1.服务器响应速度 如果…

跟着LearnOpenGL学习4--着色器

文章目录 一、前言二、GLSL2.1、着色器结构2.2、数据类型2.2.1、向量 2.3、输入与输出2.4、uniform2.5、更多属性 三、着色器类 一、前言 在之前绘制三角形的博文中&#xff0c;我们已经接触到了着色器&#xff0c;但是肯定有许多疑问&#xff0c;本文来详细了解一下着色器&am…

【17】SCI易中期刊推荐——计算机信息系统电子与电气(中科院4区)

💖💖>>>加勒比海带,QQ2479200884<<<💖💖 🍀🍀>>>【YOLO魔法搭配&论文投稿咨询】<<<🍀🍀 ✨✨>>>学习交流 | 温澜潮生 | 合作共赢 | 共同进步<<<✨✨ 📚📚>>>人工智能 | 计算机视觉…

Flink从入门到精通之-09状态编程

Flink从入门到精通之-09状态编程 Flink 处理机制的核心&#xff0c;就是“有状态的流式计算”。我们在之前的章节中也已经多次提到了“状态”&#xff08;state&#xff09;&#xff0c;不论是简单聚合、窗口聚合&#xff0c;还是处理函数的应用&#xff0c;都会有状态的身影出…

Spring Cloud第二季--Spring Cloud Bus

文章目录 Spring Clud Bus什么是总线基本原理 牛刀小试 Spring Clud Bus 在Spring Cloud学习–配置中心&#xff08;Config&#xff09;中实现了集中管理微服务配置、不同环境不同配置、运行期间也可动态调整、配置修改后可以自动更新的需求&#xff0c;但同时也有一个弊端&am…

服务器电源线:同为科技(TOWE)机房工程专用电源延长线

工程机房电源延长线 众所周知&#xff0c;世界上不同国家或地区所使用的插头、插座标准有所不同&#xff0c;在庞大复杂的数据中心计算集群内&#xff0c;需要电源供电才能正常工作&#xff0c;因此&#xff0c;服务器电源线是连接电源分配器和服务器的基本配件。在机房服务器…

如何通过优化服务器提升网站的SEO排名

在当今数字化时代&#xff0c;拥有一个高效、稳定的服务器对于网站的成功至关重要。然而&#xff0c;服务器不仅仅是为了提供网站的基本运行&#xff0c;它还可以对搜索引擎优化(SEO)起到关键作用&#xff0c;于是就有了多IP站群服务器这样对SEO非常友好的服务器。通过优化服务…

Goby 漏洞更新 |Telesquare TLR-2005Ksh 路由器 setSyncTimeHost 命令执行漏洞

漏洞名称&#xff1a;Telesquare TLR-2005Ksh 路由器 setSyncTimeHost 命令执行漏洞 English Name&#xff1a;Telesquare TLR-2005Ksh setSyncTimeHost RCE CVSS core: 9.8 影响资产数&#xff1a;25826 漏洞描述&#xff1a; Telesquare Tlr-2005Ksh是韩国Telesquare公司…

5月12日作业

作业1&#xff1a; 作业2&#xff1a;

make_shared知识点

背景 普通创建shared_ptr的方法如&#xff1a; shared_ptr<int> sp1(new int(11));sp1所开辟的动态内存分为如下两部分 uses是持有该资源shared_ptr数量&#xff0c;weaks表示持有该资源weak_ptr数量。 有可能出现 new int(10) 成功&#xff0c;但是引用计数 ref 的内…

应用程序服务器监控

什么是应用程序服务器监视 为了确保业务应用程序的最佳性能&#xff0c;必须使用应用程序服务器监视工具&#xff0c;以深入了解应用程序的运行状况和正常运行时间。应用程序服务器监视可帮助您识别性能不足的服务器组件以及性能问题的根本原因&#xff0c;修复它们并确保应用…

IDEA 导入 spring 源码

文章目录 前言一、下载源码二、安装 Gragle1. 下载 Gragle2. 配置环境变量 三、导入前准备四、编译源码1. 导入源码2. 我所遇见的问题 五、测试1. 创建 module2. 编写测试代码3. 我所遇到的问题 六、总结 前言 我们在学习 spring 源码的时候&#xff0c;有时候是需要在阅读源码…

【Linux Network】网络版计算器

目录 实验目标&#xff1a; 源代码&#xff1a; 实验结果&#xff1a; Linux网络编程✨ 实验目标&#xff1a; 制作一个应用层的简易版的计算器&#xff08;加、减、乘、除、取余&#xff09;&#xff1b; 源代码&#xff1a; makefile .PHONY:all all:CalClient CalServe…

国产仪器 6592A便携式高精度光伏电池伏安特性测试仪

6592A便携式高精度光伏电池伏安特性测试仪&#xff0c;主要用于室外太阳能电池阵列/组件/电池片伏安特性测试&#xff0c;能够方便、快速的测试太阳能电池阵列/组件/电池片在自然光照下的工作特性&#xff0c;可为太阳能电站设计、验收、维护提供测试保障&#xff0c;是电站建设…

sql进阶—— 查询重复数据 查询连续数据

目录 sql语句查询重复的数据 MYSQL 连续性问题求解 binlog 索引 sql语句查询重复的数据 查找所有重复 [标题] 的记录&#xff1a;SELECT * FROM t_info a WHERE ((SELECT COUNT(*) FROM t_info WHERE Title a.Title) > 1) ORDER BY Title DESC删除重复记录1。删除全部…

(9)Qt---网络编程(半双工通信)

目录 1. 复习 1.1 UDP 与TCP 1.2 IP地址与端口号 2. 前期准备 3. 编程内容 1. 复习 1.1 UDP 与TCP UDP TCP 协议相同点&#xff1a;都存在于传输层 TCP&#xff08;即传输控制协议&#xff09;&#xff1a; 是一种面向连接的传输层协议&#xff0c;它能提供高可靠性通信(即…