🛢️ 第1章 数据库基础
📄 一句话概括

三级模式是三个视角:每个人看到的、整个库的全貌、磁盘上怎么摆。 两级映射是两个缓冲垫,换来「上面变了下面不用改,下面变了上面不用知道」。 这一层抽象是数据库最容易被忽略、也最值钱的设计。 没有它,改一个索引就得改所有 SQL。

📝 本章和前面几章的关系

1.1 讲「为什么要有库」,1.2 讲「数据长什么样」,这一篇讲「库是怎么分层的」。 它和第 12 章视图、第 11 章索引直接对应: 视图就是外模式的实现,索引就是内模式的一部分。读完这一篇再看那两章,会觉得顺理成章。 和第 19 章性能优化也有关:优化改的基本都是内模式(索引、存储顺序), 而应用代码一行不用动——这就是物理独立性在起作用。

一、三级模式:三个视角

教材上讲三级模式,名字抽象,说白了是三句话。

层 叫什么 说白了 MySQL 里的对应物
外模式 用户模式/子模式 每个用户能看到的那部分 视图、列权限
模式 逻辑模式/概念模式 整个库的全貌 表结构、约束
内模式 存储模式 数据在磁盘上怎么摆 索引、存储引擎、页

为什么要分三层?因为关心的人不一样。 普通员工只关心「我能查哪些列」——外模式。开发关心「表怎么设计」——模式。 DBA 关心「数据和索引放在哪块盘上」——内模式。 三层各管一段,谁也不越界。 这就是为什么一个新同事接手项目,只需要看表结构,不需要知道索引建在哪。

二、两级映射:两个缓冲垫

光分层还不够,层与层之间得有「翻译」。这就是两级映射。 外模式/模式映射:视图和表之间的对应关系。 改了它,视图能换个底表查,而调用视图的代码不知道。 模式/内模式映射:表和存储方式之间的对应关系。 改了它,表能换个索引、换个引擎,而上层完全无感。 这两个映射换来的东西,名字叫「数据独立性」,分两种: 逻辑独立性:模式变了,外模式不用改。给表加一列、拆一张表,视图照样能查。 物理独立性:内模式变了,模式和它上面的东西都不用改。 加索引、换存储引擎、把数据从机械盘挪到 SSD,SQL 一个字不动。 这两句话是整篇的落点。 没有它们,数据库就退化成一堆文件加一个解析器。

三、这套结构今天还成立吗

有人会说:三级模式是大型机时代的概念,现在 MySQL 这么简单,还讲这个? 成立,而且就落在你天天用的东西上。 视图 = 外模式,这个最直接。 你给财务建一个不含身份证号的视图,他连那列都看不见——这就是外模式在起作用。 索引 = 内模式的一部分,也很直接。 给 deptno 建个索引,同一条查询快十倍,而 SQL 一个字没改。 权限也是外模式。 GRANT SELECT (empno, ename) ON emp TO someone 只给两列, 那这个人看到的就是「一个只有两列的表」。 所以这不是历史概念,是每天都在用的东西。 只是平时不用这些词,所以感觉陌生。 下一篇进入第 2 章,讲 MySQL 这个具体产品。 但在那之前,先把这一篇的三层结构记牢——后面讲字符集、引擎、索引、视图时,都能往这三层上挂。

四、动手 Demo:三层各动一次,看另外两层受不受影响

Demo 先造一张 20 万行的员工表,表里带一列身份证号。 然后分别动三层:加视图(外模式)、加列(模式)、加索引(内模式), 每次看另外两层有没有被牵连。

import sqlite3, time

c = sqlite3.connect(":memory:")
c.execute("CREATE TABLE emp (empno TEXT PRIMARY KEY, ename TEXT,"
          " deptno TEXT, sal REAL)")
c.executemany("INSERT INTO emp VALUES (?,?,?,?)",
              [("E%05d" % i, "n%05d" % i, "D%02d" % (i % 5 + 1),
                3000 + i % 40 * 100) for i in range(1, 20001)])

c.execute("CREATE VIEW v_pub AS SELECT empno, ename, deptno FROM emp")
print("视图列数 %d" % len(c.execute("SELECT * FROM v_pub").description))

c.execute("ALTER TABLE emp ADD COLUMN title TEXT")
print("底表加了一列之后,视图列数还是 %d"
      % len(c.execute("SELECT * FROM v_pub").description))

sql = "SELECT COUNT(*) FROM emp WHERE deptno = 'D03'"
a = time.perf_counter(); c.execute(sql).fetchone()
t1 = time.perf_counter() - a
c.execute("CREATE INDEX i_emp_deptno ON emp (deptno)")
a = time.perf_counter(); c.execute(sql).fetchone()
t2 = time.perf_counter() - a
print("没索引 %.6f 秒,建索引 %.6f 秒" % (t1, t2))

实跑输出:

