技术解析

数据库设计太拉跨被喷了。
0
2021-06-03 22:39:47
idczone

发现都是些智障问题,总结一下。

  • 表内创建人字段和更新人字段,只存了 name,没有存人的 Id 。当时想着是应该只关心是谁做的操作,所以没有加入 id 的关联关系。但是实际上在表设计中有关联关系但是不存关联信息的数据干脆就不存,要存就都存。

  • 关联表的索引关系一定要明确,该做约束的约束。该设置必输的设置必输。

  • 部分在代码中或者项目里比较高频的字段,表设计时最好加_info 。

  • 英文尽量要合理 比如创建人不应该是 create_name 合理的应该是 creator_name,这块当时设计的时候想的是 creator_name,但是为了和创建时间和更新时间对应就统一变成了 create_name 和 lst_name 。

目前统计下来应该都是类似的问题。我数据库设计确实拉跨,可能是因为之前做的东西过度松散,对于数据库设计基本都是能用就行,关联表的约束关系可能都美国服务器没有做全,全靠代码来做。还有很多细节上的问题。其实每次开发都是很快很简单,但是表设计真的让我头大。


有没有朋友分享点 常见的数据库设计的问题

站在全局的角度和第三者的角度 考虑下这个设计
如果是多人开发 一个人先出一版 然后一起讨论下 可通常这个都是架构师处理的
如果是自己开发 等多来几次就可以了

这块有啥靠谱的书推荐吗?同苦恼

你说的这些都不是问题,只能说喷你的人洁癖太严重了。真要抠起来还得细分,你的 create_time 是什么类型呢,datetime 还是 timestamp 。那对应的要叫 create_datetime, create_timestamp

lst_name 也费脑。

我只在数据库概论里读过一章数据库设计的,其他的也求下,实战类的

时间的话我是倾向于 created_at, updated_at

确实是有点的,他说受不了这种哈哈哈

1 、保持 sql 脚本风格的统一,对齐属性以便阅读
2 、相同字段使用相同的名称、类型和说明
3 、字段保持有序,不要 A 表 a 字段在前 b 字段在后而 B 表则 b 字段在前 a 字段在后
4 、适当冗余,譬如在保存客户 ID 的时候也保存客户名称,减少联表查询
5 、建表的同时规划好索引
6 、使用合适的字段类型,但不必过度考虑存储空间

这个 lst_name,第一眼看成数字 1 然后读成了 first name 。 然后定睛一看貌似是个小写 l 然后读成 last name 。 然后纠结了 10 秒到底是哪个。。。 LZ 真是起名专家

你这个问题很好解决 上 mongodb
加字段完事

害……很少有人嫌弃字段命名过长,被嫌弃的只有词不达意

第一点感觉没有改到点上。
数据关联的时候,真正关联的其实是 id 字段,其他如 name 等等都是可以连接查询到的。
所以,必须在主表记录的是 id,是否存 name 看你对性能和存储空间的要求来取舍。
你只记名字,遇到重名的人就完蛋。

你说的这几条,除了第一条在某种严格环境下必须外,其他都是你们团队代码风格的事情

其他几个硬要那么理解也可以说得通,比较高频的字段表设计时最好加_info 是什么目的?

hhhh 因为定义的 更新时间是 lst_date_time,我为了对应上就智障的写下了 lst_name

是的,表有关联关系一定要用 id,name 这种只是为了减少联表查询做的冗余,其实是可以不要的。没想到这个

last 就比 lst 多一个字母吧。。有必要搞这种缩写吗?

意思是有些字段概念比较高频,比如 operator,property,这种的直接拿来当表名会比较尴尬,所以加一个 info,大概是这个意思,也可能是我没有理解到位

我觉得是没必要的,不过团队一开始统一了 创建时间和更新时间的字段定义,所以就沿用下去了

了解

本来有收藏数据库设计的文章,但是传送门关站以后,收藏的就是个死链接了……

笑哭,这是哪个智障定的规范

可惜,数据库设计一直是我的痛

用 Ruby on Rails 的标准规范就行了。
https://ruby-china.github.io/rails-guides/active_record_basics.html

创建人 ID,修改人 ID,创建时间,修改时间,删除标记位(不需要的就不加)常规都加,表字段常用的和非常用的分开设计_core(核心),_extends (拓展)

库表设计方面的话题太大了,不过基本上可以从这几个方面考虑:
1. 字段命名,
2. 字段类型的选择
3. 数据模型的设计(业务对象如何存,存几张表,如何冗余)
4. 业务对象关联关系的设计(业务对象的关系怎么存等)
5. 索引的设计
6. 存储引擎的选择
7. 库表容量的规划(数据规模多大,增长趋势如何,是否考虑分库分表)
8. 团队的规范
9. 行业最佳实践
暂时想到这些

数据库中一直有一个建表的模板, id, create_at, create_by, update_at, update_by, delete_at, delete_by, is_deleted
随用随取

看下阿里的 mysql 规范文档呗

虽然我也比较拉跨,但是我真的 lz 比我还 拉跨
lst_name 我也想到应该是 last_name 但是我不敢肯定,不知道为啥会这样设计,少一个 a 有啥用?
只存 name 不存 id 我也不懂是干啥的,如果有重名怎么办,如果不光需要知道 对方的名字还需要其他的信息怎么办?用中文关联吗?
肯定是先关联 ID 然后在考虑要不要保存 name
约束,很多都说 不要做,特别是外键约束,会影响性能
实在不知道怎么弄好的时候,就 看看 框架默认的是什么,比如 laravel 的框架 时间默认都是 created_at updated_at deleted_at
关联都是 表名单数_id

