【性能】MySQL使用自增ID和自建ID区别

题记:整改公司以前的商城数据,打开数据库,看到原项目中所有的表主键都是uuid,并且所有表都没有注释,我表示一脸懵逼。由于该表是领导以前找的一个人做的,好吧,联系人去。

由主键引发的“血案”就这么开始了,这个框架是之前的人自己写的,里面主键全是uuid,我跟他建议说MySQL最好使用自增主键,效率更高,innoDB的索引特性导致了自增id做主键是效率最好的,为了拿实际的案例来说服他,所以准备做个详细的测试



一:准备表及数据
USER,自增ID为主键,表结构类似如下:
CREATE TABLE `USER` (
   `ID` bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
   `USER_NAME` varchar(100) DEFAULT NULL COMMENT '用户名',
   `USER_PWD` varchar(200) DEFAULT NULL COMMENT '密码',
   `USER_ICON` varchar(500) DEFAULT NULL COMMENT '头像图片',
   `SEX` char(1) DEFAULT NULL COMMENT '性别, 1:男,2:女,3:保密',
   `STAT` varchar(10) DEFAULT NULL COMMENT '用户状态,01:正常,02:冻结',
   `LAST_LOGIN_DATE` datetime DEFAULT NULL COMMENT '最后登录时间',
   `LAST_LOGIN_IP` varchar(100) DEFAULT NULL COMMENT '最后登录IP',
   `SRC_OPEN_USER_ID` bigint(20) DEFAULT NULL COMMENT '来源的联合登录',
   `EMAIL` varchar(200) DEFAULT NULL COMMENT '邮箱',
   `MOBILE` varchar(50) DEFAULT NULL COMMENT '手机',
   `IS_DEL` char(1) DEFAULT '0' COMMENT '是否删除',
   `IS_EMAIL_CONFIRMED` char(1) DEFAULT '0' COMMENT '是否绑定邮箱',
   `IS_PHONE_CONFIRMED` char(1) DEFAULT '0' COMMENT '是否绑定手机',
   `CREATER` bigint(20) DEFAULT NULL COMMENT '创建人',
   `CREATE_DATE` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
   `UPDATE_DATE` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改日期',
   `PWD_INTENSITY` char(1) DEFAULT NULL COMMENT '密码强度',
   `ACTIVATE` char(1) DEFAULT '1' COMMENT '激活,1:激活,0:未激活',
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表'
USER_BACK表,字符串ID为主键,采用uuid:
CREATE TABLE `USER` (
   `UUID` VARCHAT(36) NOT NULL COMMENT '主键',
   `USER_NAME` varchar(100) DEFAULT NULL COMMENT '用户名',
   `USER_PWD` varchar(200) DEFAULT NULL COMMENT '密码',
   `USER_ICON` varchar(500) DEFAULT NULL COMMENT '头像图片',
   `SEX` char(1) DEFAULT NULL COMMENT '性别, 1:男,2:女,3:保密',
   `STAT` varchar(10) DEFAULT NULL COMMENT '用户状态,01:正常,02:冻结',
   `LAST_LOGIN_DATE` datetime DEFAULT NULL COMMENT '最后登录时间',
   `LAST_LOGIN_IP` varchar(100) DEFAULT NULL COMMENT '最后登录IP',
   `SRC_OPEN_USER_ID` bigint(20) DEFAULT NULL COMMENT '来源的联合登录',
   `EMAIL` varchar(200) DEFAULT NULL COMMENT '邮箱',
   `MOBILE` varchar(50) DEFAULT NULL COMMENT '手机',
   `IS_DEL` char(1) DEFAULT '0' COMMENT '是否删除',
   `IS_EMAIL_CONFIRMED` char(1) DEFAULT '0' COMMENT '是否绑定邮箱',
   `IS_PHONE_CONFIRMED` char(1) DEFAULT '0' COMMENT '是否绑定手机',
   `CREATER` bigint(20) DEFAULT NULL COMMENT '创建人',
   `CREATE_DATE` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
   `UPDATE_DATE` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改日期',
   `PWD_INTENSITY` char(1) DEFAULT NULL COMMENT '密码强度',
   `ACTIVATE` char(1) DEFAULT '1' COMMENT '激活,1:激活,0:未激活',
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表'


二:1000万数据开始测试
2.1)确认数据量
自增id为主键的表:
mysql> select count(*) from USER;
+----------+
| count(*) |
+----------+
| 10698102 |
+----------+
1 row in set (27.42 sec)

uuid为主键的表:
mysql> select count(*) from USER_BACK;
+----------+
| count(*) |
+----------+
| 10698102 |
+----------+
1 row in set (0.00 sec)

占据的空间容量来看,自增ID比UUID小一半左右:

自增ID: -rw-rw---- 1 mysql mysql 4.2G Aug 20  23:08 USER.ibd        4.2 G 

UUID:   -rw-rw---- 1 mysql mysql 8.8G Aug 20  18:20 USER_BACK.ibd    8.8 G


2.2)单个数据走索引查询,自增id和 uuid效率比约为:(2~3):1
单条记录查询 
自增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


