?
測试缘由
?
一个开发同事做了一个框架。里面主键是uuid。我跟他建议说mysql不要用uuid用自增主键,自增主键效率高,他说不一定高,我说innodb的索引特性导致了自增id做主键是效率最好的,为了拿实际的案例来说服他,所以准备做一个具体的測试。
?
作为互联网公司,一定实用户表,并且用户表UC_USER基本会有百万记录,所以在这个表基础上准測试数据来进行測试。
?
???????? 測试过程是眼下我想到的多方位的经常使用的几种类型的sql进行測试。当然可能不太完好。欢迎大家留言提出更加完好的測试方案或者測试sql语句。
?
?
?
1、准备表以及数据
UC_USER,自增ID为主键,表结构相似例如以下:
CREATE?TABLE?`UC_USER`?( |
?
?
?
UC_USER_PK_VARCHAR表,字符串ID为主键,採用uuid
CREATE?TABLE?`UC_USER_PK_VARCHAR_1`?( |
?
?
?
?
2、500W数据測试
2.1 录入500W数据。自增ID节省一半磁盘空间
确定两个表数据量
# 自增id为主键的表 mysql> select count(1) from UC_USER; +----------+ | count(1) | +----------+ |? 5720112 | +----------+ 1 row in set (0.00 sec) ? mysql> ? # uuid为主键的表 mysql> select count(1) from UC_USER_PK_VARCHAR_1; +----------+ | count(1) | +----------+ |? 5720112 | +----------+ 1 row in set (1.91 sec) |
?
占领的空间容量来看,自增ID比UUID小一半左右。
主键类型 | 数据文件大小 | 占领容量 |
自增ID | -rw-rw---- 1 mysql mysql 2.5G Aug 11 18:29 UC_USER.ibd | 2.5 G |
UUID | -rw-rw---- 1 mysql mysql 5.4G Aug 15 15:11 UC_USER_PK_VARCHAR_1.ibd | 5.4 G |
?
?
?
2.2 单个数据走索引查询,自增id和uuid相差不大
主键类型 | SQL语句 | 运行时间 (秒) |
自增ID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER` t WHERE t.`MOBILE` =‘14782121512‘; | 0.118 |
? | ? | ? |
UUID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_PK_VARCHAR_1` t WHERE t.`MOBILE` =‘14782121512‘; | 0.117 |
? | ? | ? |
自增ID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER` t WHERE t.`MOBILE` IN( ‘14782121512‘,‘13761460105‘); | 0.049 |
UUID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_PK_VARCHAR_1` t WHERE t.`MOBILE` IN(‘14782121512‘,‘13761460105‘); | 0.040 |
? | ? | ? |
自增ID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER` t WHERE t.`CREATE_DATE`=‘2013-11-24 10:26:36‘ ; | 0.139 |
UUID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_PK_VARCHAR_1` t WHERE t.`CREATE_DATE`=‘2013-11-24 10:26:43‘ ; | 0.126 |
?
?
?
2.3 范围like查询,自增ID性能优于UUID
主键类型 | SQL语句 | 运行时间 (秒) |
? (1)模糊范围查询1000条数据,自增ID性能要好于UUID | ||
自增ID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER` t WHERE t.`MOBILE` LIKE ‘147%‘ LIMIT 1000; | 1.784 |
UUID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_PK_VARCHAR_1` t WHERE t.`MOBILE` LIKE ‘147%‘ LIMIT 1000; | 3.196 |
? (2)日期范围查询20条数据,自增ID略微弱于UUID | ||
自增ID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER` t WHERE t.`CREATE_DATE` > ‘2016-08-01 10:26:36‘ ORDER BY t.`UPDATE_DATE` DESC LIMIT 20; | 0.601 |
UUID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_PK_VARCHAR_1` t WHERE t.`CREATE_DATE` > ‘2016-08-01 10:26:36‘ ORDER BY t.`UPDATE_DATE` DESC LIMIT 20; | 0.543 |
? (3)范围查询200条数据,自增ID性能要好于UUID | ||
自增ID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER` t WHERE t.`CREATE_DATE` > ‘2016-07-01 10:26:36‘ ORDER BY t.`UPDATE_DATE` DESC LIMIT 200; | 2.314 |
UUID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_PK_VARCHAR_1` t WHERE t.`CREATE_DATE` > ‘2016-07-01 10:26:36‘ ORDER BY t.`UPDATE_DATE` DESC LIMIT 200; | 3.229 |
? 范围查询总数量。自增ID要好于UUID | ||
自增ID | SELECT SQL_NO_CACHE COUNT(1) FROM test.`UC_USER` t WHERE t.`CREATE_DATE` > ‘2016-07-01 10:26:36‘? ; | 0.514 |
UUID | SELECT SQL_NO_CACHE COUNT(1) FROM test.`UC_USER_PK_VARCHAR_1` t WHERE t.`CREATE_DATE` > ‘2016-07-01 10:26:36‘? ; | 1.092 |
?
?
?
PS:在有缓存的情况下。两者运行效率没有相差非常小。
?
?
?
2.4 写入測试。自增ID是UUID的4倍
主键类型 | SQL语句 | 运行时间 (秒) |
? ? | ? ? | ? |
自增ID | UPDATE test.`UC_USER` t SET t.`MOBILE_TGC`=‘T2‘ WHERE t.`CREATE_DATE` > ‘2016-05-03 10:26:36‘ AND t.`CREATE_DATE` <‘2016-05-04 00:00:00‘? ; | 1.419 ? |
UUID | UPDATE test.`UC_USER_PK_VARCHAR_1` t SET t.`MOBILE_TGC`=‘T2‘ WHERE t.`CREATE_DATE` > ‘2016-05-03 10:26:36‘ AND t.`CREATE_DATE` <‘2016-05-04 00:00:00‘? ; | 5.639 |
? | ? | ? |
自增ID | INSERT INTO test.`UC_USER`(?? ID,?? `USER_NAME`,?? `USER_PWD`,?? `BIRTHDAY`,?? `NAME`,?? `USER_ICON`,?? `SEX`,?? `NICKNAME`,?? `STAT`,?? `USER_MALL`,?? `LAST_LOGIN_DATE`,?? `LAST_LOGIN_IP`,?? `SRC_OPEN_USER_ID`,?? `EMAIL`,?? `MOBILE`,?? `IS_DEL`,?? `IS_EMAIL_CONFIRMED`,?? `IS_PHONE_CONFIRMED`,?? `CREATER`,?? `CREATE_DATE`,?? `UPDATE_DATE`,?? `PWD_INTENSITY`,?? `MOBILE_TGC`,?? `MAC`,?? `SOURCE`,?? `ACTIVATE`,?? `ACTIVATE_TYPE` ) SELECT?????? NULL,??? CONCAT(‘110‘,`USER_NAME`,8),?? `USER_PWD`,?? `BIRTHDAY`,?? `NAME`,?? `USER_ICON`,?? `SEX`,?? `NICKNAME`,?? `STAT`,?? `USER_MALL`,?? `LAST_LOGIN_DATE`,?? `LAST_LOGIN_IP`,?? `SRC_OPEN_USER_ID`,?? `EMAIL`, CONCAT(‘110‘,TRIM(`MOBILE`)),?? `IS_DEL`,?? `IS_EMAIL_CONFIRMED`,?? `IS_PHONE_CONFIRMED`,?? `CREATER`,?? `CREATE_DATE`,?? `UPDATE_DATE`,?? `PWD_INTENSITY`,?? `MOBILE_TGC`,?? `MAC`,?? `SOURCE`,?? `ACTIVATE`,?? `ACTIVATE_TYPE` FROM `test`.`UC_USER_1` LIMIT 100; | 0.105 |
UUID | INSERT INTO test.`UC_USER_PK_VARCHAR_1`(??? ID,?? `USER_NAME`,?? `USER_PWD`,?? `BIRTHDAY`,?? `NAME`,?? `USER_ICON`,?? `SEX`,?? `NICKNAME`,?? `STAT`,?? `USER_MALL`,?? `LAST_LOGIN_DATE`,?? `LAST_LOGIN_IP`,?? `SRC_OPEN_USER_ID`,?? `EMAIL`,?? `MOBILE`,?? `IS_DEL`,?? `IS_EMAIL_CONFIRMED`,?? `IS_PHONE_CONFIRMED`,?? `CREATER`,?? `CREATE_DATE`,?? `UPDATE_DATE`,?? `PWD_INTENSITY`,?? `MOBILE_TGC`,?? `MAC`,?? `SOURCE`,?? `ACTIVATE`,?? `ACTIVATE_TYPE` ) SELECT???????? UUID(),?? CONCAT(‘110‘,`USER_NAME`,8),?? `USER_PWD`,?? `BIRTHDAY`,?? `NAME`,?? `USER_ICON`,?? `SEX`,?? `NICKNAME`,?? `STAT`,?? `USER_MALL`, ??`LAST_LOGIN_DATE`,?? `LAST_LOGIN_IP`,?? `SRC_OPEN_USER_ID`,?? `EMAIL`, CONCAT(‘110‘,TRIM(`MOBILE`)),?? `IS_DEL`,?? `IS_EMAIL_CONFIRMED`,?? `IS_PHONE_CONFIRMED`,?? `CREATER`,?? `CREATE_DATE`,?? `UPDATE_DATE`,?? `PWD_INTENSITY`,?? `MOBILE_TGC`,?? `MAC`,?? `SOURCE`,?? `ACTIVATE`,?? `ACTIVATE_TYPE` FROM `test`.`UC_USER_1` LIMIT 100; | 0.424 |
?
?
?
2.5、备份和恢复,自增ID性能优于UUID
主键类型 | SQL语句 | 运行时间 (秒) |
? Mysqldump备份 | ||
自增ID | time mysqldump -utim -ptimgood -h192.168.121.63 test UC_USER_500> UC_USER_500.sql | 28.59秒 |
UUID | time mysqldump -utim -ptimgood -h192.168.121.63 test UC_USER_PK_VARCHAR_500> UC_USER_PK_VARCHAR_500.sql | 31.08秒 |
? MySQL恢复 | ||
自增ID | time mysql? -utim -ptimgood -h192.168.121.63? test < UC_USER_500.sql | 7m36.601s |
UUID | time mysql? -utim -ptimgood -h192.168.121.63? test < UC_USER_PK_VARCHAR_500.sql | 9m42.472s |
? | ? | ? |
?
?
?
?
3、500W总结
在500W记录表的測试下:
(1)??????普通单条或者20条左右的记录检索,uuid为主键的相差不大差点儿效率同样;
(2)??????可是范围查询特别是上百成千条的记录查询,自增id的效率要大于uuid。
(3)??????在范围查询做统计汇总的时候,自增id的效率要大于uuid;
(4)??????在存储上面,自增id所占的存储空间是uuid的1/2;
(5)??????在备份恢复上,自增ID主键略微优于UUID。
?
?
?
4、1000W数据測试
4.1 录入1000W数据记录,看存储空间
# 自增id为主键的表 mysql> use test; Database changed mysql> select count(1) from UC_USER_1; +----------+ | count(1) | +----------+ | 10698102 | +----------+ 1 row in set (27.42 sec) ? # uuid为主键的表 mysql> select count(1) from UC_USER_PK_VARCHAR_1; +----------+ | count(1) | +----------+ | 10698102 | +----------+ 1 row in set (0.00 sec) ? mysql> |
?
?
占领的空间容量来看。自增ID比UUID小一半左右:
主键类型 | 数据文件大小 | 占领容量? |
自增ID | -rw-rw---- 1 mysql mysql 4.2G Aug 20 23:08 UC_USER_1.ibd | 4.2 G |
UUID | -rw-rw---- 1 mysql mysql 8.8G Aug 20 18:20 UC_USER_PK_VARCHAR_1.ibd | 8.8 G |
?
?
?
4.2 单个数据走索引查询。自增id和 uuid效率比是:(2~3):1
主键类型 | SQL语句 | 运行时间 (秒) |
? 单条记录查询 | ||
自增ID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_1` t WHERE t.`MOBILE` =‘14782121512‘; | 0.069 |
UUID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_PK_VARCHAR_1` t WHERE t.`MOBILE` =‘14782121512‘; | 0.274 |
? 小范围查询 | ||
自增ID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_1` t WHERE t.`MOBILE` IN( ‘14782121512‘,‘13761460105‘); | 0.050 |
UUID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_PK_VARCHAR_1` t WHERE t.`MOBILE` IN(‘14782121512‘,‘13761460105‘); | 0.151 |
? 依据日期查询 | ||
自增ID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_1` t WHERE t.`CREATE_DATE`=‘2013-11-24 10:26:36‘ ; | 0.269 |
UUID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_PK_VARCHAR_1` t WHERE t.`CREATE_DATE`=‘2013-11-24 10:26:43‘ ; | 0.810 |
?
?
?
4.3 范围like查询,自增ID性能优于UUID,比值(1.5~2):1
主键类型 | SQL语句 | 运行时间 (秒) |
? (1)模糊范围查询1000条数据,自增ID性能要好于UUID | ||
自增ID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER` t WHERE t.`MOBILE` LIKE ‘147%‘ LIMIT 1000; | 2.398 |
UUID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_PK_VARCHAR_1` t WHERE t.`MOBILE` LIKE ‘147%‘ LIMIT 1000; | 5.872 |
? (2)日期范围查询20条数据。自增ID略微弱于UUID | ||
自增ID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_1` t WHERE t.`CREATE_DATE` > ‘2016-08-01 10:26:36‘ ORDER BY t.`UPDATE_DATE` DESC LIMIT 20; | 0.765 |
UUID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_PK_VARCHAR_1` t WHERE t.`CREATE_DATE` > ‘2016-08-01 10:26:36‘ ORDER BY t.`UPDATE_DATE` DESC LIMIT 20; | 1.090 |
? (3)范围查询200条数据,自增ID性能要好于UUID | ||
自增ID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_1` t WHERE t.`CREATE_DATE` > ‘2016-07-01 10:26:36‘ ORDER BY t.`UPDATE_DATE` DESC LIMIT 200; | 1.569 |
UUID | SELECT SQL_NO_CACHE t.* FROM test.`UC_USER_PK_VARCHAR_1` t WHERE t.`CREATE_DATE` > ‘2016-07-01 10:26:36‘ ORDER BY t.`UPDATE_DATE` DESC LIMIT 200; | 2.597 |
? 范围查询总数量。自增ID要好于UUID | ||
自增ID | SELECT SQL_NO_CACHE COUNT(1) FROM test.`UC_USER_1` t WHERE t.`CREATE_DATE` > ‘2016-07-01 10:26:36‘? ; | 1.129 |
UUID | SELECT SQL_NO_CACHE COUNT(1) FROM test.`UC_USER_PK_VARCHAR_1` t WHERE t.`CREATE_DATE` > ‘2016-07-01 10:26:36‘? ; | 2.302 |
?
?
?
4.4 写入測试,自增ID比UUID效率高,比值(3~10):1
主键类型 | SQL语句 | 运行时间 (秒) |
? ? 改动一天的记录 | ||
自增ID | UPDATE test.`UC_USER_1` t SET t.`MOBILE_TGC`=‘T2‘ WHERE t.`CREATE_DATE` > ‘2016-05-03 10:26:36‘ AND t.`CREATE_DATE` <‘2016-05-04 00:00:00‘? ; | 2.685 |
UUID | UPDATE test.`UC_USER_PK_VARCHAR_1` t SET t.`MOBILE_TGC`=‘T2‘ WHERE t.`CREATE_DATE` > ‘2016-05-03 10:26:36‘ AND t.`CREATE_DATE` <‘2016-05-04 00:00:00‘? ; | 26.521 |
? 录入数据 | ||
自增ID | INSERT INTO test.`UC_USER_1`(?? ID,?? `USER_NAME`,?? `USER_PWD`,?? `BIRTHDAY`,?? `NAME`,?? `USER_ICON`,?? `SEX`,?? `NICKNAME`,?? `STAT`,?? `USER_MALL`,?? `LAST_LOGIN_DATE`,?? `LAST_LOGIN_IP`,?? `SRC_OPEN_USER_ID`,?? `EMAIL`,?? `MOBILE`,?? `IS_DEL`,?? `IS_EMAIL_CONFIRMED`,?? `IS_PHONE_CONFIRMED`,?? `CREATER`,?? `CREATE_DATE`,?? `UPDATE_DATE`,?? `PWD_INTENSITY`,?? `MOBILE_TGC`,?? `MAC`,?? `SOURCE`,?? `ACTIVATE`,?? `ACTIVATE_TYPE` ) SELECT?????? NULL,??? CONCAT(‘110‘,`USER_NAME`,8),?? `USER_PWD`,?? `BIRTHDAY`,?? `NAME`,?? `USER_ICON`,?? `SEX`,?? `NICKNAME`,?? `STAT`,?? `USER_MALL`,?? `LAST_LOGIN_DATE`,?? `LAST_LOGIN_IP`,?? `SRC_OPEN_USER_ID`,?? `EMAIL`, CONCAT(‘110‘,TRIM(`MOBILE`)),?? `IS_DEL`,?? `IS_EMAIL_CONFIRMED`,?? `IS_PHONE_CONFIRMED`,?? `CREATER`,?? `CREATE_DATE`,?? `UPDATE_DATE`,?? `PWD_INTENSITY`,?? `MOBILE_TGC`,?? `MAC`,?? `SOURCE`,?? `ACTIVATE`,?? `ACTIVATE_TYPE` FROM `test`.`UC_USER_1` LIMIT 100; | 0.534 |
UUID | INSERT INTO test.`UC_USER_PK_VARCHAR_1`(??? ID,?? `USER_NAME`,?? `USER_PWD`,?? `BIRTHDAY`,?? `NAME`,?? `USER_ICON`,?? `SEX`,?? `NICKNAME`,?? `STAT`,?? `USER_MALL`,?? `LAST_LOGIN_DATE`,?? `LAST_LOGIN_IP`,?? `SRC_OPEN_USER_ID`,?? `EMAIL`,?? `MOBILE`,?? `IS_DEL`,?? `IS_EMAIL_CONFIRMED`,?? `IS_PHONE_CONFIRMED`,?? `CREATER`,?? `CREATE_DATE`,?? `UPDATE_DATE`,?? `PWD_INTENSITY`,?? `MOBILE_TGC`,?? `MAC`,?? `SOURCE`,?? `ACTIVATE`,?? `ACTIVATE_TYPE` ) SELECT???????? UUID(),?? CONCAT(‘110‘,`USER_NAME`,8),?? `USER_PWD`,?? `BIRTHDAY`,?? `NAME`,?? `USER_ICON`,?? `SEX`,?? `NICKNAME`,?? `STAT`,?? `USER_MALL`,?? `LAST_LOGIN_DATE`,?? `LAST_LOGIN_IP`,?? `SRC_OPEN_USER_ID`,?? `EMAIL`, CONCAT(‘110‘,TRIM(`MOBILE`)),?? `IS_DEL`,?? `IS_EMAIL_CONFIRMED`,?? `IS_PHONE_CONFIRMED`,?? `CREATER`,?? `CREATE_DATE`,?? `UPDATE_DATE`,?? `PWD_INTENSITY`,?? `MOBILE_TGC`,?? `MAC`,?? `SOURCE`,?? `ACTIVATE`,?? `ACTIVATE_TYPE` FROM `test`.`UC_USER_1` LIMIT 100; | 1.716 |
?
?
?
4.5、备份和恢复,自增ID性能优于UUID
主键类型 | SQL语句 | 运行时间 (秒) |
? Mysqldump备份 | ||
自增ID | time mysqldump -utim -ptimgood -h192.168.121.63 test UC_USER_1> UC_USER_1.sql | 0m50.548s |
UUID | time mysqldump -utim -ptimgood -h192.168.121.63 test UC_USER_PK_VARCHAR_1> UC_USER_PK_VARCHAR_1.sql | 0m58.590s |
? MySQL恢复 | ||
自增ID | time mysql -utim -ptimgood -h192.168.121.63 test < UC_USER_1.sql | 17m30.822s |
UUID | time mysql -utim -ptimgood -h192.168.121.63 test < UC_USER_PK_VARCHAR_1.sql | 23m6.360s |
? | ? | ? |
?
?
?
5、1000W总结
在1000W记录表的測试下:
(1)普通单条或者20条左右的记录检索,自增主键效率是uuid主键的2到3倍。
(2)可是范围查询特别是上百成千条的记录查询。自增id的效率要大于uuid;
(3)在范围查询做统计汇总的时候,自增id主键的效率是uuid主键1.5到2倍;
(4)在存储上面,自增id所占的存储空间是uuid的1/2;
(5)在写入上面,自增ID主键的效率是UUID主键的3到10倍,相差比較明显。特别是update小范围之内的数据上面。
(6)在备份恢复上。自增ID主键略微优于UUID。
?
?
?
6、MySQL分布式架构的取舍
分布式架构,意味着须要多个实例中保持一个表的主键的唯一性。
这个时候普通的单表自增ID主键就不太合适。由于多个mysql实例上会遇到主键全局唯一性问题。
?
?
?
6.1、自增ID主键+步长。适合中等规模的分布式场景
?
???????? 在每一个集群节点组的master上面,设置(auto_increment_increment),让眼下每一个集群的起始点错开 1,步长选择大于将来基本不可能达到的切分集群数,达到将 ID 相对分段的效果来满足全局唯一的效果。
?
长处是:实现简单,后期维护简单。相应用透明。
?
缺点是:第一次设置相对较为复杂。由于要针对未来业务的发展而计算好足够的步长;
?
规划:
比方计划总共N个节点组,那么第i个节点组的my.cnf的配置为:
auto_increment_offset? i
auto_increment_increment ?N
?
假如规划48个节点组,N为48,如今配置第8个节点组。这个i为8,第8个节点组的my.cnf里面的配置为:
auto_increment_offset? 8
auto_increment_increment? 48
?
?
?
6.2、UUID。适合小规模的分布式环境
???????? 对于InnoDB这样的聚集主键类型的引擎来说。数据会依照主键进行排序。由于UUID的无序性,InnoDB会产生巨大的IO压力,并且由于索引和数据存储在一起,字符串做主键会造成存储空间增大一倍。
?
在存储和检索的时候,innodb会对主键进行物理排序,这对auto_increment_int是个好消息,由于后一次插入的主键位置总是在最后。
可是对uuid来说,这却是个坏消息。由于uuid是杂乱无章的。每次插入的主键位置是不确定的,可能在开头,也可能在中间,在进行主键物理排序的时候,势必会造成大量的 IO操作影响效率,在数据量不停增长的时候。特别是数据量上了千万记录的时候。读写性能下降的非常厉害。
?
长处:搭建比較简单,不须要为主键唯一性的处理。
缺点:占用两倍的存储空间(在云上光存储一块就要多花2倍的钱)。后期读写性能下降厉害。
?
?
?
?
6.3、雪花算法自造全局自增ID。适合大数据环境的分布式场景
由twitter发布的开源的分布式id算法snowflake(Java版本号)
?
IdWorker.java:
?
package com.demo.elk; import org.slf4j.Logger;? import org.slf4j.LoggerFactory; ? public class IdWorker { ??? ??? protected static final Logger LOG = LoggerFactory.getLogger(IdWorker.class); ???? ??? private long workerId; ??? private long datacenterId; ??? private long sequence = 0L; ? ??? private long twepoch = 1288834974657L; ? ??? private long workerIdBits = 5L; ??? private long datacenterIdBits = 5L; ??? private long maxWorkerId = -1L ^ (-1L << workerIdBits); ??? private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); ??? private long sequenceBits = 12L; ? ??? private long workerIdShift = sequenceBits; ??? private long datacenterIdShift = sequenceBits + workerIdBits; ??? private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; ??? private long sequenceMask = -1L ^ (-1L << sequenceBits); ? ??? private long lastTimestamp = -1L; ? ??? public IdWorker(long workerId, long datacenterId) { ??????? // sanity check for workerId ??????? if (workerId > maxWorkerId || workerId < 0) { ??????????? throw new IllegalArgumentException(String.format("worker Id can‘t be greater than %d or less than 0", maxWorkerId)); ??????? } ??????? if (datacenterId > maxDatacenterId || datacenterId < 0) { ?????????? ?throw new IllegalArgumentException(String.format("datacenter Id can‘t be greater than %d or less than 0", maxDatacenterId)); ??????? } ??????? this.workerId = workerId; ??????? this.datacenterId = datacenterId; ??????? LOG.info(String.format("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d", timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId)); ??? } ? ??? public synchronized long nextId() { ??????? long timestamp = timeGen(); ? ??????? if (timestamp < lastTimestamp) { ??????????? LOG.error(String.format("clock is moving backwards.? Rejecting requests until %d.", lastTimestamp)); ??????????? throw new RuntimeException(String.format("Clock moved backwards.? Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); ??????? } ? ??????? if (lastTimestamp == timestamp) { ??????????? sequence = (sequence + 1) & sequenceMask; ??????????? if (sequence == 0) { ??????????????? timestamp = tilNextMillis(lastTimestamp); ??????????? } ??????? } else { ??????????? sequence = 0L; ??????? } ? ??????? lastTimestamp = timestamp; ? ??????? return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; ? ??} ? ??? protected long tilNextMillis(long lastTimestamp) { ??????? long timestamp = timeGen(); ??????? while (timestamp <= lastTimestamp) { ??????????? timestamp = timeGen(); ??????? } ??????? return timestamp; ??? } ? ??? protected long timeGen() { ??? ????return System.currentTimeMillis(); ??? } } |
?
?
?
測试生成ID的測试类,IdWorkerTest.java:
package com.demo.elk; ? import java.util.HashSet; import java.util.Set; ? public class IdWorkerTest { ???????? ? ??? static class IdWorkThread implements Runnable { ??????? private Set<Long> set; ??????? private IdWorker idWorker; ? ??????? public IdWorkThread(Set<Long> set, IdWorker idWorker) { ??????????? this.set = set; ??????????? this.idWorker = idWorker; ??????? } ? ??????? public void run() { ??????????? while (true) { ?????????? ?????long id = idWorker.nextId(); ??????????????? System.out.println("??????????? real id:" + id); ??????????????? if (!set.add(id)) { ??????????????????? System.out.println("duplicate:" + id); ??????????????? } ??????????? } ??????? } ??? } ? ??? public static void main(String[] args) { ??????? Set<Long> set = new HashSet<Long>(); ??????? final IdWorker idWorker1 = new IdWorker(0, 0); ??????? final IdWorker idWorker2 = new IdWorker(1, 0); ??????? Thread t1 = new Thread(new IdWorkThread(set, idWorker1)); ? ??????Thread t2 = new Thread(new IdWorkThread(set, idWorker2)); ??????? t1.setDaemon(true); ??????? t2.setDaemon(true); ??????? t1.start(); ??????? t2.start(); ??????? try { ??????????? Thread.sleep(30000); ??????? } catch (InterruptedException e) { ????? ??????e.printStackTrace(); ??????? } ??? } } |
?
?
?
7,总结
(1)单实例或者单节点组:
经过500W、1000W的单机表測试,自增ID相对UUID来说。自增ID主键性能高于UUID。磁盘存储费用比UUID节省一半的钱。所以在单实例上或者单节点组上,使用自增ID作为首选主键。
?
(2)分布式架构场景:
???????? 20个节点组下的小型规模的分布式场景。为了高速实现部署,能够採用多花存储费用、牺牲部分性能而使用UUID主键高速部署;
?
???????? 20到200个节点组的中等规模的分布式场景,能够採用自增ID+步长的较高速方案。
?
???????? 200以上节点组的大数据下的分布式场景,能够借鉴相似twitter雪花算法构造的全局自增ID作为主键。
?
?
?