我们不背定义,直接看一个具体场景:
你开发了一款游戏。玩家打开背包,看到自己有 3 瓶治疗药水。这个过程中,数据库的三层模型分别在哪里?
先记住:不是有三份数据,而是从三个角度看同一套数据。
1. 先把全服背包数据设计出来:模式
游戏需要记录所有玩家的物品,于是你设计了一张“背包表”:
| 玩家ID | 物品名称 | 数量 |
|---|---|---|
| 1001 | 治疗药水 | 3 |
| 1001 | 铁矿石 | 10 |
| 1002 | 治疗药水 | 8 |
你规定:
每条记录包含:玩家ID、物品名称、数量。 数量必须是整数,而且不能小于 0。“这张表有哪些字段、字段是什么类型、有什么规则”,就属于模式。
注意区分:
- “数量必须是非负整数”是结构和规则;
- “玩家 1001 有 3 瓶药水”是具体数据。
玩家喝掉一瓶药水,数量从 3 变成 2,只是数据变了,表的结构没变,模式没有变。
你可以把模式理解为:
整个数据库的表格设计图。
实际项目还会有玩家表、装备表等,它们的结构及相互关系共同组成全局的模式。
2. 玩家只需要自己的背包:外模式
数据库里有所有玩家的数据,但玩家 1001 打开背包时,只需要得到:
| 物品名称 | 数量 |
|---|---|
| 治疗药水 | 3 |
| 铁矿石 | 10 |
这里有两个变化:
- 只取玩家 1001 的记录;
- 不再显示“玩家ID”这一列,因为已经知道这是自己的背包。
面向某类用户或应用提供的局部数据结构,就是外模式所描述的内容。
运营人员可能需要另一种视角:
| 物品名称 | 全服总数量 |
|---|---|
| 治疗药水 | 11 |
| 铁矿石 | 10 |
药水为什么是 11?
玩家 1001 的 3 瓶 + 玩家 1002 的 8 瓶 = 11 瓶所以,同一套数据可以有不同视角:
玩家:查看自己的物品明细。 运营:查看全服物品总量。你可以把外模式理解为:
从完整数据库中,为特定使用者提供的一份数据视角。
这里讲的是数据视角,不是背包界面的颜色、图标和布局。实际限制玩家只能读取自己的数据,还需要权限控制。
3. 这些记录到底怎么存、怎么找:内模式
刚才的表是画给人看的。数据库底层不会真的存成一张屏幕上的表格。
它需要把记录组织在文件和数据页里,还可以建立索引来加快查找。
假设全服有一千万条背包记录,现在要查玩家 1001 的物品。
没有合适的索引时
数据库可能需要扫描大量记录:
检查这条:是不是玩家 1001? 检查下一条:是不是玩家 1001? ……有合适的索引时
索引类似书的目录,可以帮助数据库更快找到相关记录:
查找玩家 1001 ↓ 定位相关数据 ↓ 读取他的背包记录记录如何组织、索引采用什么结构、如何通过它找到数据,这些属于内模式关心的事情。
你可以把内模式理解为:
数据库底层存放和访问数据的方案。
4. 把一次“打开背包”串起来
现在,玩家 1001 打开背包:
① 应用需要什么数据? 当前玩家的“物品名称、数量”。 ——外模式的角度 ② 这些数据在逻辑上来自哪里? 背包表,字段是“玩家ID、物品名称、数量”。 ——模式的角度 ③ 数据库怎样找到它们? 利用索引,读取相应的数据页。 ——内模式的角度这是三个描述角度,不是说请求必须经过三个独立服务器。
5. 为什么要分开?看一次实际优化
上线后,玩家抱怨:
“打开背包太慢了!”
于是,你为背包查询增加了合适的索引。
结果是:
| 检查项 | 是否变化 |
|---|---|
| 玩家看到的物品名称和数量 | 没变 |
| 背包表的字段和规则 | 没变 |
| 数据库底层查找数据的方式 | 变了 |
也就是:
外模式没变 模式没变 内模式发生调整你优化了底层存取方式,却不必让玩家端跟着修改数据接口。这就是分层带来的好处之一,叫作物理数据独立性。
最后,只记住这三句话
拿这款游戏来说:
- 外模式:玩家需要看到怎样的背包数据?
- 模式:整个背包数据库的表和规则怎样设计?
- 内模式:背包记录在底层怎样存、怎样找?
面向使用者看,是外模式;面向全局结构看,是模式;面向底层存储看,是内模式。