2.3)范围like查询,自增ID性能优于UUID,比值约为:(1.5~2):1
(1)模糊范围查询1000条数据,自增ID性能要好于UUID 
自增ID:    SELECT SQL_NO_CACHE t.* FROM  test.`USER` t WHERE t.`MOBILE` LIKE '147%' LIMIT 1000; 2.398 
UUID:    SELECT  SQL_NO_CACHE t.* FROM test.`USER_BACK` t WHERE t.`MOBILE` LIKE  '147%' LIMIT 1000; 5.872 

(2)日期范围查询20条数据,自增ID稍微弱于UUID 
自增ID:    SELECT SQL_NO_CACHE t.* FROM  test.`USER` 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.`USER_BACK` 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.`USER` 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.`USER_BACK` 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.`USER` t WHERE t.`CREATE_DATE` > '2016-07-01 10:26:36'  ; 1.129 
UUID:    SELECT  SQL_NO_CACHE COUNT(1) FROM test.`USER_BACK` t WHERE  t.`CREATE_DATE` > '2016-07-01 10:26:36'   ;



2.4)写入测试,自增ID比UUID效率高,比值约为:(3~10):1
修改一天的记录 
自增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_CONFIR



2.3)备份和恢复,自增ID性能优于UUID
Mysqldump备份

自增ID:	time mysqldump -utim -ptimgood -h192.168.121.63  test USER> USER_BACK.sql	0m50.548s
UUID:	time mysqldump -utim -ptimgood  -h192.168.121.63 test USER> USER_BACK.sql	0m58.590s
 
MySQL恢复

自增ID:	time mysql -utim -ptimgood  -h192.168.121.63 test < USER.sql	        17m30.822s
UUID:	time mysql -utim -ptimgood  -h192.168.121.63 test < USER_BACK.sql        23m6.360s


小结

在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。



三:MySQL分布式架构的取舍

分布式架构,意味着需要多个实例中保持一个表的主键的唯一性。这个时候普通的单表自增ID主键就不太合适,因为多个mysql实例上会遇到主键全局唯一性问题。


3.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


3.2)UUID,适合小规模的分布式环境

对于InnoDB这种聚集主键类型的引擎来说,数据会按照主键进行排序,由于UUID的无序性,InnoDB会产生巨大的IO压力,而且由于索引和数据存储在一起,字符串做主键会造成存储空间增大一倍。


在存储和检索的时候,innodb会对主键进行物理排序,这对auto_increment_int是个好消息,因为后一次插入的主键位置总是在最后。但是对uuid来说,这却是个坏消息,因为uuid是杂乱无章的,每次插入的主键位置是不确定的,可能在开头,也可能在中间,在进行主键物理排序的时候,势必会造成大量的 IO操作影响效率,在数据量不停增长的时候,特别是数据量上了千万记录的时候,读写性能下降的非常厉害。


优点:搭建比较简单,不需要为主键唯一性的处理。

缺点:占用两倍的存储空间(在云上光存储一块就要多花2倍的钱),后期读写性能下降厉害。



总结

(1):单实例或者单节点组3.2

经过500W、1000W的单机表测试,自增ID相对UUID来说,自增ID主键性能高于UUID,磁盘存储费用比UUID节省一半的钱。所以在单实例上或者单节点组上,使用自增ID作为首选主键。


(2)分布式架构场景

20个节点组下的小型规模的分布式场景,为了快速实现部署,可以采用多花存储费用、牺牲部分性能而使用UUID主键快速部署;

20到200个节点组的中等规模的分布式场景,可以采用自增ID+步长的较快速方案。

200以上节点组的大数据下的分布式场景,可以借鉴类似twitter雪花算法构造的全局自增ID作为主键。






***************当你发现自己的才华撑不起野心时,就请安静下来学习吧***************


客官,点击下方打赏一个呗~


点赞

发表评论