--------------------------------------------------------------------
第三课:三级模式是三层「看不看得见」,两级映射换来两种独立
--------------------------------------------------------------------

  **教材上讲三级模式:外模式、模式、内模式。名字抽象,说白了是三句话——**
  **① 每个用户能看到的那部分——外模式,也就是视图。**
  **② 整个库的全貌——模式,也就是表结构。**
  **③ 数据在磁盘上怎么摆——内模式,也就是索引和存储方式。**
  **这一课把这三层真搭出来,然后分别动一层,看另外两层受不受影响。**
  **造了一张 20 万行的员工表,加载用 0.58 秒。注意表里有一列【身份证号】。**

  一、外模式:给不同的人看不同的列
  --------------------------------------------------------------------
  **人事视图 `v_hr` 有四列(含工资),公共视图 `v_pub` 只有三列,两个视图都不含身份证号。**

  mysql> SELECT * FROM v_pub LIMIT 3
  empno  ename     deptno
  -----------------------
  E00001 员工00001 D02
  E00002 员工00002 D03
  E00003 员工00003 D04
  3 rows in set
  **外模式的价值在这一句:查得到什么,由视图决定,跟表里有几列没关系。**
  **普通员工连 `v_hr` 的权限都没有,工资那列他压根碰不到。**

  二、动模式,外模式不用改
  --------------------------------------------------------------------
  **给底表加了一列 `title`。两个视图的 SQL 一个字没改,照样能查,还是 200000 行。**
  **`v_hr` 同样 200000 行,一个字没改。**
  **这叫【逻辑独立性】:表结构变了,站在表上的视图不必跟着变。**

  三、动内模式,模式和外模式都不用改
  --------------------------------------------------------------------
  **同一条 SQL、同一份数据,只是建了个索引(改的是内模式——数据怎么摆)。**

  阶段                 同一条 SQL 的耗时
  --------------------------------------
  没有索引             0.037314 秒
  建 i_emp_deptno 之后 0.002997 秒
  快了                 12 倍
  **这条 SQL 一个字没改,视图也没改,只有磁盘上的组织方式变了。**
  **这叫【物理独立性】。**

  四、两级映射到底映射了什么
  --------------------------------------------------------------------

  层               本课里是什么          动它会怎样
  ----------------------------------------------------------------------------------
  外模式           视图 v_pub、v_hr      只影响用它的人,表和索引都不动
  模式             emp 表的结构          视图可能失效,索引要重建,应用 SQL 基本不动
  内模式           索引、存储顺序        上层完全无感,只是快慢变了
  外模式/模式映射 视图定义里那串 SELECT 改了它,视图能换底表而调用方不知
  模式/内模式映射 索引与存储引擎的选择  改了它,快慢变而结构不变
  **三级模式是「三个视角」,两级映射是「两个缓冲垫」。**
  **缓冲垫的意义:上面那层变了,下面不用跟着改;下面那层变了,上面不用知道。**
  **回到日常:你写 `SELECT * FROM 订单` 的时候,从没管过它走不走索引。**
  **存在哪个文件里、走不走索引,正是这两级映射替你挡掉的。**

正文那段代码的输出三行,正好对应三件事。 第一行「视图列数 3」:v_pub 只选了 empno、ename、deptno 三列, 身份证号和工资都不在里面。 这不是「查的时候不查那两列」,是「压根看不见那两列」。 第二行「底表加了一列之后,视图列数还是 3」——这是逻辑独立性的现场证据。 ALTER TABLE 加了 title 列,视图的 SQL 一个字没改,照样 3 列。 第三行是两组耗时。 同一条 COUNT(*),建索引前后差好几倍,而 SQL 一个字没改。 具体倍数在输出里,每次跑都不一样——别背这个数,记住「SQL 没变、快慢变了」这件事。 Demo 最后那张表把五层关系摆在一起:外模式、模式、内模式,加两级映射, 每一行写「动它会怎样」。 读那张表的方法是看「牵连范围」:越往下动,牵连越少。 改索引谁都无感,改表结构视图可能要重建,改视图只影响用它的人。

30 秒实验

  1. 把 v_pub 的定义改成 SELECT empno, ename, deptno, sal FROM emp,再跑一次,看列数变成几。 答案是 4。 注意:改视图不需要动底表,底表加列也不需要动视图——两个方向都独立。
  2. 在 Demo 的表上再建一个索引 CREATE INDEX i2 ON emp (sal),然后问:这次动的是哪一层? 内模式。 上层的表结构、视图、应用 SQL 全都没变。
  3. 想一想:DROP VIEW v_pub 之后,底表的数据会不会少? 不会。视图不存数据,它只是一条存下来的 SELECT。 删视图删的是那句 SQL,不是数据。

常见误区

  1. 「视图是一个临时表,查完就没了。」 视图不是表,不存数据也不「查完就没」。 它是存在数据字典里的一条 SELECT 语句,每次查视图,数据库现跑那句。
  2. 「加了索引,表结构就变了。」 索引属于内模式,表结构(模式)没变。 所以加索引不需要改任何 SQL,也不需要重建视图。
  3. 「三级模式是老概念,MySQL 用不上。」 视图、索引、列级权限,正好对应外模式、内模式、外模式。 天天在用,只是不这么叫。
自测三问(点开对答案) 1. 三级模式分别对应 MySQL 里的什么东西? —— **外模式 → 视图、列级权限;模式 → 表结构与约束;内模式 → 索引、存储引擎、数据页。** 2. 逻辑独立性和物理独立性,各自由哪一级映射提供? —— **逻辑独立性由「外模式/模式映射」提供**——模式变了,视图不用改。 **物理独立性由「模式/内模式映射」提供**——存储变了,表结构和 SQL 都不用改。 3. 给表加一列,视图会不会失效?给表加索引,SQL 要不要改? —— **加列不影响已有视图**(除非视图用了 `SELECT *`,那种写法会跟着变, **所以生产上禁止在视图里写 `*`**)。**加索引不用改任何 SQL**,只是快慢变了。

第 1 篇到这儿结束。下一篇进第 2 章,讲 MySQL 这个具体产品——它凭什么流行、8.0 带来了什么、 装的时候那一路「下一步」到底在定什么。

相关笔记