发现都是些智障问题,总结一下。
表内创建人字段和更新人字段,只存了 name,没有存人的 Id 。当时想着是应该只关心是谁做的操作,所以没有加入 id 的关联关系。但是实际上在表设计中有关联关系但是不存关联信息的数据干脆就不存,要存就都存。
关联表的索引关系一定要明确,该做约束的约束。该设置必输的设置必输。
部分在代码中或者项目里比较高频的字段,表设计时最好加_info 。
英文尽量要合理 比如创建人不应该是 create_name 合理的应该是 creator_name,这块当时设计的时候想的是 creator_name,但是为了和创建时间和更新时间对应就统一变成了 create_name 和 lst_name 。
目前统计下来应该都是类似的问题。我数据库设计确实拉跨,可能是因为之前做的东西过度松散,对于数据库设计基本都是能用就行,关联表的约束关系可能都没有做全,全靠代码来做。还有很多细节上的问题。其实每次开发都是很快很简单,但是表设计真的让我头大。
1
Renco OP 有没有朋友分享点 常见的数据库设计的问题
|
2
puzzle9 2021-04-23 14:23:03 +08:00
站在全局的角度和第三者的角度 考虑下这个设计
如果是多人开发 一个人先出一版 然后一起讨论下 可通常这个都是架构师处理的 如果是自己开发 等多来几次就可以了 |
3
leonme 2021-04-23 14:24:39 +08:00 via iPhone
这块有啥靠谱的书推荐吗?同苦恼
|
4
hahasong 2021-04-23 14:28:46 +08:00
你说的这些都不是问题,只能说喷你的人洁癖太严重了。真要抠起来还得细分,你的 create_time 是什么类型呢,datetime 还是 timestamp 。那对应的要叫 create_datetime, create_timestamp
|
5
wangkun025 2021-04-23 14:29:13 +08:00 3
lst_name 也费脑。
|
6
raaaaaar 2021-04-23 14:32:56 +08:00 via Android
我只在数据库概论里读过一章数据库设计的,其他的也求下,实战类的
|
7
lyz1990 2021-04-23 14:33:59 +08:00 6
时间的话我是倾向于 created_at, updated_at
|
9
xuanbg 2021-04-23 14:36:38 +08:00
1 、保持 sql 脚本风格的统一,对齐属性以便阅读
2 、相同字段使用相同的名称、类型和说明 3 、字段保持有序,不要 A 表 a 字段在前 b 字段在后而 B 表则 b 字段在前 a 字段在后 4 、适当冗余,譬如在保存客户 ID 的时候也保存客户名称,减少联表查询 5 、建表的同时规划好索引 6 、使用合适的字段类型,但不必过度考虑存储空间 |
10
pckillers 2021-04-23 14:41:01 +08:00 9
这个 lst_name,第一眼看成数字 1 然后读成了 first name 。 然后定睛一看貌似是个小写 l 然后读成 last name 。 然后纠结了 10 秒到底是哪个。。。 LZ 真是起名专家
|
11
soulzz 2021-04-23 14:44:35 +08:00
你这个问题很好解决 上 mongodb
加字段完事 |
12
ParfoisMeng 2021-04-23 14:45:37 +08:00 5
害……很少有人嫌弃字段命名过长,被嫌弃的只有词不达意
|
13
shyrock 2021-04-23 14:46:34 +08:00
第一点感觉没有改到点上。
数据关联的时候,真正关联的其实是 id 字段,其他如 name 等等都是可以连接查询到的。 所以,必须在主表记录的是 id,是否存 name 看你对性能和存储空间的要求来取舍。 你只记名字,遇到重名的人就完蛋。 |
14
qW7bo2FbzbC0 2021-04-23 14:52:41 +08:00
你说的这几条,除了第一条在某种严格环境下必须外,其他都是你们团队代码风格的事情
|
15
admol 2021-04-23 14:53:45 +08:00
其他几个硬要那么理解也可以说得通,比较高频的字段表设计时最好加_info 是什么目的?
|
16
Renco OP @pckillers hhhh 因为定义的 更新时间是 lst_date_time,我为了对应上就智障的写下了 lst_name
|
17
Renco OP @shyrock 是的,表有关联关系一定要用 id,name 这种只是为了减少联表查询做的冗余,其实是可以不要的。没想到这个
|
19
Renco OP @admol 意思是有些字段概念比较高频,比如 operator,property,这种的直接拿来当表名会比较尴尬,所以加一个 info,大概是这个意思,也可能是我没有理解到位
|
22
kamal 2021-04-23 15:07:34 +08:00
本来有收藏数据库设计的文章,但是传送门关站以后,收藏的就是个死链接了……
|
25
nine 2021-04-23 15:12:59 +08:00
用 Ruby on Rails 的标准规范就行了。
https://ruby-china.github.io/rails-guides/active_record_basics.html |
26
huifer 2021-04-23 15:13:03 +08:00
创建人 ID,修改人 ID,创建时间,修改时间,删除标记位(不需要的就不加)常规都加,表字段常用的和非常用的分开设计_core(核心),_extends (拓展)
|
27
xiaochun41 2021-04-23 15:14:44 +08:00
库表设计方面的话题太大了,不过基本上可以从这几个方面考虑:
1. 字段命名, 2. 字段类型的选择 3. 数据模型的设计(业务对象如何存,存几张表,如何冗余) 4. 业务对象关联关系的设计(业务对象的关系怎么存等) 5. 索引的设计 6. 存储引擎的选择 7. 库表容量的规划(数据规模多大,增长趋势如何,是否考虑分库分表) 8. 团队的规范 9. 行业最佳实践 暂时想到这些 |
28
young 2021-04-23 15:17:04 +08:00
数据库中一直有一个建表的模板, id, create_at, create_by, update_at, update_by, delete_at, delete_by, is_deleted
随用随取 |
29
5G 2021-04-23 15:19:17 +08:00 1
看下阿里的 mysql 规范文档呗
|
30
QlanQ 2021-04-23 15:20:11 +08:00
虽然我也比较拉跨,但是我真的 lz 比我还 拉跨
lst_name 我也想到应该是 last_name 但是我不敢肯定,不知道为啥会这样设计,少一个 a 有啥用? 只存 name 不存 id 我也不懂是干啥的,如果有重名怎么办,如果不光需要知道 对方的名字还需要其他的信息怎么办?用中文关联吗? 肯定是先关联 ID 然后在考虑要不要保存 name 约束,很多都说 不要做,特别是外键约束,会影响性能 实在不知道怎么弄好的时候,就 看看 框架默认的是什么,比如 laravel 的框架 时间默认都是 created_at updated_at deleted_at 关联都是 表名单数_id |
31
ksedz 2021-04-23 15:21:41 +08:00
先画 ER 图,再生成表
很多时候没有标准答案的,比如你说的存 id 这事,如果要尽量简单无冗余,就只存 id,提高性能简化查询就顺手缓存 name,而一些场景考虑到 name 可能变化甚至删除,你需要知道这里是要修改时的 name 还是最新的 name 。这不是能轻易判断对错的问题,全看对业务需求的理解了。 |
32
Vegetable 2021-04-23 15:29:13 +08:00
平时多看,自己设计的时候,多换位思考,多反思,每个人都有自己的风格,无可厚非,但是看得多了,就知道什么样的设计是好的,什么样的是差的。
|
33
no1xsyzy 2021-04-23 15:44:42 +08:00
creation_subject_id
creation_subject_user_name creation_complete_time 首先,如果是 canonical complete 的关系型表设计,不应当是一个条目里存储创建者 而应当是记录创建的发生,其主体与客体与完成时间(参考是)。 就你提到的设计来说,item.creator_id 实质上是 creation.subject_id ON creation.object_id=item.id 的嵌入,[id] 相消,应当是 creation_subject_id 我认为应当写成 嵌入表名称 "_" 嵌入表字段名 同理,item.creator_name 其实是 user.name ON user.id=item.creation_subject_id 的嵌入,[id] 相消,所以剩下的是 creation_subject_user_name |
35
NULL2020 2021-04-23 15:59:58 +08:00
1. 关联的字段肯定是用主键 id 啊,一个表的字段除了主键 id,其它都是有可能会变的。
2. 常用的约束一般就是非空和唯一性两个。 3. create_by update_by create_time/create_at update_time/update_at 4. 布尔型的习惯用 xxx_flag,如软删除标记 delete_flag |
36
no1xsyzy 2021-04-23 16:00:55 +08:00
|
38
xhldtc 2021-04-23 16:14:12 +08:00
|
39
buster 2021-04-23 16:31:23 +08:00
难道只有我一个人,第一版数据设计总是不完善,做着做着就改呗。
|
40
jones2000 2021-04-23 16:35:48 +08:00
请专业的 DBA 设计, 否则后续肯定还要重新设计。
|
42
CantSee 2021-04-23 16:58:40 +08:00
create_name 我可能会用 create_user
|
43
CantSee 2021-04-23 17:00:19 +08:00
更新人可能会用 up_user 或者 update_user ?
|
44
dayudayupao 2021-04-23 17:11:53 +08:00 1
目前在用的一套感觉比较好的命名规范
n_sol n_ur cd_sx vu_ail nu_pne (手动狗头) |
45
neptuno 2021-04-23 17:14:39 +08:00
五大金刚,无脑加上去就好了
status created_at update_at created_user update_user,关联直接存主键 |
46
iugo 2021-04-23 17:30:06 +08:00
created_by
|
47
Hallelu 2021-04-23 17:53:40 +08:00
今天还因为数据库设计,跟同事讨论了一下午,太拉跨了......
完全没有考虑到以后业务扩展,随便增加一个需求,就需要重新设计数据库和业务操作.... |
49
ErenJaeger 2021-04-23 18:11:50 +08:00
三范式嘛
|
50
Number13 2021-04-23 18:49:56 +08:00
我看没人说这个,但是遵守这个比较好
数据库的 ACID 四原则及: 事物的原子性(Atomic)、一致性(Consistent)、独立性(Isolated)及持久性(Durable)。 1.事务的原子性是指一个事务要么全部执行,要么不执行.也就是说一个事务不可能只执行了一半就停止了.比如你从取款机取钱,这个事务可以分成两个步骤:1 划卡,2 出钱.不可能划了卡,而钱却没出来.这两步必须同时完成.要么就不完成. 2.事务的一致性是指事务的运行并不改变数据库中数据的一致性.例如,完整性约束了 a+b=10,一个事务改变了 a,那么 b 也应该随之改变. 3.事务的独立性是指两个以上的事务不会出现交错执行的状态.因为这样可能会导致数据不一致. 4.事务的持久性是指事务运行成功以后,就系统的更新是永久的.不会无缘无故的回滚. 摘抄自[传送门-数据库的 ACID 四原则]( https://blog.csdn.net/tiercel2008/article/details/6871961) |
51
mengdodo 2021-04-23 19:15:28 +08:00
写好字段描述,status 各种条件描述,不然,我原地爆炸给你看
|
52
xiaochong0302 2021-04-23 19:34:01 +08:00
参考一下我的模型文件:[app/Models/Course.php]( https://gitee.com/koogua/course-tencent-cloud/blob/master/app/Models/Course.php)
我个人觉得不用写注释都能看得一清二楚 ``` <?php namespace App\Models; use App\Caches\MaxCourseId as MaxCourseIdCache; use App\Services\Sync\CourseIndex as CourseIndexSync; use App\Services\Sync\CourseScore as CourseScoreSync; use Phalcon\Mvc\Model\Behavior\SoftDelete; use Phalcon\Text; class Course extends Model { /** * 模型 */ const MODEL_VOD = 1; // 点播 const MODEL_LIVE = 2; // 直播 const MODEL_READ = 3; // 图文 const MODEL_OFFLINE = 4; // 面授 /** * 级别 */ const LEVEL_ENTRY = 1; // 入门 const LEVEL_JUNIOR = 2; // 初级 const LEVEL_MEDIUM = 3; // 中级 const LEVEL_SENIOR = 4; // 高级 /** * @var array * * 点播扩展属性 */ protected $_vod_attrs = [ 'duration' => 0, ]; /** * @var array * * 直播扩展属性 */ protected $_live_attrs = [ 'start_date' => '', 'end_date' => '', ]; /** * @var array * * 图文扩展属性 */ protected $_read_attrs = [ 'duration' => 0, 'word_count' => 0, ]; /** * @var array * * 面授扩展属性 */ protected $_offline_attrs = [ 'start_date' => '', 'end_date' => '', 'user_limit' => 30, 'location' => '', ]; /** * 主键编号 * * @var int */ public $id = 0; /** * 标题 * * @var string */ public $title = ''; /** * 封面 * * @var string */ public $cover = ''; /** * 简介 * * @var string */ public $summary = ''; /** * 关键字 * * @var string */ public $keywords = ''; /** * 详情 * * @var string */ public $details = ''; /** * 主分类编号 * * @var int */ public $category_id = 0; /** * 主教师编号 * * @var int */ public $teacher_id = 0; /** * 原始价格 * * @var float */ public $origin_price; /** * 优惠价格 * * @var float */ public $market_price = 0.00; /** * 会员价格 * * @var float */ public $vip_price = 0.00; /** * 学习期限(月) * * @var int */ public $study_expiry = 12; /** * 退款期限(天) * * @var int */ public $refund_expiry = 7; /** * 用户评价 * * @var float */ public $rating = 5.00; /** * 综合得分 * * @var float */ public $score = 0.00; /** * 模式类型 * * @var int */ public $model = self::MODEL_VOD; /** * 难度级别 * * @var int */ public $level = self::LEVEL_JUNIOR; /** * 扩展属性 * * @var array|string */ public $attrs = []; /** * 推荐标识 * * @var int */ public $featured = 0; /** * 发布标识 * * @var int */ public $published = 0; /** * 删除标识 * * @var int */ public $deleted = 0; /** * 资源数 * * @var int */ public $resource_count = 0; /** * 学员数 * * @var int */ public $user_count = 0; /** * 课时数 * * @var int */ public $lesson_count = 0; /** * 套餐数 * * @var int */ public $package_count = 0; /** * 咨询数 * * @var int */ public $consult_count = 0; /** * 评价数 * * @var int */ public $review_count = 0; /** * 收藏数 * * @var int */ public $favorite_count = 0; /** * 创建时间 * * @var int */ public $create_time = 0; /** * 更新时间 * * @var int */ public $update_time = 0; public function getSource(): string { return 'kg_course'; } public function initialize() { parent::initialize(); $this->keepSnapshots(true); $this->addBehavior( new SoftDelete([ 'field' => 'deleted', 'value' => 1, ]) ); } public function beforeCreate() { if (empty($this->attrs)) { if ($this->model == self::MODEL_VOD) { $this->attrs = $this->_vod_attrs; } elseif ($this->model == self::MODEL_LIVE) { $this->attrs = $this->_live_attrs; } elseif ($this->model == self::MODEL_READ) { $this->attrs = $this->_read_attrs; } elseif ($this->model == self::MODEL_OFFLINE) { $this->attrs = $this->_offline_attrs; } } if (is_array($this->attrs)) { $this->attrs = kg_json_encode($this->attrs); } if (empty($this->cover)) { $this->cover = kg_default_course_cover_path(); } elseif (Text::startsWith($this->cover, 'http')) { $this->cover = self::getCoverPath($this->cover); } $this->create_time = time(); } public function beforeUpdate() { if (time() - $this->update_time > 3 * 3600) { $sync = new CourseIndexSync(); $sync->addItem($this->id); $sync = new CourseScoreSync(); $sync->addItem($this->id); } if (Text::startsWith($this->cover, 'http')) { $this->cover = self::getCoverPath($this->cover); } if (empty($this->summary)) { $this->summary = kg_parse_summary($this->details); } if (is_array($this->attrs)) { $this->attrs = kg_json_encode($this->attrs); } if (empty($this->origin_price)) { $this->origin_price = 1.5 * $this->market_price; } if ($this->deleted == 1) { $this->published = 0; } $this->update_time = time(); } public function afterCreate() { $cache = new MaxCourseIdCache(); $cache->rebuild(); } public function afterFetch() { $this->origin_price = (float)$this->origin_price; $this->market_price = (float)$this->market_price; $this->vip_price = (float)$this->vip_price; $this->rating = (float)$this->rating; $this->score = (float)$this->score; if (!Text::startsWith($this->cover, 'http')) { $this->cover = kg_cos_course_cover_url($this->cover); } if (is_string($this->attrs)) { $this->attrs = json_decode($this->attrs, true); } } public static function getCoverPath($url) { if (Text::startsWith($url, 'http')) { return parse_url($url, PHP_URL_PATH); } return $url; } public static function modelTypes() { return [ self::MODEL_VOD => '点播', self::MODEL_LIVE => '直播', self::MODEL_READ => '图文', self::MODEL_OFFLINE => '面授', ]; } public static function levelTypes() { return [ self::LEVEL_ENTRY => '入门', self::LEVEL_JUNIOR => '初级', self::LEVEL_MEDIUM => '中级', self::LEVEL_SENIOR => '高级', ]; } public static function sortTypes() { return [ 'score' => '综合', 'rating' => '好评', 'latest' => '最新', 'popular' => '最热', 'featured' => '推荐', 'free' => '免费', ]; } public static function studyExpiryOptions() { return [ 1 => '1 个月', 3 => '3 个月', 6 => '6 个月', 12 => '12 个月', 36 => '36 个月', ]; } public static function refundExpiryOptions() { return [ 0 => '0 天', 7 => '7 天', 14 => '14 天', 30 => '30 天', 90 => '90 天', 180 => '180 天', ]; } } ``` |
53
icylogic 2021-04-23 19:41:35 +08:00 via iPhone
所以 lst name 是啥意思?
|
55
xuanbg 2021-04-23 19:52:52 +08:00
update_xxx 就没必要了,更新又不会是唯一的。我都是通过统一的操作日志进行审计的。
|
56
IvanLi127 2021-04-23 20:49:21 +08:00 via Android
上 orm 框架,学习下 orm 框架采用的命名规则
|
57
vivisidea 2021-04-23 23:01:28 +08:00
lst_name 节省了一个字符,结果引发各种猜
last_name? list_name? |
58
kylix 2021-04-23 23:10:35 +08:00
若是用拼音首字母作字段名,怕不是要被喷的体无完肤~
我干过 /狗头🐶 |
59
h82258652 2021-04-23 23:40:12 +08:00
codefirst 一把梭不香么 /狗头
|
60
iseki 2021-04-24 00:28:49 +08:00 via Android
存 name 不存 id 这完全不可取,虽然逻辑上正确的做法是存 id 不存 name,但是考虑到某些场景下某些数据库拉垮的性能存上 name 也不是不行,不过还是先不要过早优化比较好吧,感觉如果不想连表 join,那就是把 name 塞 Redis 都不是不可以…
看起来加 Redis 加了缓存好麻烦,实际上在数据表里事实上缓存了 name 更麻烦…完全和数据库的设计思想对着干不讨好 |
61
PolarBears 2021-04-24 01:26:46 +08:00
@kylix 我现在在的单位有套古老的系统就是这样,而且还有些是全拼有些是首字母简写,也没有文档根本猜不透.我真希望如果实在不会取名的话直接用中文做字段名不就挺好的吗
|
62
mingl0280 2021-04-24 02:40:46 +08:00 via Android
这不是 SQL 101 那种级别的知识么,随便找一本数据库设计的教科书都告诉你数据库设计三级范式有啥要求照着做就行了,自己瞎猜结果猜了半天是别人教科书就写过的没意思的好么……
|
63
risky 2021-04-24 02:55:14 +08:00
created_at
updated_at created_by |
64
nutting 2021-04-24 07:36:32 +08:00 via Android
我们公司有规范,还得考试学习
|
65
encro 2021-04-24 10:08:23 +08:00
表内创建人字段和更新人字段,只存了 name,没有存人的 Id 。当时想着是应该只关心是谁做的操作,所以没有加入 id 的关联关系。但是实际上在表设计中有关联关系但是不存关联信息的数据干脆就不存,要存就都存。
我这里建议假设 post 表,需要保存更新人,那么增加是 post.update_user_id 就行,除非需要经常 “select fields from post where user.fullname like ?”,那么需要加一个 post.update_user_fullname 字段 关联表的索引关系一定要明确,该做约束的约束。该设置必输的设置必输。 数据库设计的时候一定要按最严格的约束去,但是不要嵌套删除之类的。对于可选的字段,用 null 而不是 0 或者''。 部分在代码中或者项目里比较高频的字段,表设计时最好加_info 。 什么鬼? 有必要吗? IDE 不会自动提示 field ? 用户名表的用户名是高频字段,所以需要 user.name_info 吗? XJB 折腾吧! 英文尽量要合理 比如创建人不应该是 create_name 合理的应该是 creator_name,这块当时设计的时候想的是 creator_name,但是为了和创建时间和更新时间对应就统一变成了 create_name 和 lst_name 。 前面已经有答案。created_at, updated_at,create_user_id,ower_id,admin_user_id,create_username,create_user_fullname 需要根据项目和字段实际用途和存储关系来。 架构设计是门艺术,这门艺术的核心是可维护性,开发效率以及运行效率的恰如其分。 |
66
sockball07 2021-04-24 10:13:20 +08:00
redis 键名可以缩 数据库可真别缩写
|
67
encro 2021-04-24 10:49:04 +08:00
@xiaochong0302
我们的是 const LEVELS = [ self::LEVEL_JUNIOR =>'初级', ... ]; 不用方法,除非需要逻辑判断的。 当然更理想的其他语言的 enum 类型,就少了一层定义,且可以采用位运算符。 use MyCLabs\Enum\Enum; class Gender extends Enum { public const MALE = '男'; public const FEMALE = '女'; } |
68
qile1 2021-04-24 13:53:33 +08:00 via Android
学习啦,我们好多接口都是拼音首字母,那理解起来才酸爽,每次猜字段都骂娘
|
69
Senorsen 2021-04-24 15:47:48 +08:00
尽量不要反太多范式吧,冗余多了到时不一致问题一多很恶心。
命名尽量规范。 |
70
clf 2021-04-25 10:42:49 +08:00
尽量不要在其它数据里冗余更多的非 ID 信息,比如不要在业务表里存用户表的姓名字段,而应该存 ID 。因为两份数据后会导致后期的更新维护成本过高。业务表只放业务数据。
|
71
adocder 2021-04-25 10:53:05 +08:00
别的不说,就第一个存 name 不存 id 的问题,改名了咋整,你跟谁对去
|