这是软考架构师的基础知识系列,我将枯燥的学习内容整理成生动的网文,希望能帮助到其他人更好的理解这些枯燥的知识,废话不多说。 here we go!
我是店长(数据库管理员DBA),你是来帮忙的伙计,咱们看看怎么用数据库把店管得井井有条。
1. 三级模式-两级映像:仓库、货架和小票
这个概念听起来高大上,其实就是咱们奶茶店的后台、前台和你手里的菜单。
-
内模式(物理存储层):这是咱们的后厨仓库。一箱箱牛奶堆在哪儿、珍珠粉圆放在第几个货架第三层,这是最底层的物理存放方式。你作为伙计不用管这个,这是我店长的事儿。
-
模式(概念层):这是我整理好的货架和配料表。我把仓库里的东西整理成一张张表:一张“原料库存表”、一张“饮品配方表”、一张“销售记录表”。你就知道咱们店里有什么,东西在“逻辑上”是怎么组织的。
-
外模式(用户视图):这是给你的收银机小屏幕。我不会让你看到复杂的“原料成本价”和“供应商电话”,只给你看“饮品名称”和“价格”。这就是专门为你这个“伙计角色”定制的视图。
-
两级映像(神奇的映射):
- 模式/内模式映像:假如我把珍珠从“第三层货架”搬到了“门口推车里”(修改了物理存储位置),我只需要改一下我脑中的“映射”,你收银机上的“珍珠奶茶”照样能点,不受影响。这就是物理独立性。
- 外模式/模式映像:假如我在后台把“饮品配方表”里加了“卡路里”一栏,但只要我不把它放到你的小屏幕上,你的操作界面一点没变。这就是逻辑独立性。
2. 数据库设计:开分店六步曲
隔壁老王看咱们店火了,想加盟,咱们得给他一套标准流程,这就是数据库设计。
- 需求分析:老王说,“我要能点单、能查库存、能看谁买得多。”我拿出小本本记下,画成数据流图(钱怎么流、货怎么流),写成需求说明书。
- 概念结构设计:我把老王的需求画成一幅E-R图(实体-联系图)。
- 实体(长方形):就是人、事、物。比如“顾客”、“奶茶”、“订单”。
- 属性(椭圆形):实体的特征。比如“顾客”有“电话”、“姓名”。
- 联系(菱形):实体之间的关系。比如顾客购买奶茶。
- 逻辑结构设计:把E-R图这张画,翻译成电脑能懂的二维表格(关系模式)。
- 1:1联系:一个店长管一家店。店长和店的信息可以放一张表。
- 1:N联系:一个顾客可以下多个订单。那把“顾客ID”放到“订单”表里。
- M:N联系:一杯奶茶可以用多种配料,一种配料可以加到多种奶茶里。这可麻烦了,必须单独建一张“奶茶-配料表”。
- 物理设计:决定这些表格文件具体怎么存,放固态硬盘还是机械硬盘,怎么建索引让查“杨枝甘露”时更快。
- 数据库实施:真正在电脑上把表建起来,写个收银程序,让老王试营业。
- 运行和维护:开业后,发现“生椰拿铁”卖爆了,系统变慢,我再回来调优。
3. 关系代数:给表格做“拼图游戏”
你把表格当成积木,关系代数就是玩积木的手。
- 选择(σ):就是筛选行。
σ_{价格>15}(奶茶表)→ “把价格超过15块的奶茶挑出来”。 - 投影(π):就是筛选列。
π_{品名,价格}(奶茶表)→ “我只看品名和价格,别的列先藏起来”。 - 连接(⋈):就是拼图。
- 自然连接:把“顾客表”和“订单表”里相同顾客ID的信息自动拼成一张大宽表,重复的ID列只留一列。
- 笛卡尔积:不管你三七二十一,强行把A表的每一行和B表的每一行都拼一次。比如“奶茶表”有10行,“配料表”有5行,结果就是10×5=50行,很多是无意义的组合。
4. 函数依赖与范式:给表格“瘦身减肥”
一开始,我把所有信息写在一张大表里:
| 顾客名 | 电话 | 奶茶名 | 价格 | 店长名 |
|---|---|---|---|---|
| 张三 | 123 | 珍珠奶茶 | 15 | 老文 |
| 张三 | 123 | 杨枝甘露 | 20 | 老文 |
这表毛病可大了!
- 1NF(第一范式):要求每个格子里只能有一个值,不能是数组。如果张三点了两杯,不能写“珍珠奶茶,杨枝甘露”,得拆成两行。上面这表满足1NF。
- 2NF(第二范式):消除部分依赖。
- 主键应该是(顾客名,奶茶名)。
- 但是!“电话”只依赖于“顾客名”(部分依赖),“价格”只依赖于“奶茶名”(部分依赖)。这不行!会造成数据冗余(张三电话存了两遍)和更新异常(奶茶涨价了要改N个地方)。
- **拆!**拆成三个表:
顾客表(顾客名, 电话)、奶茶表(奶茶名, 价格)、订单表(顾客名, 奶茶名)。
- 3NF(第三范式):消除传递依赖。
- 假设
奶茶表里有个字段叫店长名。我们知道:奶茶名 → 所属分店ID,然后所属分店ID → 店长名。这就是传递依赖。 - 还是那个问题,店长升职了,得把所有含该店长的奶茶记录全改一遍。
- **拆!**再拆出一个
分店表(分店ID, 店长名)。
- 假设
经过2NF和3NF的减肥,数据就不冗余了,改一个地方就行。这就是规范化的意义。
5. 并发控制:两个人同时点同一杯奶茶
- 丢失更新:你和另一个伙计同时点“最后一杯杨枝甘露”的“已售罄”按钮。你先点了,系统记售罄;他后点了,他的操作把你的操作覆盖了,系统以为还没售罄,结果顾客下单后做不出来。**X锁(写锁)**可以解决:谁先点售罄,谁就锁住这条记录,别人改不了。
- 读脏数据:我已经点了售罄,库存变0。另一个伙计在查库存,看到是0,告诉顾客没了。顾客刚走,我突然发现点错了,回滚了操作,库存又变回1。但顾客已经走了。二级封锁协议可以解决。
- 不可重复读:你查库存时杨枝甘露还有1杯。在你准备下单的几秒内,隔壁柜台线上下单抢走了这最后一杯。你再查,发现没了。你两次读取的数据不一致。三级封锁协议可以解决。
6. NoSQL(非关系数据库):当网红店遇到双十一
咱们店火了,一天卖100万杯,传统的关系型数据库(SQL)像记账先生,一笔笔记,虽然准确但速度跟不上了。这时需要NoSQL。
- 键值存储(Redis):就像个大号的临时记事贴。顾客ID是Key,他刚加入购物车的东西是Value。查得飞快,用来做缓存,缓解后台数据库压力。
- 文档存储(MongoDB):就像顾客的朋友圈。有的发图,有的发视频,有的只发文字,结构不固定。用JSON格式存起来很方便,不用像SQL那样先定义好所有列才能存。
- 列存储(HBase):就像咱们分析销售报表。关系数据库是按“行”存的(张三、珍珠奶茶、15元、3月1日;李四、柠檬水、10元、3月2日...)。列存储是按“列”存的(把所有顾客名存一个文件,所有奶茶名存一个文件),做统计(比如计算奶茶平均价格)时,只读“价格”这一列,速度快得飞起。
7. 分布式数据库:从一家店到全国连锁
咱们的“奶茶店”越开越多,北京、上海、广州都有分店。数据全存总部一台电脑里肯定不行:广州分店查个库存,数据得从北京机房千里迢迢传过去,太慢了。于是我们搞起了分布式数据库——数据分开存放,但用起来像一家店。
新增的几种模式(在三级模式之外)
- 全局概念模式:这是咱们总部的大脑。它定义整个连锁店所有的数据长什么样,比如“所有分店的顾客表都是(顾客ID,姓名,电话)”。对于广州分店的伙计来说,他感觉就像在操作一个统一的全国数据库,这就是数据透明性。
- 分片模式:数据怎么切开存。比如我们按地区分:北京分店的销售记录存在北京服务器,广州的存广州。这叫水平分片(按行分)。也可以垂直分片:顾客基础信息存一台机器,顾客的消费积分存另一台机器。
- 分布模式:告诉系统,哪个分片具体放在哪台服务器上。比如“北京销售记录”放在IP为123的服务器上。
CAP理论:鱼与熊掌不可兼得
假设双十一期间,网络突然断了,北京和广州的服务器联系不上了。这时候你作为店长,得做一个痛苦的抉择:
- C(一致性):所有门店数据绝对一致。比如“买一送一”的优惠券总量必须精确。为了C,网络断了时,我宁愿暂时停掉所有分店的优惠券业务,直到网络恢复。这就是CP系统,牺牲了可用性。
- A(可用性):系统永远能响应。网络断了,广州店可以自己先发券,先记录着,等网络通了再告诉总部。此时北京和广州数据暂时不一致,但店能继续营业。这就是AP系统,牺牲了强一致性,追求最终一致性(BASE理论)。
- P(分区容忍性):必须允许网络分区。在分布式系统里,网络故障是常态,P基本是必须选的。所以真正的选择是在CP(保证一致性)和AP(保证可用性)之间二选一。
8. SQL语言:收银机上的操作命令
你每天在收银机上点点点,背后跑的都是SQL。它就是你跟数据库对话的“咒语”,不区分大小写。
咱们以“奶茶店数据库”为例,手把手教你念咒语。
建表(开新账本)
CREATE TABLE 奶茶表 (
奶茶号 CHAR(5) PRIMARY KEY, -- 主键,不能重复不能空
名称 CHAR(30) UNIQUE, -- 名称唯一
价格 INT,
类型 CHAR(10)
);
查数据(最常用)
- “给我看看所有‘鲜果茶’类型的奶茶名称和价格。”
SELECT 名称, 价格
FROM 奶茶表
WHERE 类型 = '鲜果茶';
- “我想按价格从贵到便宜排个序。”
SELECT * FROM 奶茶表 ORDER BY 价格 DESC;
- “咱们一共有多少种不同类型的奶茶?”
SELECT 类型, COUNT(*) AS 种类数
FROM 奶茶表
GROUP BY 类型;
这里的GROUP BY就是把“鲜果茶”的放一堆,“奶茶”的放一堆,然后数每堆有几个。AS是起个别名,让结果更好懂。
增删改(操作数据)
- “新出了一款‘生椰冰咖’,加进去。”
INSERT INTO 奶茶表 VALUES ('T006', '生椰冰咖', 18, '咖啡');
- “珍珠奶茶涨价了,改成16块。”
UPDATE 奶茶表 SET 价格 = 16 WHERE 名称 = '珍珠奶茶';
- “芝士葡萄卖得不好,下架吧。”
DELETE FROM 奶茶表 WHERE 名称 = '芝士葡萄';
高级一点的:连接查询
- “我想看看‘张三’这个人,都买过哪些奶茶。”这需要把
顾客表和订单表连起来查。自然连接NATURAL JOIN会根据相同的列名(比如都有顾客ID)自动匹配。
SELECT 顾客.姓名, 订单.奶茶名
FROM 顾客 NATURAL JOIN 订单
WHERE 顾客.姓名 = '张三';