• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    MySQL为id选择合适的数据类型

    选择 id 的数据类型,不仅仅需要考虑数据存储类型,还需要了解 MySQL 对该种类型如何计算和比较。例如,MySQL 将 ENUM 和 SET 类型在内部使用整型存储,但是在字符串场景下会当做字符串进行比较。一旦选择了 id 的数据类型后,需要保证引用 id 的相关数据表的数据类型一致,而且是完全一致,这包括属性,例如长度、是否有符号!如果混用不同的数据类型可能导致性能问题,即便是没有性能问题,在进行比较时的隐式数据转换可能导致难以捉摸的错误。而如果在实际开发过程中忘记了数据类型不同这个问题,可能会突然出现意想不到的问题。

    在选择长度的时候,也需要尽可能选择小的字段长度并给未来留有一定的增长空间。例如,如果是用于存放省份的话,我们只有几十个值,此时使用 TINYINT 就 INT 就更好,如果是相关的表也存有这个 id 的话,那么效率差别会很大。

    下面是适用于 id 的一些典型的类型:

    如果确实要使用 UUID 值,应当移除掉“-”字符,或者是使用 UNHEX 函数将其转换为16字节数字,并使用 BINARY(16)存储。然后可以使用 HEX 函数以十六进制的方式进行获取。UUID 产生的方法有很多,有些是随机分布的,有些是有序的,但是即便是有序的性能也不如整型。

    分布式ID方案总结

    ID是数据的唯一标识,传统的做法是利用UUID和数据库的自增ID,如今MySQL的应用越来越广泛,并且因为需要事务支持,所以通常会使用Innodb存储引擎,UUID太长以及无序,所以并不适合在Innodb中来作为主键,自增ID比较合适,但是业务发展,数据量将越来越大,需要对数据进行分表,而分表后,每个表中的数据都会按自己的节奏进行自增,很有可能出现ID冲突。这时就需要一个单独的机制来负责生成唯一ID,生成出来的ID也可以叫做分布式ID,或全局ID。下面来分析各个生成分布式ID的机制。

    数据库自增ID

    这种方式是基于数据库的自增ID,需要单独使用一个数据库实例,在这个实例中新建一个单独的表:

    表结构如下:

    CREATE DATABASE `SEQID`;
    
    CREATE TABLE SEQID.SEQUENCE_ID (
    	id bigint(20) unsigned NOT NULL auto_increment, 
    	stub char(10) NOT NULL default '',
    	PRIMARY KEY (id),
    	UNIQUE KEY stub (stub)
    ) ENGINE=MyISAM;
    

    可以使用下面的语句生成并获取到一个自增ID

    begin;
    replace into SEQUENCE_ID (stub) VALUES ('anyword');
    select last_insert_id();
    commit;
    

    stub字段在这里并没有什么特殊的意义,只是为了方便的去插入数据,只有能插入数据才能产生自增id。而对于插入我们用的是replace,replace会先看是否存在stub指定值一样的数据,如果存在则先delete再insert,如果不存在则直接insert。

    这种生成分布式ID的机制,需要一个单独的MySQL实例,虽然可行,但是基于性能与可靠性来考虑的话都不够,业务系统每次需要一个ID时,都需要请求数据库获取,性能低,并且如果此数据库实例下线了,那么将影响所有的业务系统。;所以这种方式数据存在一定的不可靠性。

    数据库多主模式

    如果我们两个数据库组成一个主从模式集群,正常情况下可以解决数据库可靠性问题,但是如果主库挂掉后,数据没有及时同步到从库,这个时候会出现ID重复的现象。这是我们可以使用多主模式☞双主模式集群,也就是两个MySQL实例都能单独的生产自增ID,这样能够提高效率,但是如果不经过其他改造的话,这两个MySQL实例很可能会生成同样的ID。需要单独给每个MySQL实例配置不同的起始值和自增步长。

    第一台MySQL实例配置(mysql_01):

    set @@auto_increment_offset = 1;     -- 起始值
    set @@auto_increment_increment = 2;  -- 步长
    

    第二台MySQL实例配置(mysql_02):

    set @@auto_increment_offset = 2;     -- 起始值
    set @@auto_increment_increment = 2;  -- 步长
    

    经过上面的配置后,这两个MySQL实例生成的id序列如下:
    mysql_01:起始值为1,步长为2,ID生成的序列为:1,3,5,7,9,…
    mysql_02:,起始值为2,步长为2,ID生成的序列为:2,4,6,8,10,…

    对于这种生成分布式ID的方案,需要单独新增一个生成分布式ID应用,比如DistributIdService,该应用提供一个接口供业务应用获取ID,业务应用需要一个ID时,通过rpc的方式请求DistributIdService,DistributIdService随机去上面的两个MySQL实例中去获取ID。

    实行这种方案后,就算其中某一台MySQL实例下线了,也不会影响DistributIdService,DistributIdService仍然可以利用另外一台MySQL来生成ID。

    但是这种方案的扩展性不太好,如果两台MySQL实例不够用,需要新增MySQL实例来提高性能时,这时就会比较麻烦。

    现在如果要新增一个实例mysql_03,要怎么操作呢?

    号段模式

    该模式可以理解成批量获取,比如DistributIdService从数据库获取ID时,如果能批量获取多个ID并缓存在本地的话,那样将大大提供业务应用获取ID的效率。

    比如DistributIdService每次从数据库获取ID时,就获取一个号段,比如(1,1000],这个范围表示了1000个ID,业务应用在请求DistributIdService提供ID时,DistributIdService只需要在本地从1开始自增并返回即可,而不需要每次都请求数据库,一直到本地自增到1000时,也就是当前号段已经被用完时,才去数据库重新获取下一号段。

    所以,我们需要对数据库表进行改动,如下:

    CREATE TABLE id_generator (
      id int(10) NOT NULL,
      current_max_id bigint(20) NOT NULL COMMENT '当前最大id',
      increment_step int(10) NOT NULL COMMENT '自增步长',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    这个数据库表用来记录自增步长以及当前自增ID的最大值(也就是当前已经被申请的号段的最后一个值),因为自增逻辑被移到DistributIdService中去了,所以数据库不需要这部分逻辑了。

    这种方案不再强依赖数据库,就算数据库不可用,那么DistributIdService也能继续支撑一段时间。但是如果DistributIdService重启,会丢失一段ID,导致ID空洞。

    为了提高DistributIdService的高可用,需要做一个集群,业务在请求DistributIdService集群获取ID时,会随机的选择某一个DistributIdService节点进行获取,对每一个DistributIdService节点来说,数据库连接的是同一个数据库,那么可能会产生多个DistributIdService节点同时请求数据库获取号段,那么这个时候需要利用乐观锁来进行控制,比如在数据库表中增加一个version字段,在获取号段时使用如下SQL:

    update id_generator set current_max_id=#{newMaxId}, version=version+1 where version = #{version}
    

    因为newMaxId是DistributIdService中根据oldMaxId+步长算出来的,只要上面的update更新成功了就表示号段获取成功了。

    为了提供数据库层的高可用,需要对数据库使用多主模式进行部署,对于每个数据库来说要保证生成的号段不重复,这就需要利用最开始的思路,再在刚刚的数据库表中增加起始值和步长,比如如果现在是两台MySQL,那么:
    mysql_01将生成号段(1,1001],自增的时候序列为1,3,4,5,7…
    mysql_02将生成号段(2,1002],自增的时候序列为2,4,6,8,10…

    具体实现代码可以参照:tinyid

    雪花算法

    数据库自增ID模式、数据库多主模式、号段模式三种方式都是基于自增的思想;下面可以简单理解一下雪花算法的思想。
    snowflake是twitter开源的分布式ID生成算法,是一种算法,所以它和上面的三种生成分布式ID机制不太一样,它不依赖数据库。

    核心思想是:分布式ID固定是一个long型的数字,一个long型占8个字节,也就是64个bit,原始snowflake算法中对于bit的分配如下图:

    根据这个算法的逻辑,只需要将这个算法用Java语言实现出来,封装为一个工具方法,那么各个业务应用可以直接使用该工具方法来获取分布式ID,只需保证每个业务应用有自己的工作机器id即可,而不需要单独去搭建一个获取分布式ID的应用。它也不依赖数据库。

    具体代码实现

    package com.yeming.tinyid.application;
    
    import static java.lang.System.*;
    
    /**
     * @author yeming.gao
     * @Description: 雪花算法实现
     * p>
     * SnowFlake算法用来生成64位的ID,刚好可以用long整型存储,能够用于分布式系统中生产唯一的ID,
     * 并且生成的ID有大致的顺序。 在这次实现中,生成的64位ID可以分成5个部分:
     * 0 - 41位时间戳 - 5位数据中心标识 - 5位机器标识 - 12位序列号
     * @date 2020/07/28 16:15
     */
    public class SnowFlake {
        /**
         * 起始的时间戳
         */
        private static final long START_STMP = 1480166465631L;
    
        /**
         * 机器标识占用的位数
         */
        private static final long MACHINE_BIT = 5;
        /**
         * 数据中心占用的位数
         */
        private static final long DATACENTER_BIT = 5;
        /**
         * 序列号占用的位数
         */
        private static final long SEQUENCE_BIT = 12;
    
        /**
         * 机器标识最大值
         */
        private static final long MAX_MACHINE_NUM = ~(-1L  MACHINE_BIT);
        /**
         * 数据中心最大值
         */
        private static final long MAX_DATACENTER_NUM = ~(-1L  DATACENTER_BIT);
        /**
         * 序列号最大值
         */
        private static final long MAX_SEQUENCE = ~(-1L  SEQUENCE_BIT);
        /**
         * 每一部分向左的位移
         */
        private static final long MACHINE_LEFT = SEQUENCE_BIT;
        private static final long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
        private static final long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
    
        private long datacenterId; //数据中心
        private long machineId; //机器标识
        private long sequence = 0L; //序列号
        private long lastStmp = -1L;//上一次时间戳
    
        private SnowFlake(long datacenterId, long machineId) {
            if (datacenterId > MAX_DATACENTER_NUM || datacenterId  0) {
                throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
            }
            if (machineId > MAX_MACHINE_NUM || machineId  0) {
                throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
            }
            this.datacenterId = datacenterId;
            this.machineId = machineId;
        }
    
        /**
         * 产生下一个ID
         *
         * @return long
         */
        private synchronized long nextId() {
            long currStmp = System.currentTimeMillis();
            if (currStmp  lastStmp) {
                throw new RuntimeException("Clock moved backwards. Refusing to generate id");
            }
            if (currStmp == lastStmp) {
                //相同毫秒内,序列号自增
                sequence = (sequence + 1)  MAX_SEQUENCE;
                //同一毫秒的序列数已经达到最大
                if (sequence == 0L) {
                    currStmp = getNextMill();
                }
            } else {
                //不同毫秒内,序列号置为0
                sequence = 0L;
            }
            lastStmp = currStmp;
            return (currStmp - START_STMP)  TIMESTMP_LEFT //时间戳部分
                    | datacenterId  DATACENTER_LEFT //数据中心部分
                    | machineId  MACHINE_LEFT //机器标识部分
                    | sequence; //序列号部分
        }
    
        private long getNextMill() {
            long mill = System.currentTimeMillis();
            while (mill = lastStmp) {
                mill = System.currentTimeMillis();
            }
            return mill;
        }
    
        public static void main(String[] args) {
            SnowFlake snowFlake = new SnowFlake(2, 3);
            //数据中心标识最大值
            long maxDatacenterNum = ~(-1L  DATACENTER_BIT);
            //机器标识最大值
            long maxMachineNum = ~(-1L  MACHINE_BIT);
            //序列号最大值
            long maxSequence = ~(-1L  SEQUENCE_BIT);
            out.println("数据中心标识最大值:" + maxDatacenterNum + ";机器标识最大值:" + maxMachineNum + ";序列号最大值:" + maxSequence);
            for (int i = 0; i  (1  12); i++) {
                out.println(snowFlake.nextId());
            }
        }
    }
    

    雪花算法可以参照:

    以上就是MySQL为id选择合适的数据类型的详细内容,更多关于MySQL id选择合适的数据类型的资料请关注脚本之家其它相关文章!

    您可能感兴趣的文章:
    • Mysql主键UUID和自增主键的区别及优劣分析
    • MySQL的MaxIdleConns不合理,会变成短连接的原因
    • Mysql根据某层部门ID查询所有下级多层子部门的示例
    • 详解mysql插入数据后返回自增ID的七种方法
    • 使用IDEA配置Tomcat和连接MySQL数据库(JDBC)详细步骤
    • MYSQL数据库GTID实现主从复制实现(超级方便)
    • MySQL的自增ID(主键) 用完了的解决方法
    • JDBC-idea导入mysql连接java的jar包(mac)的方法
    • 深入分析mysql为什么不推荐使用uuid或者雪花id作为主键
    • MySQL如何实现事务的ACID
    • IDEA连接mysql报错的问题及解决方法
    上一篇:MySQL单表千万级数据处理的思路分享
    下一篇:浅谈MySQL next-key lock 加锁范围
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    MySQL为id选择合适的数据类型 MySQL,为,选择,合适,的,数据,