之前做过的项目,基本都是用自增 id 做主键或者没有主键。这次领导突然说用 UUID varchar(36)位做主键,让小弟有点迷惑,望大家指点迷津。
1
chengxiao 2019-09-06 15:04:14 +08:00
分库
|
2
lolizeppelin 2019-09-06 15:06:29 +08:00 6
不容易被人看出规律(隐藏用户数量), 散列好,做缓存字段更好处理, 不用自增降低数据库消耗,也更适合做分布式系统主键, 长度比 int 好, 字符串做主键也不会出现 long 型导致前端 js 麻烦问题
缺点也就性能差点 |
3
chendy 2019-09-06 15:07:06 +08:00
*据说*,字符串的主键在某些 DB 下会有一些性能问题(毕竟数字就一个,字符串是一堆)
压力不很大的话,snowflake + big int 也够用了 |
4
xkeyideal 2019-09-06 15:07:52 +08:00
优势就是唯一了,劣势一堆,字符串还是不要用来做主键的好,浪费存储,mysql 有热数据的,不利于连续的热数据加载
|
5
DovaKeen 2019-09-06 15:07:57 +08:00
优势是猜不出来,劣势是采用聚簇索引时候会影响插入性能
|
6
wd 2019-09-06 15:15:59 +08:00
|
7
lolizeppelin 2019-09-06 15:17:52 +08:00
|
8
dk7952638 2019-09-06 15:18:39 +08:00 2
uuid 这种方案其实挺尴尬的,小项目不需要,而大项目性能又不好
|
9
littlewing 2019-09-06 15:19:21 +08:00 2
如果是 MySQL innodb,非自增主键空间利用率低 and 插入性能稍差
详细原因请自行了解 B+树的原理 |
10
tomczhen 2019-09-06 15:20:14 +08:00 via Android 2
|
11
newtype0092 2019-09-06 15:22:14 +08:00
我们是两者都有,系统内部用自增 id 做主键,系统间 RPC 用 uuid 标识唯一用户
|
12
rockyou12 2019-09-06 15:23:02 +08:00
项目大了好分库,小项目主键哪个都差不多,大项目肯定不能用自增,所以业务扩展上 uuid 比较好。说 uuid 性能问题的,你真的让 uuid 有性能影响的时候,其它地方的瓶颈才是重点……
|
13
whypool 2019-09-06 15:23:37 +08:00
优势就是猜不出规律
劣势就是浪费存储和索引性能 |
14
BBCCBB 2019-09-06 15:26:34 +08:00
没啥优势. 推荐用趋势递增的 long 型数字.
|
15
wysnylc 2019-09-06 15:29:42 +08:00 1
优势:无规律,碰撞小
劣势:无规律,大数据量一样会碰撞 总结:建议使用雪花算法等解决碰撞问题 |
16
aaronysj OP |
17
STRRL 2019-09-06 15:38:34 +08:00 via Android 2
ORM 那边可以做一个 trick
类里写的是 UUID 其实 db 里存的是 big int 哒 |
18
Mrun 2019-09-06 15:39:47 +08:00 1
有种东西叫做时间有序 UUID,在数据库中的性能依然很好
https://www.codeproject.com/Articles/388157/GUIDs-as-fast-primary-keys-under-multiple-database |
19
niubee1 2019-09-06 15:41:16 +08:00 7
用自增 ID 做物理主键,再加一个 uuid 做逻辑主键(加 unique 索引),在外键关联的时候用逻辑主键,这样子在分库的时候无痛, 也不会遇到 uuid 作为物理主键的时候遇到的性能问题。唯一的 issuse 就是会浪费一部分存储空间
|
20
Mrun 2019-09-06 15:41:52 +08:00
我觉得对 UUID 作为 DB 的主键有着错误的认知,UUID 可以实现微秒精度的时间有序
|
21
wlw0327 2019-09-06 15:43:12 +08:00 via Android
优势就是全局唯一了,但是劣势嘛,性能会差
|
22
Valid 2019-09-06 15:43:31 +08:00
主键还是自增,给每条数据分配 uuid 不是更好?有问题?
|
23
tanszhe 2019-09-06 15:43:50 +08:00 1
优势是可分布式唯一
劣势性能差,浪费空间 和 自增比 |
24
jzmws 2019-09-06 15:46:24 +08:00
雪花了解一下 .
|
26
lbp0200 2019-09-06 15:51:16 +08:00
UUID 性能差?少写点 bug,什么都有了
|
27
Aresxue 2019-09-06 15:51:40 +08:00
优点:
1.可以用来做分布式 id 更方便; 2. 具有唯一性, 可以让数据分布的更均匀; 3.字符串可以屏蔽掉 java 和 js 的一些数据类型差异,比如使用 Long 时如果数字过大会产生问题; 缺点: 1.太长了,主键会被用作聚簇索引,每次插入都要损耗不少性能,bigint 也才 8 个字节; 2.临近的数据没有相关性,不能有效利用局部性原理节省性能; 3.进行范围查询时会扫描过多的页,性能不好,这点和 2 类似。 优先推荐 Twitter 的 snowflake,但需要记得在最外层和前端交互时使用 String |
28
passerbytiny 2019-09-06 16:19:17 +08:00 3
优势:完全业务无关,完全与数据库无关,意味着生成迅速、生成简单、全局统一、不依赖底层基础设施。
劣势:完全业务无关,完全与数据库无关,意味着不是自然主键(你需要另加唯一索引字段才能有自然主键)、不直观、底层基础设施的性能优化措施用不上。 至于前面那些人所说的“太长”的劣势,这纯粹是人云亦云,早期存储值钱的时候还可以这么说,现在都大数据了,还这么说就是扯淡。另外,UUID 本质上是 16 字节 128 位二进制数据,如果没有特殊需要(懒得加额外的自然主键,直接拿 UUID 做不那么高效的自然主键),那么 UUID 就没必要再 dump 成 32/36 字节的字符串,直接用十六字节长度二进制类型进行存储。 |
29
lihongjie0209 2019-09-06 16:27:30 +08:00 2
数据库无关, 你的几百行业务代码中, 各种数据库插入就是为了获取自增 ID, 然后给后面业务逻辑用。
如果我们使用 UUID, 那么你的业务代码中数据库操作出现的概率会小很多, 方便测试, 也是一种解耦 |
30
ryd994 2019-09-06 16:35:09 +08:00 via Android 4
UUID 用什么 varchar ?
首先 UUID 是定长的,不需要变长类型。定长类型在储存和索引时有优势 其次 UUID 是 hex,一共才 36 种字母数字,为什么作为 text 存?任何 UUID 完全可以导出为二进制 数据库提供 UUID 数据类型时存 UUID 类型。原因是比较顺序。UUID1 前几位是机器代号,最后才是随机数。所以最好从后往前建立索引,这样可以帮助索引自然平衡。 UUID 最适合分库的情况,UUID 的随机性保证了分库的平均。同时结合机器代号可以保证不同机器无需单点也能保证不碰撞。 |
31
ryd994 2019-09-06 16:44:37 +08:00 via Android 1
最后,主键只是数据库内在的储存结构的一部分。如果你用的是单机或者客户端了解的分表分库,那自增主键的性能反而更好
楼上说什么方便给用户的,你自己想想看,数据库主键直接暴露给用户,这是求人碰撞攻击你吗?给用户看到的 token 为什么不和内部 ID 解耦? token 和 id 解耦之后还能 revoke 就算用自增主键,也可以另外生成一个带索引的 token 字段,隐藏用户 ID 和主键用什么,没有半毛钱关系 再吐槽一下 varchar 做主键,存的是 UUID。真的不知道在想什么。任何读过数据库的用户手册的人都想不出这样的主意。你就没有想过为什么某些数据库要专门做个 UUID 类么? |
32
ryd994 2019-09-06 16:48:45 +08:00 via Android 1
|
33
ryd994 2019-09-06 16:55:23 +08:00 via Android
@wysnylc 正确使用 UUID1 是不会有碰撞的。要碰撞需要你在 100 纳秒内反复生成还刚好遇上随机数碰撞
UUID4 是纯随机,鉴于值域有 128bit,你可以算算 10 年内碰撞到一次需要多高的频率。实际上也基本可以忽略,当然,比不上 UUID1 的绝对保证。优点是不依赖,不暴露机器信息 其他 UUID 各有生成逻辑,使用之前彻底搞懂生成逻辑,然后论证为什么可以避免碰撞即可。 |
34
zjiecode 2019-09-06 16:58:49 +08:00
最直接的好处是,避免被穷举
|
35
Raymon111111 2019-09-06 17:04:15 +08:00
劣势
1. 不自增, 插入性能会有点问题. 这个和索引的结构有关系, 往中间插一个数据如果遇上当页数据满了后面都得重做 2. 数据量大一点碰撞是很常见的, 或者可以说是必现的, 按天维度 (这个可以搜一下别人的使用经验) |
36
wanguorui123 2019-09-06 17:18:26 +08:00 1
UUID 优势:
1、避免 API 接口传入恶意 ID,试图绕过验证 2、分库不用担心 ID 冲突 3、web 端不用考虑 ID 精度丢失,因为是字符串 3、UUID 理论上无限大,可以认为插入无限量的 ID,而序列是有限的 UUID 劣势: 1、相对序列耗费资源 2、文本的索引效率相对低了一点 |
37
yalin 2019-09-06 17:21:23 +08:00
减少对具体数据库的主键依赖
|
38
dk7952638 2019-09-06 17:21:42 +08:00
雪花也不是没有缺点,对系统时钟有严格要求
|
39
wanguorui123 2019-09-06 17:22:43 +08:00
UUID 优势:
1、避免 API 接口传入恶意 ID,试图绕过验证 2、分库不用担心 ID 冲突 3、web 端不用考虑 ID 精度丢失,因为是字符串 4、UUID 理论上无限大,可以认为插入无限量的 ID,而序列是有限的 5、不需要维护 ID 的顺序 UUID 劣势: 1、相对序列耗费资源 2、文本的索引效率相对低了一点 |
40
sujin190 2019-09-06 17:23:25 +08:00
其实为啥要把表主键和数据主键弄成一个,是不是表自身还是有个自增的 int 主键 ID 更好一些?数据再有个非线性可分布式的数据主键
|
41
oma1989 2019-09-06 17:25:47 +08:00
UUID 数据量的大话会有吃屎的感觉,别问我咋知道的。。。。。。。
|
42
yalin 2019-09-06 17:33:00 +08:00
|
43
love 2019-09-06 17:38:35 +08:00
哪怕用 UUID 也不要 varchar(36) 啊,8 个二进制字节的事你搞这么大干嘛
|
44
wysnylc 2019-09-06 17:39:30 +08:00
@ryd994 #33 都说了大数据量,概率再低不是没有概率
我明确说明是大数据量就是防止你这种说概率低的抬杠,没想到你还是来了 |
45
tanszhe 2019-09-06 17:46:18 +08:00
直接搞个 id 生成服务, 和数据库自增一样的。
唯一缺点就是要单独部署一个服务 |
46
wenzhoou 2019-09-06 17:48:44 +08:00 via Android
分布式只能用 UUID 的情况。
假定我是一个公开的接口,有不特定的若干个客户端。 没个客户端给我们系统发送请求的时候,又需要这些请求记录都保存在 db 中。 这种需求就让客户端自己发 UUID 作为主键,你们随便发,保证不会冲突。 这是一种必须用 UUID 的场景。 |
47
W1angMh 2019-09-06 17:52:58 +08:00
建议用雪花算法
|
48
passerbytiny 2019-09-06 17:58:02 +08:00
@wysnylc #39 照你这“概率再低不是没有概率”的说法,下雨天就不能出门,遭雷劈的概率可要比 UUID 碰撞发生的概率高的多。抛开具体数字去说“大”,你这才是真正的抬杠。
|
49
wangliran1121 2019-09-06 18:10:33 +08:00
劣势很明显,不自增
|
50
wysnylc 2019-09-06 18:15:34 +08:00
@passerbytiny #48 爱抬你就多抬点,我当乐子看
|
51
swulling 2019-09-06 18:17:11 +08:00 via iPhone
uuid 碰撞和什么大数据量没关系
关键是两点,吞吐和生成 uuid 的机器 |
52
Takamine 2019-09-06 18:36:53 +08:00 via Android
我们是用的 PG 自带的 uuid 生成。_(:з」∠)_
|
53
weiruanniubi 2019-09-06 18:51:54 +08:00
求楼主头像的高清大图……
|
54
singworld 2019-09-06 21:01:53 +08:00
前公司代码规范里 禁止使用 uuid 做主键
|
55
Cbdy 2019-09-06 21:03:59 +08:00 via Android
优势是简单,劣势是性能差,而且差到在有的场景成为瓶颈
|
56
LokiSharp 2019-09-06 21:15:49 +08:00
各方面都有优势,劣势是总有人用字符串存他
|
57
wind3110991 2019-09-06 21:27:08 +08:00
如果是 INNODB 的话,索引结构是一颗 B+树,叶子节点存储的是索引的值,如果存递增的数字作为索引,那么无论是空间还是在一定范围内搜索都是高效的,如果存一段 UUID,真想不出有什么优势。
有人提到可以搞分布式+大数据量,但是 Mysql 本来就不是干这个事情的,如果是量级达到这个级别不如换成 HBase |
58
lishunan246 2019-09-06 21:44:47 +08:00 via Android
靠 uuid 反爬根本就是掩耳盗铃。等把所有主键都换成 uuid,我想知道服务端一条日志能有多长。
|
59
itning 2019-09-06 22:44:28 +08:00
UUID 劣势是因为 mysql 索引是 B+树的结构导致的
|
60
iluckypig 2019-09-06 23:52:23 +08:00
你应该先补充一下索引结构的知识,就是到上面大家说的 UUID 索引性能差是为什么了
|
61
ryd994 2019-09-07 04:37:47 +08:00 via Android 2
@wysnylc 你才抬杠好么
你什么系统做到单节点 100 纳秒多次调用?一般操作系统调度时间都要几十微秒。就算针对低延迟特别调优,你随便处理处理数据 100 纳秒就过去了。你要每秒 1 千万次插入操作才会碰撞。先问问你数据库性能到这个数没有? 就算到了,后面还有 14 位序号,正确使用的话,重复需要超过 10 亿次插入每秒。你用的什么数据库可以做到这个性能? 有这功夫和我杠,为什么不去查一下 UUID 的生成方式,再自己用笔算一下? @passerbytiny UUID1 是靠生成机制*保证*不可能重复 UUID4 才是拼概率。然而 128 位随机,实际上碰撞概率也是低到忽略不计。 UUID3/5 是靠 hash 算法的碰撞复杂度来拼概率 |
62
arraysnow 2019-09-07 10:30:52 +08:00
@ryd994 雪花算法生成的字符串更长,更适用分库分表的高频记录。既然用 uuid 目的是分库,那他造成的性能损失已经无关紧要了。字符串长度不会影响索引速度,内存不够可以再分,io 可以忽略不计
|
63
xuanbg 2019-09-07 14:13:26 +08:00
优点一大堆,缺点就一点:因为索引代价大,所以插入性能比较低。尽管插入性能低,每秒写入百十来条数据还是没问题了。
|
64
RyanOne 2019-09-07 15:28:07 +08:00
snowflake 强依赖机器时钟,如果机器上时钟回拨,或者分布式环境,每台机器上的时钟不可能完全同步,会导致发号重复或者服务会处于不可用状态。
|
65
FrankHB 2019-09-08 22:37:31 +08:00
UUID varchar,呵呵……
@passerbytiny 你搞反了扯蛋的方向。 首先,键的规模开销不是只有空间性能,之前回复的还反过来强调问题主要是响应性能开销。(之后也有别的回复提到。) 其次,对于通常的数据库,每行都有不可压缩的键的情况下,键的规模影响存储成本体现在整行数据中的占比。 注意考虑现有产品的承载能力,大数据的所谓大体现在行而不是列的规模。 存储再不值钱也不保证不要钱,扩大键的存储占用,越大数据绝对成本增加得越大,反而你不怎么大数据倒不用计较那么多。 定性都不正确,你还是不要纠结是不是得抛开具体数字的问题了。 另外真要杠你就该说零概率都不表示不可能事件,所谓的“没有概率”是什么鬼? @lihongjie0209 要不要自增主键本来就和业务相关。前面有说业务内容了? |
66
lihongjie0209 2019-09-08 22:56:12 +08:00
@FrankHB #65 使用自增主键是业务相关的, 但是使用数据库提供的自增组件就是一个具体的实现, 和 DB 强相关, 你的业务和数据库强相关就是耦合, 也就是难测试
|
67
FrankHB 2019-09-08 23:18:14 +08:00
@lihongjie0209 我理解你的意思是不清楚具体业务的场景下,用自增主键是一个实现细节。
但是从业务的角度来讲,是否允许依赖插入有序这个保证通常就是系统设计的基础特性而不是实现细节。如果这样的基础性质会被需求变更推翻,整个数据库设计很可能都得推倒重来。 这类数据库设计的决策本来就得在这种业务需求明确后才能做,你的测试能先于业务需求明确前搭建?考虑这类需求变更导致数据库设计的整体不确定性,你确定这样出来的测试能覆盖多少内容才能确保业务变化有更大适应性?特别地,一些业务就算不用自增主键,也得用其它逻辑来实现这个保证,你如何应对测试这些东西的需求——测试 DB 可能确实是简单了,但测试整个系统呢? |
68
lihongjie0209 2019-09-08 23:40:59 +08:00
@FrankHB #67
1. 好的架构应该是存储无关的(尽可能) 2. 业务就管好业务, 要有序就在程序中实现, 加一个额外的排序字段就可以了 3. 业务需求作为最重要的核心, 应该是第三方(数据库)依赖于业务需求, 比如数据量大了上 nosql,但是你的业务需求不应该依赖于数据库, 这是依赖倒置原则。 4. 我的测试不会先于需求, 但是我的设计理念高于需求。 5. 需求变化导致数据库变化是正常现象, 数据库依赖于业务!!!变就变嘛, 软件写出来就是用来改的。 6. 不使用数据库的自增主键那么测试就会很简单, 自己写的代码实现的纯业务代码怎么不能测试了。 7. 测试业务逻辑(单元测试)和测试整个系统(集成测试)本来就是不同层面的东西, 二者的需求和测试量是不同的, 也没有任何依赖关系。 单元测试的简单与否与集成测试的可测试性没有关系。 以上观点来自《 Clean Architecture 》 |
69
Elecoxy 2019-09-09 09:05:11 +08:00 via iPhone
crazy 使用 uuid 做主键?!?
|
70
wysnylc 2019-09-09 11:10:22 +08:00
@ryd994 #61 uuid 的生成方式有多种,你让我看哪种?还是你觉得只有一种?
雪花算法是 Twitter 提出的算法,请问你比 Twitter 厉害吗? 雪花算法生成的是 long,uuid 是字符串请问哪个好? uuid 的不同生成算法会依赖不同的种子,例如 time-based:基于时间的 UUID name-based:基于名字的 UUID,通过计算名字和名字空间的 MD5 来计算 UUID 所以 uuid 就不能重复?谁说碰撞非得每秒使用到 1630 亿个 uuid?可笑 |
71
memi 2019-09-09 15:25:17 +08:00
@love 您好,请教下。uuid 业界一般用什么字段存比较好,我看了我之前写的 demo 都是用 varchar 存的 T_T。
|
72
FrankHB 2019-09-21 22:49:20 +08:00
@lihongjie0209
1.我认为“存储无关”决定架构优劣这是教条,并且有悖常理。 因为架构设计是服务于业务的,是不是允许系统设计依赖甚至鼓励依赖某个子系统(不管是持久化层还是你这里说更具体的“存储”这样的实现)的特征以便使系统容易具有其它优势,这根本还是业务需求说了算。 而且,强制要求先划分出存储还可能使实现容易违反一些其它更一般的普遍原则,比如 Principle of least privilege。 2.决定存储在哪里实现本身就是需要设计决策确定的一个问题,不能总是简单地排除出业务之外。 3.更一般地,数据库实际上不过是存储(或者持久化层)的一种实现。预先要求依赖某种数据库也是应该在普遍的架构设计中避免的——只不过这个主题的题设已经限定了数据库这个背景而已。 4.设计是需求的解。你的“高于需求”的理念不符合工程上一般意义的认知,这个矛盾看来需要你在落实设计之前解决。 5.设计的变化不一定成问题,但是为了变化付出太大的成本可能成问题。(另外,明确架构设计的一个作用就是为了避免这种成本太大。)不是任意的变化都应该是被无条件接受的。 6.你似乎认为测试数据库总是困难的,测试别的模块总是比测试数据库简单。不过事实上恐怕未必总是如此。 的确有几种强行让业务逻辑看起来总是更容易测试的设计套路,但是因为会限制表达能力,并不是每个业务领域都允许这样。 7.这点原则上我同意你的意见,虽然严格来讲测试业务逻辑不总是单元测试,这是和这里内容没有直接关系的次要话题。我的提法有些不够清楚,原意是指整个系统中的其它模块。 |
73
ruandao 2020-01-15 10:17:43 +08:00
@Raymon111111
劣势 1. 不自增, 插入性能会有点问题. 这个和索引的结构有关系, 往中间插一个数据如果遇上当页数据满了后面都得重做 存在这个问题吗? uuid1 不是时间顺序的吗? 我现在感觉,问题好像就只有要进索引,但是太大 |
74
sherlockfff 2020-06-29 10:39:02 +08:00
@love
uuid 长度为 128bit,16 字节,以 mysql 来看,数值类型中最大的 bigint 为 8 字节,所以没法用数值 |
75
sherlockfff 2020-06-29 10:40:51 +08:00
@singworld 那主键是用数值类型吗?
那对于上面提到的那些缺点,如分库等情况怎么处理呢 |
76
love 2020-06-29 11:00:49 +08:00 via Android
@sherlockfff mysql 有二进制数据类型
|
77
huanghq 2020-10-21 10:50:20 +08:00
以前做个一个项目,自增 id 做主键,另加 UUID 字符串字段做唯一索引。
内部使用自增 ID 关联,暴露在外部的 api 使用 uuid. |