最近接触了一个项目,里面的数据库设计让我“大开眼界”,很多表没有主键 id,取而代之的是复合主键,也就是几个字段同时作为主键,比如说: A 表:
name
age
addr
主键是 name+age
B 表:
name
age
money
something
xxx
主键也是 name+age
实际上 B 表是 A 表的附属表,也就说是补充 A 表的内容,或者是关联到其它表
恕我愚昧,不知道这种设计有什么好处,我觉得用 id 当主键更简介方便,便于修改数据,比如上面的例子有时候还有修改 age 的需求,这时候就非常麻烦了,难道这是反范式化设计,故意做的数据冗余,我不敢苟同,咱也不敢问,咱也不敢说。
各位大佬,求打醒!!!
1
tabris17 2020-03-19 10:23:15 +08:00
理论上当然可以不需要自增 ID 当主键,但是实际这么操作会被人打的
|
2
littleylv 2020-03-19 10:23:40 +08:00
|
3
tabris17 2020-03-19 10:24:30 +08:00
另外用非自增 ID 做主键,会增加随机写入的概率,insert 操作性能会降低
|
4
Uyuhz 2020-03-19 10:24:33 +08:00
如果同名同岁怎么办...
|
5
ytmsdy 2020-03-19 10:28:40 +08:00
这是学生的毕业设计才会出现的情况吧。
没有主键,怎么非常明确简单的定位一条数据? 写 sql,查找删除的时候,估计多谢几次就像打人了 |
6
amon 2020-03-19 10:29:50 +08:00
同意,这是典型没有经验的设计体现,或者说压根没有设计。
|
7
fancy111 2020-03-19 10:32:51 +08:00 1
我觉得唯一的可能就是为了防止名字重复,设计者为了偷懒不在后端验证,直接数据库禁止。
比如注册时提交的姓名重复,一般做法是先 select 数据库判断重复,再插入。它这样可以省去查询这一步,直接 insert,失败成功一目了然。 而为什么要把 A 表分离出来,也是为了查询性能。有好有坏吧,主要看网站业务。 另外 A 表应该不会加 age 字段,只会加不常修改的。 |
8
Jooooooooo 2020-03-19 10:33:49 +08:00
@fancy111 防止重复弄成唯一键就好了, 主键 id 还是不能省的.
|
9
IMCA1024 2020-03-19 10:35:07 +08:00
问题非常大。。
更新岁数,物理删除数据,同名怎么办,除非你告诉我这个不用更新岁数和不删除 |
10
fancy111 2020-03-19 10:35:49 +08:00
@Jooooooooo 既然用不到 ID,那当然可以省。
|
11
littleylv 2020-03-19 10:36:14 +08:00
@fancy111 #7 数据库层面的防止重复可以用 unique index i_name_age(name, age)
|
12
chendy 2020-03-19 10:36:52 +08:00 1
这就是瞎搞,鉴定完毕
|
14
littlewing 2020-03-19 10:38:36 +08:00
不一定需要自增主键
但以 name + age 作为联合主键是谁出的馊主意... ... 你至少给我用个身份证号,手机号什么的吧 |
15
wysnylc 2020-03-19 10:39:18 +08:00
要,而且主键不要与业务关联
|
16
loading 2020-03-19 10:39:38 +08:00 via Android 9
别的不提,真在数据库里面用 age ?每年改一次?
|
17
littlewing 2020-03-19 10:40:48 +08:00
@fancy111 自增主键对于 MySQL InnoDB 来说,插入性能和空间利用率更高。另外,如果要更新 name 或 age,更新主键比更新非主键效率低
|
18
littleylv 2020-03-19 10:40:55 +08:00
@fancy111 #13 表面上来看当然没问题,作为简单的系统设计省掉 id 当然没问题
不过,你可以试试下次在你的工作里这么设计表试试,我看看你同事打不打你 |
19
DamonLin 2020-03-19 10:41:12 +08:00
肯定要啦,不然编辑更新删除问题很大,新增插入倒没啥
|
20
wangbenjun5 OP |
21
fx 2020-03-19 10:42:40 +08:00
大学生水平的人写的
|
22
Vegetable 2020-03-19 10:43:13 +08:00
非自增的主键插入时一定要检查是否唯一,这个损失就洗不了。
|
23
wangbenjun5 OP @ytmsdy 唉,反正现在数据库里面大部分都是这种设计,也有极少部分是有 id 的,估计不是一个人写的,木已成舟
|
24
FragmentLs 2020-03-19 10:44:22 +08:00
不说 id,看到 age 就被吓哭了...这一定是被外在和内在因素影响深思熟虑挣扎出来的结果!
|
25
wangbenjun5 OP |
26
hbolive 2020-03-19 10:45:13 +08:00
一般情况下,表是需要一个 ID 字段标识唯一一条数据记录,这个 ID 不一定跟业务发生关联。
|
27
fancy111 2020-03-19 10:47:47 +08:00
@littlewing 自增主键对于 MySQL InnoDB 来说,插入性能和空间利用率更高? 你想当然是吗?谁告诉你自增插入性能高?
更新的话我已经说了,这个表肯定不会这样设计。 |
28
fancy111 2020-03-19 10:49:51 +08:00
@littleylv 看清楚我的回答,我并没说这个设计就好。数据库的设计本身就是跟项目结合的,哪个性能好选哪个。搞编程的思维都这么死板的话没啥发展前途。
|
29
heyjei 2020-03-19 10:51:05 +08:00
你举得例子叫业务主键(只不过你举得例子不好),自增的 ID 是逻辑主键。
做数仓的一般比较倾向于业务主键,应用系统的喜欢逻辑主键。 设计良好的使用业务主键的系统,看上去真舒服!!! |
31
wangbenjun5 OP @heyjei 额,怎么说呢,这个业务确实不是什么 Web 应用,偏内部系统,但是有一部分内容需要白屏化,所以涉及到一些增删改查,没有 id 主键确实头疼
|
32
lsls931011 2020-03-19 10:54:53 +08:00
不用主键 ID, 会让 mysql 插入的时候进行随机写入排序,毕竟 B-tree 索引是按顺序存储的, 而且以后也不利于运用到聚簇索引和覆盖索引啊。 除非你用的不是 innodb 引擎,而是其他的😓
|
33
heyjei 2020-03-19 11:00:44 +08:00
|
34
ZSeptember 2020-03-19 11:03:26 +08:00
主键还是要的。
|
35
smallpython 2020-03-19 11:06:48 +08:00
看起来像是把 mysql 当成 mongo 在用了, 这样做的好处就是查询快吧, 比如我查 B 表的时候就直接可以知道对应的 name 而不是要和 A 表关联一次才知道 name
|
36
tant 2020-03-19 11:11:43 +08:00 8
mysql 的 innodb 是必须的,其他的数据库可以建议,我个人只对 MySQL 比较了解,供你参考:
InnoDB 引擎表是基于 B+树的索引组织表; InnoDB 会选择主键作为聚集索引、如果没有显式定义主键,则 InnoDB 会选择第一个不包含有 NULL 值的唯一索引作为主键索引; 数据记录本身被存于主索引(一颗 B+Tree )的叶子节点上。这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放,因此每当有一条新的记录插入时,MySQL 会根据其主键将其插入适当的节点和位置,如果页面达到装载因子( InnoDB 默认为 15/16 ),则开辟一个新的页(节点) 如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页; 如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置,此时 MySQL 不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过 OPTIMIZE TABLE 来重建表并优化填充页面; 以上是《高性能 MySQL 》中的原话 大概意思就是:InnoDB 表的数据写入顺序能和 B+树索引的叶子节点顺序一致的话,这时候存取效率是最高的,而主键可能不是递增关系时(例如字符串、UUID、多字段联合唯一索引的情况),该表的存取效率就会比较差。 所以一般 mysql 又用 innodb 的情况下一律加上自增 id,如果你说的两个表,B 表是 A 表的附属表,B 表的 ID 可以不设置自增,直接存 A 表的 ID 作为主键即可 |
37
l3n641 2020-03-19 11:12:02 +08:00
大部分的情况下是需要的 id 作为 primary key 的.如果表是 inodb 引擎的话,他是通过主键聚集数据的, 通过联合索引做主键,会增加 mysql 插入数据的时间,而且索引更大.而且会导致页分裂和碎片话.
|
38
Jooooooooo 2020-03-19 11:13:29 +08:00
@fancy111 瞎搞, 扩展性什么的完全不考虑. 之后要往外扔数据也用 name+age 当实体标记别人会用奇怪的眼神看你的
|
39
arjen 2020-03-19 11:14:43 +08:00 via Android 1
大学生都不这么写,别黑我们大学生...
|
40
zunceng 2020-03-19 11:14:47 +08:00
遵循数据库范式到哪个程度
业务简单的话都行 如果业务比较复杂设计的人出来谢罪 |
41
opengps 2020-03-19 11:15:57 +08:00
可以不用主键。
但是不用主键的时候,聚集索引列的设置一定要合理。 另外会引发某些其他问题:例如 EF 对无主键表不能自动代码生成 看我的使用案例(虽然是 SQL server ): https://www.opengps.cn/Blog/View.aspx?id=284&from=v2ex |
42
rockyou12 2020-03-19 11:19:20 +08:00
复合主键其实设计上,在很多业务中是最优解( lz 这个当然不合适)。但确实容易出性能问题,我觉得和 mysql 本身不优化性能过烂也不无关系……
|
43
aliasliyu4 2020-03-19 11:21:13 +08:00
开心就好
|
44
aliasliyu4 2020-03-19 11:21:56 +08:00
可以试试不要 痛过才知道香
|
45
optional 2020-03-19 11:24:29 +08:00
小项目无所谓,多人项目要做好被喷的准备。
|
46
crist 2020-03-19 11:27:46 +08:00
你这是在瞎搞!
|
47
bk201 2020-03-19 11:28:06 +08:00
主键和业务相关,问题很多。
|
49
gamexg 2020-03-19 11:34:39 +08:00
我碰到过没有主键的...
|
50
xiaowangge 2020-03-19 11:34:54 +08:00 1
阿里巴巴《 Java 开发手册》
[强制] 表必备三字段:id, create_time, update_time。 说明: 其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。 create_time, update_time 的类型均为 datetime 类型 https://github.com/alibaba/p3c/blob/master/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C%EF%BC%88%E5%8D%8E%E5%B1%B1%E7%89%88%EF%BC%89.pdf |
51
ajaxfunction 2020-03-19 11:35:25 +08:00
典型的小学生
好奇写法 而且代码变量名还能用中文呢, 没想到吧.jpg |
52
passerbytiny 2020-03-19 11:38:41 +08:00
@tabris17 #27 你的理解完全反了,顺序主键提高的是 select 效率,但降低 insert 效率。随机写入不需要寻址,不需要考虑重复、乱序、并发等问题,明显要比顺序写入快。
@wangbenjun5 你的理解也反了,单列(字段)主键,从来都不是数据库设计或者数据库范式的要求,而是程序业务设计或者性能优化(更多的是后者)的要求,数据库范式甚至都没要求必须有主键。业务设计上要求单字段主键也是被逼无奈的被动要求。不管是为了识别还是关联,对象总得有 ID ;单字段 ID 明显比多字段复合 ID 舒服多了; ID 不能轻易修改:所以业务设计就给了个不成熟的规范:对象必须有一个业务无关的单字段 ID。这个规范有一个缺点,不是多了个字段,而是对象原本业务相关的自然 ID 被掩盖掉了。总归来说,加一个业务无关 ID,优点大于缺点,所以这个规范就这么用下来了。但是要记住这是程序设计规范,不是数据库设计规范。 |
54
passerbytiny 2020-03-19 11:50:26 +08:00
@xiaowangge #44 一刀切 /懒政规范,就别搬出来了,徒让人笑话。举几个例子:用户表的主键叫做 user_id 是不是违反规范;是用 create_time 同时表示注册时间和记录创建时间,还是用 register_time、create_time 分别表示注册时间、记录创建时间;历史记录类型数据只插入不更新,放个 update_time 字段是不是脱裤子放屁。
|
55
assad 2020-03-19 12:09:16 +08:00
不想要主键的人,想啥呢
|
56
sadfQED2 2020-03-19 12:09:28 +08:00 via Android
@tant 补充一点,如果没有显示定义自增 id,会自动选择第一个不含空值的唯一索引当主键。但是这个也没有的时候,MySQL 应该会自动生成一个隐藏的自增字段当主键
|
57
tabris17 2020-03-19 12:17:31 +08:00
|
59
love 2020-03-19 13:10:11 +08:00
复合主键也是主键,也挺常用,比如用在关联表中。
只是在你这里的 name+age 不适合而已。 |
60
encro 2020-03-19 13:20:31 +08:00
innodb 没有主键,插入速度慢。
|
61
jswh 2020-03-19 13:57:24 +08:00
逻辑上没问题,表小当然可以这么玩。自增主键的好处是插入的时候是顺序的。
|
62
yjxjn 2020-03-19 14:15:26 +08:00
@assad 其实楼主举的例子不恰当,之前做过的项目,一些关联表,确实没有主键,确实拿的就是两个字段合并做一个的,业务表里面,我见过,比如省份名字, province_name,city_name,postcode,各种关联, 确实没有主键,还有一种就是设计工资等级,比如 1W-2W 是 A 级别,2-3WB 级,这样的。。。当时做项目好奇,但是还是不建议这么做,检索的时候,性能那叫一个垃圾。
|
63
littlewing 2020-03-19 14:21:17 +08:00 via iPhone
@fancy111 31 楼的 tant 已经解释的非常清楚了,你可以参考
|
64
Lonely 2020-03-19 14:31:42 +08:00 via iPhone
@passerbytiny 你也别出来回复了,徒让人笑话。
|
65
arry 2020-03-19 14:53:51 +08:00
@passerbytiny innodb 在插入数据的时候本身就是有序的,所以才需要创建一个自增 id,否则每次新增一个记录都需要做移动数据,页分裂等操作,缓存空间中的数据也需要频繁更新。
|
67
iConnect 2020-03-19 14:57:03 +08:00 via Android
联合主键一般是别的表的主键联合起来用,不是你上面的这个场景
|
68
v2Geeker 2020-03-19 15:01:32 +08:00
没有主键 mysql 内部也会给你创建一个~
|
69
passerbytiny 2020-03-19 15:19:13 +08:00
@tabris17 #50 @Lonely #56 两位这是做过什么事,@我是没提醒的。
至于 tabris17 你的回复,你的目光局限到了 InnoDB 引擎上面。热点问题那可不是极端情况,在大数据存储上可是首要问题,关系数据库上热点问题仍然存在,只不过数据量小的时候无需关注罢了,数据量一大照样得使用表分区来避免热点。InnoDB 引擎下若真因为非顺序主键拆分页造成性能灾难,那主因是 InnoDB 引擎而不是顺序主键,难不成 InnoDB 引擎要告诉所有人“我太娇贵,那些用 UUID 当主键的我伺候不了”。 正好又来了 @arry 的新通知,那就一并回复了,性能跟业务逻辑之间,是要做取舍的,不能为了微小的性能提升而牺牲过多业务逻辑的简洁性。现在 InnoDB 要求顺序主键,业务上需要自增主键来自动生成主键,二者正好相互支持。但如果哪天业务上需要把主键换成 UUID 了,要么 InnoDB 引擎能保证性能损失不大,要么就要换掉 Mysql 了。 需要说明一点,本人不是数据库开发者,或者表面是 Java 本质是 SQL 开发的程序员,我推崇的是用 JPA/Hibernate 完全隐藏掉任何数据库细节,并且在主键选择上倾向于 UUID 和自然主键。 |
70
wmhx 2020-03-19 15:45:33 +08:00
很多表比如就用身份证来做主键了, 手机号码了, 也不一定非要多一个 ID 啊, 理解了, 做表关联还是非常方便的, 否则 FID 多了, 真不容易记住.
|
71
saulshao 2020-03-19 15:45:41 +08:00
我倾向于有 ID 主键的原因主要是基于实践。其实如果一个人能预测未来,那从逻辑上看有没有 ID 主键根本无所谓。
但是问题在于没人能预测未来,今天你建了一个表以 name+出生地来标识一个确定的人,明天可能就需要根据这个表来扩展出一堆的表来存储别的什么东西。要是万一前面那个表需要改成 Name+出生地+出生日期才能标识一个确定的人。 我实在就想不出来到底该怎么处理后面那堆派生出来的表了....... |
72
wangkun025 2020-03-19 15:49:02 +08:00
性能冗余的话,只要不出错就行。
|
73
ocean1477 2020-03-19 15:52:49 +08:00
官方推荐需要有自增主键,数据紧凑,页分裂操作少。
|
74
arry 2020-03-19 15:54:14 +08:00
@passerbytiny 用自增主键并不需要牺牲多少业务简洁性,至少在我见过的代码中,直接显式使用主键来进行业务操作的非常少。所以几乎不存在业务上把自增主键改成 uuid 这种情况,反而是自然主键这种和业务强关联的字段,会因为业务的变更而产生重复 冲突的情况。
而使用 uuid 这种情况就更不能理解了,使用 uuid 必然会使得效率降低,而且占用更多的空间。 |
75
passerbytiny 2020-03-19 16:17:32 +08:00
@arry #66 你别告诉我你们系统中基本没有 findById、editById、deleteById 之类的方法。业务变更必然要修改代码,有冲突解决就行了,为了避免将来可能有的冲突而现在不用自然主键,这属于超前设计,现在敏捷开发盛行,已经不受推崇了。你大概没做过更换系统时候的数据割接,那时候只有 UUID 主键是不会冲突的,自增主键和自然主键都会冲突(但是,若提前将主键定位 UUID 也是超前设计,不推荐)。
我推崇 UUID 的原因是它是最省事的主键。自增主键或序列主键都依赖数据库,同时,相比于有顺序有意义的数值,毫无顺序毫无意义的 UUID 才更像 ID。最后那一句就不回了,前面已经说得很清楚了。 顺便提一下,抛开数据库往外面看,git、docker 都是用 UUID 当主键的。 |
76
reedthink 2020-03-19 16:38:56 +08:00
主键必须和数据无关,这不是常识吗=。=
竟然还有人这样写,服了 |
77
killerv 2020-03-19 16:56:28 +08:00
#76 楼上的建议很正确,主键必须和业务无关。而且如果不是百分之百确定不需要主键,那就加上。
|
78
cobola 2020-03-19 18:28:42 +08:00
我喜欢 shortid uuid 太长 不舒服
|
79
qq1004108488 2020-03-19 18:34:20 +08:00
@passerbytiny 我却是觉得用 user_id 真的是设计有问题,你的表名已经指定是的 user 表,然后 id 还要叫 user_id,这是很多余的写法。
|
80
fox0001 2020-03-19 19:15:10 +08:00 via Android
复合主键,数据更新后,重建索引是噩梦
|
81
CEBBCAT 2020-03-19 19:15:47 +08:00 via Android
这个帖子发得很一般,一来是举的例子时候被发现是不恰当的例子,二来是这帖子根本不是在探讨,只是在吐槽
|
82
cabing 2020-03-19 19:47:35 +08:00
这种写法很棒 :)
|
83
cabing 2020-03-19 19:49:08 +08:00
补充下,真是需要脑洞的啊。
|
84
tairan2006 2020-03-19 21:14:12 +08:00
肯定是用自增啊。。楼主说的这个更适合用复合唯一索引。
|
85
reeco 2020-03-19 21:57:29 +08:00
曾经我有一张单表撑了 1 亿多的数据,大量重复的数据在里面,要筛选出来删除。啥索引都不好使了,只能通过自增 id 遍历,加载一部分到内存里慢慢筛选,慢慢删除。那一刻我真的感谢当时用了 id 自增这玩意
|
86
whywhywhy 2020-03-19 22:17:48 +08:00 via Android
我之前用过一个小 erp,没有主键,我的 sql 编辑器为了防止出错,在更新内容的时候采用 where 所有字段,然后还 top 1,感觉太不可思议了
|
87
wangchonglie 2020-03-19 22:30:12 +08:00
@wmhx #70 选取主键的一个基本原则是:不使用任何业务相关的字段作为主键。因为主键是用来唯一定位记录的,修改了主键,会造成一系列的影响。因此,身份证号、手机号、邮箱地址这些看上去可以唯一的字段,均不可用作主键。
|
88
Aresxue 2020-03-19 23:31:02 +08:00
elasticsearch 就是这么做的。。。主键的作用是作为数据库组织数据用的,以 mysql 为例默认的 innodb 使用聚簇索引, 决定了数据的位置, 辅助索引也都会带上主键信息, 所以选择随着时间递增且占用空间较小的主键比较符合时间局部性原理,能够充分利用磁盘预读
|
89
eggache0914 2020-03-20 01:23:53 +08:00
这同名咋办?。。。。
|
90
learningman 2020-03-20 01:28:29 +08:00 via Android
@fx 选修过数据库设计就不会干这种事。。。
|
91
xingheng 2020-03-20 02:41:18 +08:00
头一次见识这样的“附属表”设计,真是“涨姿势”了
|
92
bzj 2020-03-20 06:45:44 +08:00
有些表是可以不用主键的,楼主学一下 EAV 模型
|
94
luzihang 2020-03-20 06:47:25 +08:00 via iPhone
K 线数据,可以用复合,股票代码➕时间
|
95
justin2018 2020-03-20 09:59:41 +08:00
阿里新零售数据库设计与实战
非推广 |
96
nesuk 2020-03-20 10:02:05 +08:00
必须要自增主键,没有商量的余地,也没有必要在这方面耗费精力。
|
97
MonoLogueChi 2020-03-20 13:06:58 +08:00 via Android
MySQL 墙裂推荐使用自增主键,非自增主键效率会差很多,没有主键更差,其他数据库的话,有的可能对非自增主键支持好一点,效率上不会比自增主键差太多
|
98
realpg 2020-03-20 20:08:11 +08:00
按需而论。
并不是所有的项目都需要自增 ID 或者 UUID 主键。 对于简单项目的部分场景,直接用复合主键同时约束组合唯一性,大数据量节省资源也正常。 |
99
passerbytiny 2020-10-16 17:50:24 +08:00 via Android 1
最近在设计事件存储,看了 mysql 索引的数据结构,搜索时把这贴给搜出来了,然后需要纠正一些自己之前回复的错误。
InnoDB 引擎中,插入数据时,顺序(不一定要自增)主键确实比离散主键好。因为:1,InnoDB 引擎的表的背后存储就是聚集索引,通常是一个以主键值为 key 的 B +树,主键索引结构直接影响表数据的文件结构; 2,虽然插入总会导致 B +树分裂,但顺序插入时只在一侧分裂,而离散插入时随机分裂; 3,若 B +树只在一侧分裂则它的终端节点是有序增加的,进而可以与磁盘上连续的磁盘分页对应起来,从而可以连续写入磁盘,否则就只能随机写入磁盘(会增加 IO 时间,制造磁盘碎片)。 但是我仍然推荐用 UUID 而不是自增作为主键。因为:1,自增主键不具备业务性; 2,虽然通过层超模型+ Hibernate 可以实现自增代理主键加业务自然主键的双主键模式来完美解决,但是这样实现的门槛不低; 3,非大量数据时,离散主键并没有损失多少性能,而到讨论性能的时候往往也要分布式了,那时候也用不了自增主键,要用雪花主键。 |