V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Renco
V2EX  ›  程序员

数据库设计太拉跨被喷了。

  •  
  •   Renco · 151 天前 · 8698 次点击
    这是一个创建于 151 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

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

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

    71 条回复    2021-04-25 10:53:05 +08:00
    Renco
        1
    Renco   151 天前
    有没有朋友分享点 常见的数据库设计的问题
    puzzle9
        2
    puzzle9   151 天前
    站在全局的角度和第三者的角度 考虑下这个设计
    如果是多人开发 一个人先出一版 然后一起讨论下 可通常这个都是架构师处理的
    如果是自己开发 等多来几次就可以了
    leonme
        3
    leonme   151 天前 via iPhone
    这块有啥靠谱的书推荐吗?同苦恼
    hahasong
        4
    hahasong   151 天前
    你说的这些都不是问题,只能说喷你的人洁癖太严重了。真要抠起来还得细分,你的 create_time 是什么类型呢,datetime 还是 timestamp 。那对应的要叫 create_datetime, create_timestamp
    wangkun025
        5
    wangkun025   151 天前   ❤️ 3
    lst_name 也费脑。
    raaaaaar
        6
    raaaaaar   151 天前 via Android
    我只在数据库概论里读过一章数据库设计的,其他的也求下,实战类的
    lyz1990
        7
    lyz1990   151 天前   ❤️ 5
    时间的话我是倾向于 created_at, updated_at
    Renco
        8
    Renco   151 天前
    @hahasong 确实是有点的,他说受不了这种哈哈哈
    xuanbg
        9
    xuanbg   151 天前
    1 、保持 sql 脚本风格的统一,对齐属性以便阅读
    2 、相同字段使用相同的名称、类型和说明
    3 、字段保持有序,不要 A 表 a 字段在前 b 字段在后而 B 表则 b 字段在前 a 字段在后
    4 、适当冗余,譬如在保存客户 ID 的时候也保存客户名称,减少联表查询
    5 、建表的同时规划好索引
    6 、使用合适的字段类型,但不必过度考虑存储空间
    pckillers
        10
    pckillers   151 天前   ❤️ 9
    这个 lst_name,第一眼看成数字 1 然后读成了 first name 。 然后定睛一看貌似是个小写 l 然后读成 last name 。 然后纠结了 10 秒到底是哪个。。。 LZ 真是起名专家
    soulzz
        11
    soulzz   151 天前
    你这个问题很好解决 上 mongodb
    加字段完事
    ParfoisMeng
        12
    ParfoisMeng   151 天前   ❤️ 5
    害……很少有人嫌弃字段命名过长,被嫌弃的只有词不达意
    shyrock
        13
    shyrock   151 天前
    第一点感觉没有改到点上。
    数据关联的时候,真正关联的其实是 id 字段,其他如 name 等等都是可以连接查询到的。
    所以,必须在主表记录的是 id,是否存 name 看你对性能和存储空间的要求来取舍。

    你只记名字,遇到重名的人就完蛋。
    hjahgdthab750
        14
    hjahgdthab750   151 天前
    你说的这几条,除了第一条在某种严格环境下必须外,其他都是你们团队代码风格的事情
    admol
        15
    admol   151 天前
    其他几个硬要那么理解也可以说得通,比较高频的字段表设计时最好加_info 是什么目的?
    Renco
        16
    Renco   151 天前
    @pckillers hhhh 因为定义的 更新时间是 lst_date_time,我为了对应上就智障的写下了 lst_name
    Renco
        17
    Renco   151 天前
    @shyrock 是的,表有关联关系一定要用 id,name 这种只是为了减少联表查询做的冗余,其实是可以不要的。没想到这个
    Mithril
        18
    Mithril   151 天前
    @Renco last 就比 lst 多一个字母吧。。有必要搞这种缩写吗?
    Renco
        19
    Renco   151 天前
    @admol 意思是有些字段概念比较高频,比如 operator,property,这种的直接拿来当表名会比较尴尬,所以加一个 info,大概是这个意思,也可能是我没有理解到位
    Renco
        20
    Renco   151 天前
    @Mithril 我觉得是没必要的,不过团队一开始统一了 创建时间和更新时间的字段定义,所以就沿用下去了
    Renco
        21
    Renco   151 天前
    @xuanbg 了解
    kamal
        22
    kamal   151 天前
    本来有收藏数据库设计的文章,但是传送门关站以后,收藏的就是个死链接了……
    Rwing
        23
    Rwing   151 天前
    @Renco 笑哭,这是哪个智障定的规范
    Renco
        24
    Renco   151 天前
    @kamal 可惜,数据库设计一直是我的痛
    nine
        25
    nine   151 天前
    用 Ruby on Rails 的标准规范就行了。
    https://ruby-china.github.io/rails-guides/active_record_basics.html
    huifer
        26
    huifer   151 天前
    创建人 ID,修改人 ID,创建时间,修改时间,删除标记位(不需要的就不加)常规都加,表字段常用的和非常用的分开设计_core(核心),_extends (拓展)
    xiaochun41
        27
    xiaochun41   151 天前
    库表设计方面的话题太大了,不过基本上可以从这几个方面考虑:
    1. 字段命名,
    2. 字段类型的选择
    3. 数据模型的设计(业务对象如何存,存几张表,如何冗余)
    4. 业务对象关联关系的设计(业务对象的关系怎么存等)
    5. 索引的设计
    6. 存储引擎的选择
    7. 库表容量的规划(数据规模多大,增长趋势如何,是否考虑分库分表)
    8. 团队的规范
    9. 行业最佳实践

    暂时想到这些
    young
        28
    young   151 天前
    数据库中一直有一个建表的模板, id, create_at, create_by, update_at, update_by, delete_at, delete_by, is_deleted
    随用随取
    5G
        29
    5G   151 天前   ❤️ 1
    看下阿里的 mysql 规范文档呗
    QlanQ
        30
    QlanQ   151 天前
    虽然我也比较拉跨,但是我真的 lz 比我还 拉跨
    lst_name 我也想到应该是 last_name 但是我不敢肯定,不知道为啥会这样设计,少一个 a 有啥用?
    只存 name 不存 id 我也不懂是干啥的,如果有重名怎么办,如果不光需要知道 对方的名字还需要其他的信息怎么办?用中文关联吗?
    肯定是先关联 ID 然后在考虑要不要保存 name
    约束,很多都说 不要做,特别是外键约束,会影响性能
    实在不知道怎么弄好的时候,就 看看 框架默认的是什么,比如 laravel 的框架 时间默认都是 created_at updated_at deleted_at
    关联都是 表名单数_id
    ksedz
        31
    ksedz   151 天前
    先画 ER 图,再生成表

    很多时候没有标准答案的,比如你说的存 id 这事,如果要尽量简单无冗余,就只存 id,提高性能简化查询就顺手缓存 name,而一些场景考虑到 name 可能变化甚至删除,你需要知道这里是要修改时的 name 还是最新的 name 。这不是能轻易判断对错的问题,全看对业务需求的理解了。
    Vegetable
        32
    Vegetable   151 天前
    平时多看,自己设计的时候,多换位思考,多反思,每个人都有自己的风格,无可厚非,但是看得多了,就知道什么样的设计是好的,什么样的是差的。
    no1xsyzy
        33
    no1xsyzy   151 天前
    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
    Renco
        34
    Renco   151 天前
    @QlanQ 是这块设计确实没有过脑子嗨,在反省了
    NULL2020
        35
    NULL2020   151 天前
    1. 关联的字段肯定是用主键 id 啊,一个表的字段除了主键 id,其它都是有可能会变的。

    2. 常用的约束一般就是非空和唯一性两个。

    3. create_by update_by create_time/create_at update_time/update_at

    4. 布尔型的习惯用 xxx_flag,如软删除标记 delete_flag
    no1xsyzy
        36
    no1xsyzy   151 天前
    完全可以推算出最恰当的组织形式,为什么要自己拍脑袋做转换?

    edk24
        37
    edk24   151 天前   ❤️ 1
    @leonme 阿里 Java 手册
    xhldtc
        38
    xhldtc   151 天前
    buster
        39
    buster   151 天前
    难道只有我一个人,第一版数据设计总是不完善,做着做着就改呗。
    jones2000
        40
    jones2000   151 天前
    请专业的 DBA 设计, 否则后续肯定还要重新设计。
    Renco
        41
    Renco   151 天前
    @jones2000 小公司没有 DBA,都是写业务的自己建表
    CantSee
        42
    CantSee   151 天前
    create_name 我可能会用 create_user
    CantSee
        43
    CantSee   151 天前
    更新人可能会用 up_user 或者 update_user ?
    dayudayupao
        44
    dayudayupao   151 天前   ❤️ 1
    目前在用的一套感觉比较好的命名规范
    n_sol
    n_ur
    cd_sx
    vu_ail
    nu_pne
    (手动狗头)
    neptuno
        45
    neptuno   151 天前
    五大金刚,无脑加上去就好了
    status
    created_at
    update_at
    created_user
    update_user,关联直接存主键
    iugo
        46
    iugo   151 天前
    created_by
    Hallelu
        47
    Hallelu   151 天前
    今天还因为数据库设计,跟同事讨论了一下午,太拉跨了......
    完全没有考虑到以后业务扩展,随便增加一个需求,就需要重新设计数据库和业务操作....
    cco
        48
    cco   151 天前
    @pckillers lst_nme,这种的都见多了。。。一度让我认为是人家手滑少打一个,原来是专门这么缩写的。
    ErenJaeger
        49
    ErenJaeger   151 天前
    三范式嘛
    Number13
        50
    Number13   151 天前
    我看没人说这个,但是遵守这个比较好
    数据库的 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)
    mengdodo
        51
    mengdodo   151 天前
    写好字段描述,status 各种条件描述,不然,我原地爆炸给你看
    xiaochong0302
        52
    xiaochong0302   151 天前
    参考一下我的模型文件:[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 天',
    ];
    }

    }
    ```
    icylogic
        53
    icylogic   151 天前 via iPhone
    所以 lst name 是啥意思?
    ljhrot
        54
    ljhrot   151 天前 via Android
    @icylogic lst -> 1st -> first name (逃
    xuanbg
        55
    xuanbg   151 天前
    update_xxx 就没必要了,更新又不会是唯一的。我都是通过统一的操作日志进行审计的。
    IvanLi127
        56
    IvanLi127   151 天前 via Android
    上 orm 框架,学习下 orm 框架采用的命名规则
    vivisidea
        57
    vivisidea   151 天前
    lst_name 节省了一个字符,结果引发各种猜

    last_name?
    list_name?
    kylix
        58
    kylix   151 天前
    若是用拼音首字母作字段名,怕不是要被喷的体无完肤~



    我干过 /狗头🐶
    h82258652
        59
    h82258652   151 天前
    codefirst 一把梭不香么 /狗头
    iseki
        60
    iseki   151 天前 via Android
    存 name 不存 id 这完全不可取,虽然逻辑上正确的做法是存 id 不存 name,但是考虑到某些场景下某些数据库拉垮的性能存上 name 也不是不行,不过还是先不要过早优化比较好吧,感觉如果不想连表 join,那就是把 name 塞 Redis 都不是不可以…
    看起来加 Redis 加了缓存好麻烦,实际上在数据表里事实上缓存了 name 更麻烦…完全和数据库的设计思想对着干不讨好
    PolarBears
        61
    PolarBears   151 天前
    @kylix 我现在在的单位有套古老的系统就是这样,而且还有些是全拼有些是首字母简写,也没有文档根本猜不透.我真希望如果实在不会取名的话直接用中文做字段名不就挺好的吗
    mingl0280
        62
    mingl0280   151 天前 via Android
    这不是 SQL 101 那种级别的知识么,随便找一本数据库设计的教科书都告诉你数据库设计三级范式有啥要求照着做就行了,自己瞎猜结果猜了半天是别人教科书就写过的没意思的好么……
    risky
        63
    risky   151 天前
    created_at
    updated_at
    created_by
    nutting
        64
    nutting   150 天前 via Android
    我们公司有规范,还得考试学习
    encro
        65
    encro   150 天前
    表内创建人字段和更新人字段,只存了 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 需要根据项目和字段实际用途和存储关系来。



    架构设计是门艺术,这门艺术的核心是可维护性,开发效率以及运行效率的恰如其分。
    sockball07
        66
    sockball07   150 天前
    redis 键名可以缩 数据库可真别缩写
    encro
        67
    encro   150 天前
    @xiaochong0302

    我们的是

    const LEVELS = [
    self::LEVEL_JUNIOR =>'初级',
    ...
    ];

    不用方法,除非需要逻辑判断的。


    当然更理想的其他语言的 enum 类型,就少了一层定义,且可以采用位运算符。
    use MyCLabs\Enum\Enum;

    class Gender extends Enum
    {
    public const MALE = '男';
    public const FEMALE = '女';
    }
    qile1
        68
    qile1   150 天前 via Android
    学习啦,我们好多接口都是拼音首字母,那理解起来才酸爽,每次猜字段都骂娘
    Senorsen
        69
    Senorsen   150 天前
    尽量不要反太多范式吧,冗余多了到时不一致问题一多很恶心。

    命名尽量规范。
    lychs1998
        70
    lychs1998   149 天前
    尽量不要在其它数据里冗余更多的非 ID 信息,比如不要在业务表里存用户表的姓名字段,而应该存 ID 。因为两份数据后会导致后期的更新维护成本过高。业务表只放业务数据。
    adocder
        71
    adocder   149 天前
    别的不说,就第一个存 name 不存 id 的问题,改名了咋整,你跟谁对去
    关于   ·   帮助文档   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   912 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 19:17 · PVG 03:17 · LAX 12:17 · JFK 15:17
    ♥ Do have faith in what you're doing.