共计 3557 个字符,预计需要花费 9 分钟才能阅读完成。
前言
最近有不少前端和测试转 Go 的敌人私信我: 如何做好表结构设计?
大家关怀的问题阳哥必须整理出来,心愿对大家有帮忙。
先说论断
这篇文章介绍了设计数据库表构造应该思考的 4 个方面,还有优雅设计的 6 个准则,举了一个例子分享了我的设计思路,为了进步性能咱们也要从多方面思考缓存问题。
播种最大的还是和大家的交换探讨,总结一下:
- 首先,肯定要先搞清楚业务需要。比方我的例子中,如果不须要灵便设置,齐全能够写到配置文件中,并不需要独自设计外键。主表中间接保留各种筛选标签名称(留神保护的问题,要思考到数据一致性)
- 数据库表结构设计肯定思考数据量和并发量,我的例子中如果数据量小,能够适当做冗余设计,升高业务复杂度。
4 个方面
设计数据库表构造须要思考到以下 4 个方面:
- 数据库范式 :通常状况下,咱们心愿表的数据合乎某种范式,这能够保证数据的完整性和一致性。例如,第一范式要求表的每个属性都是原子性的,第二范式要求每个非主键属性齐全依赖于主键,第三范式要求每个非主键属性不依赖于其余非主键属性。
- 实体关系模型(ER 模型):咱们须要先依据理论状况画出实体关系模型,而后再将其转化为数据库表构造。实体关系模型通常包含实体、属性、关系等因素,咱们须要将它们转化为表的模式。
- 数据库性能 :咱们须要思考到数据库的性能问题,包含表的大小、索引的应用、查问语句的优化等。
- 数据库安全 :咱们须要思考到数据库的平安问题,包含表的权限、用户角色的设置等。
设计准则
在设计数据库表构造时,能够参考以下几个优雅的设计准则:
- 简单明了 :表构造应该简单明了,防止适度复杂化。
- 一致性 :表构造应该放弃一致性,例如命名标准、数据类型等。
- 规范化 :尽可能将表规范化,防止数据冗余和不一致性。
- 性能 :表构造应该思考到性能问题,例如应用适当的索引、防止全表扫描等。
- 平安 :表构造应该思考到平安问题,例如正当设置权限、防止 SQL 注入等。
- 扩展性 :表构造应该具备肯定的扩展性,例如预留字段、可扩大的关系等。
最初,须要揭示的是,优雅的数据库表构造须要在实践中一直迭代和优化,一直满足理论需要和新的挑战。
上面举个示例让大家更好的了解如何设计表构造,如何引入内存,有哪些优化思路:
问题形容
如上图所示,红框中的视频筛选标签,应该怎么设计数据库表构造? 除了前台筛选,还想反对在治理后盾灵便配置这些筛选标签。
这是一个很好的利用场景,大家能够先本人想一下。不要焦急看我的计划。
需要剖析
- 能够依据红框的标签筛选视频
- 其中综合标签比拟非凡,和类型、地区、年份、演员等不一样
- 综合是依据业务逻辑取值,并不需要入库
- 类型、地区、年份、演员等须要入库
- 设计表构造时要思考到:
- 不便获取标签信息,不便把标签信息缓存解决
- 不便依据标签筛选视频,不便咱们写后续的业务逻辑
设计思路
- 综合标签能够写到配置文件中(或者写在前端),这些信息不须要灵便配置,所以不须要保留到数据库中
- 类型、地区、年份、演员都设计独自的表
- 视频表中设计标签表的外键,不便视频列表筛选取值
- 标签信息写入缓存,进步接口响应速度
- 类型、地区、年份、演员表也要反对对数据排序,不便前期治理保护
表结构设计
视频表
字段 | 正文 |
---|---|
id | 视频主键 id |
type_id | 类型表外键 id |
area_id | 地区表外键 id |
year_id | 年份外键 id |
actor_id | 演员外键 id |
其余和视频间接相干的字段(比方名称)我就省略不写了
类型表
字段 | 正文 |
---|---|
id | 类型主键 id |
name | 类型名称 |
sort | 排序字段 |
地区表
字段 | 正文 |
---|---|
id | 类型主键 id |
name | 类型名称 |
sort | 排序字段 |
年份表
字段 | 正文 |
---|---|
id | 类型主键 id |
name | 类型名称 |
sort | 排序字段 |
原以为年份字段不须要排序,要么是年份正序排列,要么是年份倒序排列,所以不须要 sort 字段。
认真看了看需要,还有“10 年代”还是须要灵便配置的呀~
演员表
字段 | 正文 |
---|---|
id | 类型主键 id |
name | 类型名称 |
sort | 排序字段 |
表结构设计完了,别忘了缓存
缓存策略
首先这些不会频繁更新的筛选条件倡议应用缓存:
- 比拟罕用的就是 redis 缓存
- 再进阶一点,如果你应用 docker,能够把这些配置信息写入 docker 容器所在物理机的内存中,而不必申请其余节点的 redis,进一步升高网络传输带来的耗时损耗
- 筛选条件这类配置信息,客户端和服务端能够约定一个更新缓存的机制,客户端间接缓存配置信息,进一步提高性能
列表数据主动缓存
目前很多框架都是反对主动缓存解决的,比方 goframe 和 go-zero
goframe
能够应用 ORM 链式操作 - 查问缓存
示例代码:
package main
import (
"time"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
)
func main() {
var (db = g.DB()
ctx = gctx.New())
// 开启调试模式,以便于记录所有执行的 SQL
db.SetDebug(true)
// 写入测试数据
_, err := g.Model("user").Ctx(ctx).Data(g.Map{
"name": "xxx",
"site": "https://xxx.org",
}).Insert()
// 执行 2 次查问并将查问后果缓存 1 小时,并可执行缓存名称 (可选)
for i := 0; i < 2; i++ {r, _ := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{
Duration: time.Hour,
Name: "vip-user",
Force: false,
}).Where("uid", 1).One()
g.Log().Debug(ctx, r.Map())
}
// 执行更新操作,并清理指定名称的查问缓存
_, err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{
Duration: -1,
Name: "vip-user",
Force: false,
}).Data(gdb.Map{"name": "smith"}).Where("uid", 1).Update()
if err != nil {g.Log().Fatal(ctx, err)
}
// 再次执行查问,启用查问缓存个性
r, _ := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{
Duration: time.Hour,
Name: "vip-user",
Force: false,
}).Where("uid", 1).One()
g.Log().Debug(ctx, r.Map())
}
go-zero
DB 缓存机制
go-zero 缓存设计之长久层缓存
官网都做了具体的介绍,不作为本文的重点。
探讨
这篇文章首发在我的公众号《如何做好表结构设计?》,引起了大家的探讨。
也和大家分享一下:
Q1 冗余设计和一致性问题
发问:一个表里做了这么多外键,如果我要查各自的名称,势必要关联 4 张表,对于这种存在多外键关联的这种表,要不要做冗余呢 (间接在主表里冗余各自的名称字段)?要是保障一致性的话,就势必会影响性能,如果做冗余的话,又无奈保障一致性
答复:
你看文章的上下文应该晓得,文章想解决的是视频列表筛选问题。
你提到的这个场景是在视频详情信息中,如果要展现这些外键的名称怎么设计更好。
我的倡议是这样的:
- 依据需要能够做适当冗余,比方你的主表信息量不大,配置信息批改后同步批改冗余字段的老本并不高。
- 或者像我文章中写的不做冗余设计,然而会把外键信息缓存,业务查问从缓存中取值。
- 或者将视频详情的查问后果整体进行缓存
还是看具体需要,如果这些筛选信息不变动或者不须要手工治理,甚至不须要设计表,间接写死在代码的配置文件中也能够。进一步升高 DB 压力,进步性能。
Q2 why 设计外键?
发问:为什么要设计外键关联?间接写到视频表中不就行了?这么设计的意义在哪里?
答复:
- 关键问题是想解决治理后盾灵便配置
- 如果没有这个需要,咱们能够间接把筛选条件以配置文件的形式写死在程序中,升高复杂度。
- 站在我的角度:这个性能的筛选条件变动并不会很大,所以很懂你的意思。也倡议像我 2. 中的计划去做,去和产品经理拉扯喽~
总结
这篇文章介绍了设计数据库表构造应该思考的 4 个方面,还有优雅设计的 6 个准则,举了一个例子分享了我的设计思路,为了进步性能咱们也要从多方面思考缓存问题。
播种最大的还是和大家的交换探讨,总结一下:
- 首先,肯定要先搞清楚业务需要。比方我的例子中,如果不须要灵便设置,齐全能够写到配置文件中,并不需要独自设计外键。主表中间接保留各种筛选标签名称(留神保护的问题,要思考到数据一致性)
- 数据库表结构设计肯定思考数据量和并发量,我的例子中如果数据量小,能够适当做冗余设计,升高业务复杂度
本文抛砖引玉,欢送大家留言交换。