先画 ER 图,再生成表
很多时候没有标准答案的,比如你说的存 id 这事,如果要尽量简单无冗余,就只存 id,提高性能简化查询就顺手缓存 name,而一些场景考虑到 name 可能变化甚至删除,你需要知道这里是要修改时的 name 还是最新的 name 。这不是能轻易判断对错的问题,全看对业务需求的理解了。

平时多看,自己设计的时候,多换位思考,多反思,每个人都有自己的风格,无可厚非,但是看得多了,就知道什么样的设计是好的,什么样的是差的。

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

是这块设计确实没有过脑子嗨,在反省了

1. 关联的字段肯定是用主键 id 啊,一个表的字段除了主键 id,其它都是有可能会变的。
2. 常用的约束一般就是非空和唯一性两个。
3. create_by update_by create_time/create_at update_time/update_at
4. 布尔型的习惯用 xxx_flag,如软删除标记 delete_flag

完全可以推算出最恰当的组织形式,为什么要自己拍脑袋做转换?
https://gist.github.com/no1xsyzy/5391a79203b0ee64aec7c7de46b028f2

阿里 Java 手册

阿里的 MySQL 规约可以参考下: https://github.com/alibaba/p3c/tree/master/p3c-gitbook/MySQL%E6%95%B0%E6%8D%AE%E5%BA%93

难道只有我一个人,第一版数据设计总是不完善,做着做着就改呗。

请专业的 DBA 设计, 否则后续肯定还要重新设计。

小公司没有 DBA,都是写业务的自己建表

create_name 我可能会用 create_user

更新人可能会用 up_user 或者 update_user ?

目前在用的一套感觉比较好的命名规范
n_sol
n_ur
cd_sx
vu_ail
nu_pne
(手动狗头)

五大金刚,无脑加上去就好了
status
created_at
update_at
created_user
update_user,关联直接存主键

created_by

今天还因为数据库设计,跟同事讨论了一下午,太拉跨了......
完全没有考虑到以后业务扩展,随便增加一个需求,就需要重新设计数据库和业务操作....

lst_nme,这种的都见多了。。。一度让我认为是人家手滑少打一个,原来是专门这么缩写的。

三范式嘛

我看没人说这个,但是遵守这个比较好
数据库的 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)

写好字段描述,status 各种条件描述,不然,我原地爆炸给你看

参考一下我的模型文件:[app/Models/Course.php]( https://gitee.com/koogua/course-tencent-cloud/blob/master/app/Models/Course.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; // 高级
/**
* array
*
* 点播扩展属性
*/
protected $_vod_attrs = [
'duration' => 0,
];
/**
* array
*
* 直播扩展属性
*/
protected $_live_attrs = [
'start_date' => '',
'end_date' => '',
];
/**
* array
*
* 图文扩展属性
*/
protected $_read_attrs = [
'duration' => 0,
'word_count' => 0,
];
/**
* array
*
* 面授扩展属性
*/
protected $_offline_attrs = [
'start_date' => '',
'end_date' => '',
'user_limit' => 30,
'location' => '',
];
/**
* 主键编号
*
* int
*/
public $id = 0;
/**
* 标题
*
* string
*/
public $title = '';
/**
* 封面
*
* string
*/
public $cover = '';
/**
* 简介
*
* string
*/
public $summary = '';
/**
* 关键字
*
* string
*/
public $keywords = '';
/**
* 详情
*
* string
*/
public $details = '';
/**
* 主分类编号
*
* int
*/
public $category_id = 0;
/**
* 主教师编号
*
* int
*/
public $teacher_id = 0;
/**
* 原始价格
*
* float
*/
public $origin_price;
/**
* 优惠价格
*
* float
*/
public $market_price = 0.00;
/**
* 会员价格
*
* float
*/
public $vip_price = 0.00;
/**
* 学习期限(月)
*
* int
*/
public $study_expiry = 12;
/**
* 退款期限(天)
*
* int
*/
public $refund_expiry = 7;
/**
* 用户评价
*
* float
*/
public $rating = 5.00;
/**
* 综合得分
*
* float
*/
public $score = 0.00;
/**
* 模式类型
*
* int
*/
public $model = self::MODEL_VOD;
/**
* 难度级别
*
* int
*/
public $level = self::LEVEL_JUNIOR;
/**
* 扩展属性
*
* array|string
*/
public $attrs = [];
/**
* 推荐标识
*
* int
*/
public $featured = 0;
/**
* 发布标识
*
* int
*/
public $published = 0;
/**
* 删除标识
*
* int
*/
public $deleted = 0;
/**
* 资源数
*
* int
*/
public $resource_count = 0;
/**
* 学员数
*
* int
*/
public $user_count = 0;
/**
* 课时数
*
* int
*/
public $lesson_count = 0;
/**
* 套餐数
*
* int
*/
public $package_count = 0;
/**
* 咨询数
*
* int
*/
public $consult_count = 0;
/**
* 评价数
*
* int
*/
public $review_count = 0;
/**
* 收藏数
*
* int
*/
public $favorite_count = 0;
/**
* 创建时间
*
* int
*/
public $create_time = 0;
/**
* 更新时间
*
* 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 天',
];
}
}
```

所以 lst name 是啥意思?

lst -> 1st -> first name (逃

update_xxx 就没必要了,更新又不会是唯一的。我都是通过统一的操作日志进行审计的。

上 orm 框架,学习下 orm 框架采用的命名规则

lst_name 节省了一个字符,结果引发各种猜
last_name?
list_name?

若是用拼音首字母作字段名,怕不是要被喷的体无完肤~

我干过 /狗头
数据地带为您的网站提供全球顶级IDC资源