看到同事设计的表结构我人麻了!聊聊怎么更好去设计数据库表
亲身经历最近接了个外包,和另外两个哥们一起开发。因为他们的时间更充裕,所以前期的表结构都是他们来设计,我没有参与。等我空下来开始做我的部分时,两眼一抹黑,光看表名完全不知道是什么东西。
我不理解,但是大受震撼。
比如说:
DC_COURSE_TESTPAPER 课程下的问卷DC_COURSE_PAPER_HISTORY 问卷与学生的关联表,也就是问卷下发给哪些学生 DC_COURSE_PAPER_HISTORY_ALL 问卷与问题的关联表,也就是问卷包含哪些问题光看表名能知道是什么意思吗?难受啊兄弟们。
这让我深刻意识到了:哪怕是一些“高级开发”,也并不知道怎么去设计一个好的表结构。
于是决定花点时间写一篇文章,和大家一起探讨如何更好的设计表结构。
所有观点都是我结合多年的经验得来,不一定正确,如有错误之处欢迎大家指正。
表名:第一眼就要知道是干什么的1. 有意义的前缀 + 清晰的表名前缀在大型系统中是有必要的,可以区分不同业务模块,但关键是前缀要有明确含义,表名要语义清晰。
「? 不好的命名」
DC_COURSE_TESTPAPER # TESTPAPER是问卷还是试卷?TB_USER_INFO # TB前缀无意义,INFO太泛泛T_ORDER_DTL # DTL是detail的缩写?DATA_TBL_001 # 完全看不懂「? 好的命名」
DC_COURSE_QUESTIONNAIRES # DC表示Distance Course远程课程系统SYS_USER_PROFILES # SYS表示系统核心模块ORDER_ITEMS # 订单商品明细LMS_STUDENT_SCORES # LMS表示Learning Management System「区别在哪里?」
不好的:缩写让人猜测,TESTPAPER、INFO、DTL这些词汇模糊不清好的:前缀有明确业务含义,表名用完整英文词汇表达准确含义「什么时候需要前缀?」
多个业务系统共用数据库:USER_,ORDER_,PRODUCT_区分不同数据类型:LOG_,CONFIG_,TEMP_大型项目的模块划分:CRM_,ERP_,CMS_2. 用完整的英文单词而不是拼音「? 不好的命名」
kecheng_wenjuan # 拼音user_xinxi # 中英混合订单_items # 中英混合「? 好的命名」
course_questionnaires# 纯英文,语义清晰user_profiles # 纯英文order_items # 纯英文「原因」
英文是编程的通用语言,团队成员更容易理解避免编码问题在一些专业且复杂的业务系统(比如医疗行业)中,会提倡会拼音来作为字段名,但是表名还是英文为主,这个后面展开讲。
3. 表名要体现业务含义,不要只是技术实现「? 不好的命名」
data_table_001temp_storagemiddle_tablerelation_mapping「? 好的命名」
student_scores # 学生成绩file_uploads # 文件上传记录course_enrollments # 课程报名user_preferences # 用户偏好设置除非是临时用的表,不参与任何业务逻辑,只是用来做数据处理或者测试。
字段命名:见名知意1. 布尔字段用 is_ 开头「? 不好的命名」
active # 是激活还是活跃?delete # 删除状态还是删除动作?flag # 什么标志?「? 好的命名」
is_active # 是否激活is_deleted # 是否已删除 is_verified # 是否已验证也有些团队会用if_作为前缀,这也没什么毛病。
「好处」
用is_开头有几个明显的好处:
「一眼就能看出是布尔值」- 看到is_active就知道这个字段要么是true要么是false,不用再去猜
「避免歧义」- 像active这样的名字,你搞不清楚它表示的是状态还是动作。is_active就明确表示状态
「代码可读性更好」- 写代码的时候,if (user.is_active)比if (user.active)更容易理解
2. 时间字段统一后缀「? 不好的命名」
create_timeupdate_datedelete_atregister_datetime「? 好的命名」
created_at # 创建时间updated_at # 更新时间deleted_at # 删除时间registered_at# 注册时间「好处」
「一眼就能看出是时间字段」- 看到created_at就知道这是时间类型,不用去查表结构
「避免命名混乱」- 有的用_time,有的用_date,有的用_datetime,团队里每个人习惯不一样,最后搞得乱七八糟
_at在英语里表示"在某个时间点",比_time更准确
「逻辑删除的最佳实践」- 特别推荐用deleted_at做逻辑删除字段。这样设计有几个好处:
能看出来删除的具体时间可以追踪删除操作的历史支持数据恢复(把deleted_at设为NULL就行)比用is_deleted这种布尔字段更灵活「额外经验」
除了基本的命名规范,还有一些实用的经验:
「逻辑删除用时间字段」- 用deleted_at比is_deleted好,能看出来删除时间,支持数据恢复和历史追踪
「状态字段用枚举」- 不要用数字 1、2、3 表示状态,用status字段,值用'pending'、'approved'、'rejected'这样的英文单词
「金额字段用 decimal」- 不要用float或double,用decimal(10,2)这样的类型,避免浮点数精度问题
「密码字段要加密」- 密码字段名用password_hash或encrypted_password,不要直接叫password
「软删除要加索引」- 如果经常查询未删除的数据,给deleted_at字段加索引,提高查询性能
3. 外键字段统一 _id 后缀「? 不好的命名」
user # 这是用户ID还是用户对象?course # 课程ID?teacher_key # 什么key?「? 好的命名」
user_id # 用户IDcourse_id # 课程IDteacher_id # 教师ID这个好处,应该不用过多赘述了。
4. 额外的经验除了上面这些基本的命名规范,还有一些实用的经验:
「逻辑删除用时间字段」- 用deleted_at比is_deleted更好,不仅能看出来删除时间,还能用于数据恢复和历史追踪
「状态字段用枚举」- 不要用数字 1、2、3 表示状态,用status字段,值用'pending'、'approved'、'rejected'这样的英文单词
「金额字段用 decimal」- 不要用float或double,用decimal(10,2)这样的类型,避免浮点数精度问题
「密码字段要加密」- 密码字段名用password_hash或encrypted_password,不要直接叫password
表结构设计:关系清晰、适度冗余1. 一对多关系:外键放在多的一边让我们用用户和订单的业务关系来举例:
「用户表 (users)」
字段名类型说明idBIGINT主键usernameVARCHAR(50)用户名emailVARCHAR(100)邮箱created_atTIMESTAMP创建时间「订单表 (orders)」
字段名类型说明idBIGINT主键user_idBIGINT用户ID(外键)order_noVARCHAR(32)订单号total_amountDECIMAL(10,2)总金额statusVARCHAR(20)订单状态created_atTIMESTAMP创建时间这样设计的好处:
通过 user_id 就知道订单属于哪个用户JOIN 一下就能拿到用户的所有订单新增订单字段不影响用户表2. 多对多关系:中间表命名要体现关系多对多关系的中间表命名要根据具体情况来选择:
「情况1:有业务含义的关系表」比如学生和课程的关系,不只是简单关联,还有报名时间、状态等业务信息:
「? 不好的设计」
student_course_rel # rel是什么关系?sc_mapping # 缩写看不懂middle_table # 完全不知道什么意思「? 好的设计」
「课程报名表 (course_enrollments)」
字段名类型说明idBIGINT主键student_idBIGINT学生IDcourse_idBIGINT课程IDenrolled_atTIMESTAMP报名时间statusVARCHAR(20)报名状态「情况2:纯粹的关联关系表」如果只是单纯的多对多映射,没有额外的业务属性,用mapping也是可以的:
「用户角色关联表 (user_role_mappings)」
字段名类型说明user_idBIGINT用户IDrole_idBIGINT角色ID「文章标签关联表 (article_tag_relations)」
字段名类型说明article_idBIGINT文章IDtag_idBIGINT标签ID「如何选择命名?」
有业务含义的关系:用具体的业务名词,如enrollments、orders、friendships纯粹的映射关系:可以用mappings、relations或直接用实体1_实体2s关键是保持团队内命名风格的统一3. 适当的字段冗余:提升查询效率有时候为了避免复杂的JOIN查询,适当冗余是非常有必要的。
最典型的就是冗余上级ID:
「订单详情表 (order_items)」
字段名类型说明idBIGINT主键order_idBIGINT订单IDuser_idBIGINT用户ID(冗余)product_idBIGINT商品IDquantityINT购买数量priceDECIMAL(10,2)商品单价「为什么要冗余 user_id?」
查询用户的所有购买记录时,直接查 order_items 表就行不需要先通过 orders 表再关联到 order_items一个查询代替了两表JOIN「商品评论表 (product_reviews)」
字段名类型说明idBIGINT主键product_idBIGINT商品IDcategory_idBIGINT商品分类ID(冗余)user_idBIGINT用户IDratingTINYINT评分contentTEXT评论内容「为什么要冗余 category_id?」
按分类统计评分时,不需要JOIN商品表查询某分类下的所有评论更高效「什么时候该冗余ID?」
经常需要跨层级查询的场景统计和报表查询频繁的字段读多写少的关联关系上级ID基本不会变动的情况回到最初的案例现在我们用上文讲的一些原则来重新设计开头的表:
? 原来的设计DC_COURSE_TESTPAPER # 什么鬼?DC_COURSE_PAPER_HISTORY # HISTORY是历史?DC_COURSE_PAPER_HISTORY_ALL # ALL又是什么意思?? 重新设计「课程问卷表 (course_questionnaires)」
字段名类型说明idBIGINT主键course_idBIGINT课程IDtitleVARCHAR(200)问卷标题descriptionTEXT问卷描述statusENUM状态:draft/published/closedcreated_atTIMESTAMP创建时间「问卷分发记录表 (questionnaire_assignments)」
字段名类型说明idBIGINT主键questionnaire_idBIGINT问卷IDstudent_idBIGINT学生IDassigned_atTIMESTAMP分发时间statusENUM状态:assigned/started/completed「问卷题目表 (questionnaire_questions)」
字段名类型说明idBIGINT主键questionnaire_idBIGINT问卷IDcontentTEXT题目内容question_typeENUM题目类型sort_orderINT排序现在再看:
course_questionnaires- 一眼就知道是课程问卷questionnaire_assignments- 问卷分发记录questionnaire_questions- 问卷题目是不是瞬间清晰了?
当然,因为业务简单,这里就不统一加前缀了。
写在最后数据库表结构是项目的重中之重。
好的表设计让能让整个团队开发起来更顺畅,减少沟通成本,而且代码维护起来更容易,新人上手也更简单。
投入时间做好表结构设计,绝对是值得的投资。
没有人愿意每天面对DC_COURSE_PAPER_HISTORY_ALL这样的表名写代码。
「最后一句话:代码是写给人看的,表结构也是建给人用的。」
AI编程资讯AI Coding专区指南:
https://aicoding.juejin.cn/aicoding
点击"阅读原文"了解详情~
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线