LOADING

标签 mysql 下的文章

使用方法,在select语句前加上explain就可以了:

EXPLAIN SELECT surname,first_name form a,b WHERE a.id=b.id
EXPLAIN列的解释:

table:显示这一行的数据是关于哪张表的。

type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、index和ALL。

possible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引。可以为相关的域从WHERE语句中选择一个合适的语句。

key: 实际使用的索引。如果为NULL,则没有使用索引。很少的情况下,MySQL会选择优化不足的索引。这种情况下,可以在SELECT语句中使用USE INDEX(indexname)来强制使用一个索引或者用IGNORE INDEX(indexname)来强制MySQL忽略索引。

key_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好。

ref:显示索引的哪一列被使用了,如果可能的话,是一个常数。

rows:MySQL认为必须检查的用来返回请求数据的行数。

Extra:关于MySQL如何解析查询的额外信息。

extra列返回的描述的意义:

复制代码
Distinct: 一旦MySQL找到了与行相联合匹配的行,就不再搜索了。

Not exists: MySQL优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行,就不再搜索了。

Range checked for each Record(index map:#): 没有找到理想的索引,因此对于从前面表中来的每一个行组合,MySQL检查使用哪个索引,并用它来从表中返回行。这是使用索引的最慢的连接之一。

Using filesort: 看到这个的时候,查询就需要优化了。MySQL需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行。

Using index: 列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候。

Using temporary: 看到这个的时候,查询需要优化了。这里,MySQL需要创建一个临时表来存储结果,这通常发生在对不同的列集进行ORDER BY上,而不是GROUP BY上。

Where used: 使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。如果不想返回表中的全部行,并且连接类型ALL或index,这就会发生,或者是查询有问题不同连接类型的解释(按照效率高低的顺序排序)。

system: 表只有一行:system表。这是const连接类型的特殊情况。

const: 表中的一个记录的最大值能够匹配这个查询(索引可以是主键或惟一索引)。因为只有一行,这个值实际就是常数,因为MySQL先读这个值然后把它当做常数来对待。

eq_ref: 在连接中,MySQL在查询时,从前面的表中,对每一个记录的联合都从表中读取一个记录,它在查询使用了索引为主键或惟一键的全部时使用。

ref: 这个连接类型只有在查询使用了不是惟一或主键的键或者是这些类型的部分(比如,利用最左边前缀)时发生。对于之前的表的每一个行联合,全部记录都将从表中读出。这个类型严重依赖于根据索引匹配的记录多少—越少越好。

range: 这个连接类型使用索引返回一个范围中的行,比如使用>或<查找东西时发生的情况。

index: 这个连接类型对前面的表中的每一个记录联合进行完全扫描(比ALL更好,因为索引一般小于表数据)。

ALL: 这个连接类型对于前面的每一个记录联合进行完全扫描,这一般比较糟糕,应该尽量避免。

多个字段的索引 要单独建立一个多个字段的索引。不然的话会从条件字段从左到右去索引中找第一个匹配的索引。like和and语句无法使用索引

新增字段并注释
alter table order_list add channel tinyint(1) NOT NULL COMMENT '0未统计,1小程序,2app,3公众号,4人工';
修改字段默认值
alter table order_list alter column channel set default 0;

添加默认索引
ALTER TABLE table_name ADD INDEX index_name(detail_type,user_id);
注`符号必须有
经过添加索引效率能上升好几倍
实测1w2条数据 三个表添加索引后时间从1.2秒提升到了0.4

以下是其他索引创建方法
1.PRIMARY KEY(主键索引)
mysql>ALTER TABLE table_name ADD PRIMARY KEY ( column )
2.UNIQUE(唯一索引)
mysql>ALTER TABLE table_name ADD UNIQUE (column )
3.INDEX(普通索引)
mysql>ALTER TABLE table_name ADD INDEX index_name ( column )
4.FULLTEXT(全文索引)
mysql>ALTER TABLE table_name ADD FULLTEXT ( column )
5.多列索引
mysql>ALTER TABLE table_name ADD INDEX index_name ( column1, column2, column3 )

1 创建表的时候写注释
create table test1
(
field_name int comment '字段的注释'
)comment='表的注释';

2 修改表的注释
alter table test1 comment '修改后的表的注释';

3 修改字段的注释
alter table test1 modify column field_name int comment '修改后的字段注释';
--注意:字段名和字段类型照写就行

4 查看表注释的方法
--在生成的SQL语句中看
show create table test1;
--在元数据的表里面看
use information_schema;
select * from TABLES where TABLE_SCHEMA='my_db' and TABLE_NAME='test1' G

5 查看字段注释的方法
--show
show full columns from test1;
--在元数据的表里面看
select * from COLUMNS where TABLE_SCHEMA='my_db' and TABLE_NAME='test1' G

备份数据库
/下
mysqldump -uroot -pPassword [database name] > [dump file]
导入数据库
mysql 下
source fileName.sql
更改导入大小
max_allowed_packet = 1024M
下载文件
sz

接手了一个项目,项目中所有表主键都是uuid,我表示一脸懵逼,最后找到这篇文章做总结,提前说一下,uuid没有任何存在的必要,只不过是万千程序员们在追求结果的途中绕的弯路而已.

    以下这篇在csdn上有黄杉写的文章,充分的说明了这一点.并且用实际业务数据,有代表性

测试缘由

一个开发同事做了一个框架,里面主键是uuid,我跟他建议说mysql不要用uuid用自增主键,自增主键效率高,他说不一定高,我说innodb的索引特性导致了自增id做主键是效率最好的,为了拿实际的案例来说服他,所以准备做一个详细的测试。

作为互联网公司,一定有用户表,而且用户表UC_USER基本会有百万记录,所以在这个表基础上准测试数据来进行测试。

     测试过程是目前我想到的多方位的常用的几种类型的sql进行测试,当然可能不太完善,欢迎大家留言提出更加完善的测试方案或者测试sql语句。

1、准备表以及数据
UC_USER,自增ID为主键,表结构类似如下:
CREATE TABLE UC_USER (
ID bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
USER_NAME varchar(100) DEFAULT NULL COMMENT '用户名',
USER_PWD varchar(200) DEFAULT NULL COMMENT '密码',
BIRTHDAY datetime DEFAULT NULL COMMENT '生日',
NAME varchar(200) DEFAULT NULL COMMENT '姓名',
USER_ICON varchar(500) DEFAULT NULL COMMENT '头像图片',
SEX char(1) DEFAULT NULL COMMENT '性别, 1:男,2:女,3:保密',
NICKNAME varchar(200) DEFAULT NULL COMMENT '昵称',
STAT varchar(10) DEFAULT NULL COMMENT '用户状态,01:正常,02:冻结',
USER_MALL bigint(20) DEFAULT NULL COMMENT '当前所属MALL',
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 '密码强度',
MOBILE_TGC char(64) DEFAULT NULL COMMENT '手机登录标识',
MAC char(64) DEFAULT NULL COMMENT 'mac地址',
SOURCE char(1) DEFAULT '0' COMMENT '1:WEB,2:IOS,3:ANDROID,4:WIFI,5:管理系统, 0:未知',
ACTIVATE char(1) DEFAULT '1' COMMENT '激活,1:激活,0:未激活',
ACTIVATE_TYPE char(1) DEFAULT '0' COMMENT '激活类型,0:自动,1:手动',
PRIMARY KEY (ID),
UNIQUE KEY USER_NAME (USER_NAME),
KEY MOBILE (MOBILE),
KEY IDX_MOBILE_TGC (MOBILE_TGC,ID),
KEY IDX_EMAIL (EMAIL,ID),
KEY IDX_CREATE_DATE (CREATE_DATE,ID),
KEY IDX_UPDATE_DATE (UPDATE_DATE)
) ENGINE=InnoDB AUTO_INCREMENT=7122681 DEFAULT CHARSET=utf8 COMMENT='用户表'

UC_USER_PK_VARCHAR表,字符串ID为主键,采用uuid
CREATE TABLE UC_USER_PK_VARCHAR_1 (
ID varchar(36) CHARACTER SET utf8mb4 NOT NULL DEFAULT '0' COMMENT '主键',
USER_NAME varchar(100) DEFAULT NULL COMMENT '用户名',
USER_PWD varchar(200) DEFAULT NULL COMMENT '密码',
BIRTHDAY datetime DEFAULT NULL COMMENT '生日',
NAME varchar(200) DEFAULT NULL COMMENT '姓名',
USER_ICON varchar(500) DEFAULT NULL COMMENT '头像图片',
SEX char(1) DEFAULT NULL COMMENT '性别, 1:男,2:女,3:保密',
NICKNAME varchar(200) DEFAULT NULL COMMENT '昵称',
STAT varchar(10) DEFAULT NULL COMMENT '用户状态,01:正常,02:冻结',
USER_MALL bigint(20) DEFAULT NULL COMMENT '当前所属MALL',
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 '密码强度',
MOBILE_TGC char(64) DEFAULT NULL COMMENT '手机登录标识',
MAC char(64) DEFAULT NULL COMMENT 'mac地址',
SOURCE char(1) DEFAULT '0' COMMENT '1:WEB,2:IOS,3:ANDROID,4:WIFI,5:管理系统, 0:未知',
ACTIVATE char(1) DEFAULT '1' COMMENT '激活,1:激活,0:未激活',
ACTIVATE_TYPE char(1) DEFAULT '0' COMMENT '激活类型,0:自动,1:手动',
PRIMARY KEY (ID),
UNIQUE KEY USER_NAME (USER_NAME),
KEY MOBILE (MOBILE),
KEY IDX_MOBILE_TGC (MOBILE_TGC,ID),
KEY IDX_EMAIL (EMAIL,ID),
KEY IDX_CREATE_DATE (CREATE_DATE,ID),
KEY IDX_UPDATE_DATE (UPDATE_DATE)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';

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作为主键。