软考架构师故事系列-数据库系统

0 阅读10分钟

这是软考架构师的基础知识系列,我将枯燥的学习内容整理成生动的网文,希望能帮助到其他人更好的理解这些枯燥的知识,废话不多说。 here we go!

我是店长(数据库管理员DBA),你是来帮忙的伙计,咱们看看怎么用数据库把店管得井井有条。


1. 三级模式-两级映像:仓库、货架和小票

这个概念听起来高大上,其实就是咱们奶茶店的后台、前台和你手里的菜单。

  • 内模式(物理存储层):这是咱们的后厨仓库。一箱箱牛奶堆在哪儿、珍珠粉圆放在第几个货架第三层,这是最底层的物理存放方式。你作为伙计不用管这个,这是我店长的事儿。

  • 模式(概念层):这是我整理好的货架和配料表。我把仓库里的东西整理成一张张表:一张“原料库存表”、一张“饮品配方表”、一张“销售记录表”。你就知道咱们店里有什么,东西在“逻辑上”是怎么组织的。

  • 外模式(用户视图):这是给你的收银机小屏幕。我不会让你看到复杂的“原料成本价”和“供应商电话”,只给你看“饮品名称”和“价格”。这就是专门为你这个“伙计角色”定制的视图

  • 两级映像(神奇的映射)

    • 模式/内模式映像:假如我把珍珠从“第三层货架”搬到了“门口推车里”(修改了物理存储位置),我只需要改一下我脑中的“映射”,你收银机上的“珍珠奶茶”照样能点,不受影响。这就是物理独立性
    • 外模式/模式映像:假如我在后台把“饮品配方表”里加了“卡路里”一栏,但只要我不把它放到你的小屏幕上,你的操作界面一点没变。这就是逻辑独立性

2. 数据库设计:开分店六步曲

隔壁老王看咱们店火了,想加盟,咱们得给他一套标准流程,这就是数据库设计。

  1. 需求分析:老王说,“我要能点单、能查库存、能看谁买得多。”我拿出小本本记下,画成数据流图(钱怎么流、货怎么流),写成需求说明书
  2. 概念结构设计:我把老王的需求画成一幅E-R图(实体-联系图)。
    • 实体(长方形):就是人、事、物。比如“顾客”、“奶茶”、“订单”。
    • 属性(椭圆形):实体的特征。比如“顾客”有“电话”、“姓名”。
    • 联系(菱形):实体之间的关系。比如顾客购买奶茶。
  3. 逻辑结构设计:把E-R图这张画,翻译成电脑能懂的二维表格(关系模式)。
    • 1:1联系:一个店长管一家店。店长和店的信息可以放一张表。
    • 1:N联系:一个顾客可以下多个订单。那把“顾客ID”放到“订单”表里。
    • M:N联系:一杯奶茶可以用多种配料,一种配料可以加到多种奶茶里。这可麻烦了,必须单独建一张“奶茶-配料表”。
  4. 物理设计:决定这些表格文件具体怎么存,放固态硬盘还是机械硬盘,怎么建索引让查“杨枝甘露”时更快。
  5. 数据库实施:真正在电脑上把表建起来,写个收银程序,让老王试营业。
  6. 运行和维护:开业后,发现“生椰拿铁”卖爆了,系统变慢,我再回来调优。

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 顾客.姓名 = '张三';