数据库三级模式与两级映像详解
前言
在学习数据库的过程中,你一定听说过"三级模式"和"两级映像"这两个概念。很多初学者觉得这些概念很抽象、难以理解。其实,这些概念的背后是一套非常优雅的设计思想,它解决了数据库系统中一个核心问题:如何让应用程序和数据存储相互独立,互不干扰?
数据库系统的体系结构概述
想象一下,你住在一栋公寓楼里。你只需要知道自己的房间号和钥匙,就可以进入自己的房间。你不需要关心这栋楼的地基是怎么打的、电线是怎么布的、水管是怎么走的。这就是一种"分层"的思想------每一层只关心自己该关心的事。
数据库系统也采用了类似的分层设计。三级模式结构就是把数据库系统分成三个层次:
- 外模式:用户看到的数据(你的房间)
- 模式:数据库的整体逻辑结构(整栋楼的设计图)
- 内模式:数据的物理存储方式(地基、电线、水管)
为什么需要三级模式结构
你可能会问:为什么要搞这么复杂?直接把数据存起来不就行了吗?
让我举个例子。假设你开发了一个电商系统,用户信息直接存在一个文件里。有一天,老板说:"我们要把用户数据从一个文件拆成多个文件,这样查询更快。"如果你的程序代码直接操作文件,那你就惨了------所有涉及用户数据的代码都要改!
但如果你用了三级模式,情况就完全不同了:
- 存储方式的改变只影响内模式
- 程序代码操作的是外模式
- 两者之间有映像来做转换
这样,存储方式怎么变,你的程序代码都不用改。这就是数据独立性的威力!
数据独立性的重要意义
数据独立性分为两种:
- 物理数据独立性:存储方式改变时,应用程序不需要修改
- 逻辑数据独立性:数据库逻辑结构改变时,应用程序不需要修改
这两种独立性是现代数据库系统的基石。没有它们,每次数据库有任何改动,所有相关程序都要跟着改,维护成本会高得吓人。
第一章 数据库系统基础概念
在深入学习三级模式之前,我们先来回顾一些基础概念。这些概念是理解后续内容的基石。
1.1 数据与数据库
数据的定义与特性
数据(Data) 是描述事物的符号记录。
用大白话说,数据就是我们用来描述现实世界中各种事物的信息。比如:
- 你的名字"张三"是数据
- 你的年龄"25"是数据
- 你的照片也是数据
- 甚至一段语音、一个视频都是数据
数据有几个重要特性:
- 数据需要解释:单独的"25"没有意义,只有知道它代表"年龄"才有意义
- 数据有类型:数字、文字、图片、音频、视频等
- 数据可以被处理:排序、筛选、统计、分析等
数据库的概念
数据库(Database,简称DB) 是长期存储在计算机内、有组织的、可共享的大量数据的集合。
我们用一个形象的比喻来理解:
| 日常生活 | 数据库概念 |
|---|---|
| 图书馆 | 数据库 |
| 书架 | 数据表 |
| 书 | 数据记录 |
| 书名、作者、出版社 | 字段 |
| 图书馆管理员 | 数据库管理系统 |
图书馆里的书不是随便乱放的,而是按照一定规则(如分类号)组织起来的。读者可以借阅,管理员可以管理。数据库也是一样------数据有组织地存放,多个程序可以共享使用。
数据库的基本特征
一个真正的数据库具有以下特征:
-
数据结构化
- 不是杂乱无章的数据堆积
- 数据之间有明确的关系
- 比如:订单和用户之间有关联
-
数据共享性高,冗余度低
- 多个应用程序可以同时访问同一个数据库
- 相同的数据只存一份,避免重复存储
- 比如:用户信息只存一处,订单系统和会员系统都能访问
-
数据独立性高
- 数据的存储方式和应用程序相互独立
- 修改存储结构不影响程序
- 这正是三级模式要解决的核心问题!
-
数据由DBMS统一管理和控制
- 安全性控制:谁能看什么数据
- 完整性控制:数据要符合规则
- 并发控制:多人同时操作不出错
- 恢复控制:出了问题能恢复
1.2 数据库管理系统(DBMS)
DBMS的定义与功能
数据库管理系统(Database Management System,简称DBMS) 是位于用户和操作系统之间的一层数据管理软件。
你可以把DBMS想象成一个超级管家:
- 你不需要亲自去仓库找东西,只要告诉管家你要什么
- 管家知道东西放在哪里,帮你取来
- 管家还负责保管钥匙,防止小偷进来
- 管家还会记账,知道什么东西放在什么位置
DBMS的主要功能包括:
| 功能 | 说明 | 比喻 |
|---|---|---|
| 数据定义 | 创建、修改、删除数据库结构 | 设计仓库的货架布局 |
| 数据操作 | 增、删、改、查数据 | 存货、取货、盘点 |
| 数据库运行管理 | 并发控制、安全检查、完整性检查 | 门禁管理、出入登记 |
| 数据库建立和维护 | 备份、恢复、性能优化 | 仓库维护、消防检查 |
DBMS的主要组成部分
一个完整的DBMS通常包含以下组件:
┌─────────────────────────────────────────────┐
│ 用户/应用程序 │
└──────────────────────┬──────────────────────┘
│ SQL语句
▼
┌─────────────────────────────────────────────┐
│ 查询处理器 │
│ ┌─────────┐ ┌─────────┐ ┌─────────────┐ │
│ │ SQL解析 │→│ 查询优化 │→│ 执行引擎 │ │
│ └─────────┘ └─────────┘ └─────────────┘ │
└──────────────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 存储管理器 │
│ ┌─────────┐ ┌─────────┐ ┌─────────────┐ │
│ │缓冲管理 │ │文件管理 │ │ 索引管理 │ │
│ └─────────┘ └─────────┘ └─────────────┘ │
└──────────────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 磁盘存储 │
└─────────────────────────────────────────────┘
- 查询处理器:负责解析SQL语句,优化查询,执行操作
- 存储管理器:负责与文件系统交互,管理数据的物理存储
- 事务管理器:保证数据操作的原子性和一致性
- 缓冲管理器:管理内存缓冲区,提高访问效率
常见的DBMS产品介绍
目前市场上主流的DBMS产品有:
| DBMS | 类型 | 特点 | 适用场景 |
|---|---|---|---|
| MySQL | 关系型 | 开源免费、性能好、社区活跃 | Web应用、中小型系统 |
| Oracle | 关系型 | 功能强大、稳定可靠、价格昂贵 | 大型企业、金融、电信 |
| SQL Server | 关系型 | 微软产品、与Windows集成好 | 微软技术栈企业 |
| PostgreSQL | 关系型 | 开源、功能丰富、标准兼容 | 复杂业务、GIS应用 |
| MongoDB | 文档型 | 灵活的文档结构、易于扩展 | 内容管理、日志存储 |
| Redis | 键值型 | 内存存储、极快的读写速度 | 缓存、会话管理 |
1.3 数据库系统的组成
数据库系统(Database System,简称DBS) 是指在计算机系统中引入数据库后的系统。它是一个完整的人机系统。
一个完整的数据库系统由以下几个部分组成:
┌────────────────────────────────────────────────────────┐
│ 数据库系统 (DBS) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 用户 │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────────┐ │ │
│ │ │最终用户│ │应用程序│ │开发人员│ │ DBA │ │ │
│ │ └────────┘ └────────┘ └────────┘ └────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 数据库管理系统 (DBMS) │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 数据库 (DB) │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 硬件系统 │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
硬件系统
数据库系统需要运行在硬件上,主要包括:
- 计算机主机:CPU、内存
- 存储设备:硬盘、SSD、磁盘阵列
- 网络设备:用于分布式系统和远程访问
- 其他外设:终端、打印机等
对于数据库而言,存储设备 和内存尤为重要:
- 存储设备决定了能存多少数据
- 内存大小影响缓存性能和并发处理能力
软件系统
软件系统包括:
- 操作系统:如Linux、Windows Server
- DBMS:如MySQL、Oracle
- 开发工具:如IDE、数据库客户端工具
- 应用程序:基于数据库开发的业务系统
数据库
这里的"数据库"指的是存储的数据本身,包括:
- 用户数据:业务产生的实际数据
- 元数据:描述数据的数据(也叫数据字典),如表结构定义
- 索引数据:用于加速查询的辅助数据
数据库管理员(DBA)
DBA(Database Administrator) 是负责管理数据库系统的专业人员。
DBA的主要职责:
| 职责 | 具体工作 |
|---|---|
| 数据库设计 | 参与需求分析,设计数据库结构 |
| 数据库维护 | 备份恢复、性能调优、空间管理 |
| 安全管理 | 用户权限管理、数据加密、审计 |
| 故障处理 | 监控系统状态、处理各种故障 |
| 升级迁移 | 数据库版本升级、数据迁移 |
一个好的DBA就像数据库的"保姆"+"医生"+"保安",需要:
- 保证数据库7×24小时稳定运行
- 发现问题并及时"治疗"
- 防止数据被非法访问
用户
数据库系统的用户可以分为三类:
-
最终用户(End User)
- 通过应用程序间接使用数据库
- 不需要了解数据库技术
- 如:使用淘宝购物的消费者
-
应用程序员(Application Programmer)
- 开发基于数据库的应用程序
- 需要掌握SQL和编程语言
- 如:后端开发工程师
-
数据库管理员(DBA)
- 负责数据库的管理和维护
- 需要深入了解数据库原理
- 是数据库系统的"守护者"
第二章 三级模式结构
现在我们进入本文的重点内容------三级模式结构。这是数据库体系结构的核心,理解了它,你就掌握了数据库设计的精髓。
2.1 三级模式概述
三级模式的提出背景
在数据库发展的早期,应用程序和数据的存储是紧密耦合的。这带来了很多问题:
早期的数据管理方式:
┌─────────────┐ ┌─────────────┐
│ 程序A │───────→│ 文件1 │
└─────────────┘ └─────────────┘
┌─────────────┐ ┌─────────────┐
│ 程序B │───────→│ 文件2 │
└─────────────┘ └─────────────┘
问题:
❌ 数据冗余严重(同样的数据存多份)
❌ 数据不一致(各处数据不同步)
❌ 程序依赖存储(文件格式变了,程序就要改)
为了解决这些问题,美国国家标准协会(ANSI)的SPARC委员会在1975年提出了著名的三级模式结构。这个架构成为了现代数据库系统的理论基础。
ANSI/SPARC三级模式架构
三级模式把数据库系统分成三个抽象层次:
┌─────────────────────────────────────────┐
│ 用户视角 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 外模式1 │ │ 外模式2 │ │ 外模式3 │ │
│ │(用户A) │ │(用户B) │ │(用户C) │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
└───────┼───────────┼───────────┼─────────┘
│ │ │
└───────────┼───────────┘
│ 外模式/模式映像
▼
┌─────────────────────────────────────────┐
│ 数据库视角 │
│ ┌─────────────────────────────────────┐ │
│ │ 模式(概念模式) │ │
│ │ 数据库的全局逻辑结构 │ │
│ └──────────────────┬──────────────────┘ │
└─────────────────────┼───────────────────┘
│ 模式/内模式映像
▼
┌─────────────────────────────────────────┐
│ 存储视角 │
│ ┌─────────────────────────────────────┐ │
│ │ 内模式 │ │
│ │ 数据的物理存储结构 │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 物理存储 │
│ 磁盘、文件、数据块 │
└─────────────────────────────────────────┘
用一个更形象的比喻来理解:
| 层次 | 比喻 | 关心的问题 |
|---|---|---|
| 外模式 | 你在淘宝看到的商品页面 | 用户看到什么数据 |
| 模式 | 淘宝数据库的表结构设计 | 数据之间有什么关系 |
| 内模式 | 阿里云服务器上的存储配置 | 数据怎么存才快 |
作为买家,你只关心商品名称、价格、评价;淘宝的工程师关心表怎么设计;运维人员关心数据存在哪个磁盘上。各司其职,互不干扰。
三级模式的层次关系图
让我们用一个具体的例子来说明三级模式的层次关系。假设我们有一个学生选课系统:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
外模式层
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
学生视图 教师视图
┌────────────────┐ ┌────────────────┐
│ 学号 │ │ 工号 │
│ 姓名 │ │ 姓名 │
│ 已选课程列表 │ │ 教授课程列表 │
│ 成绩 │ │ 班级学生成绩 │
└────────────────┘ └────────────────┘
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
模式层
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
学生表 课程表 选课表
┌───────────┐ ┌───────────┐ ┌───────────┐
│ 学号(PK) │ │ 课程号(PK)│ │ 学号(FK) │
│ 姓名 │ │ 课程名 │ │ 课程号(FK)│
│ 性别 │ │ 学分 │ │ 成绩 │
│ 出生日期 │ │ 教师工号 │ │ 选课时间 │
│ 班级 │ └───────────┘ └───────────┘
└───────────┘
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
内模式层
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
存储文件配置:
├── student.ibd (学生表数据文件,按学号B+树索引)
├── course.ibd (课程表数据文件)
├── selection.ibd (选课表数据文件,联合索引)
└── 索引策略:学号字段创建聚簇索引,课程号创建二级索引
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2.2 外模式(External Schema)
外模式的定义与作用
外模式(External Schema) ,也称为子模式(Subschema) 或 用户模式(User Schema),是数据库用户(包括应用程序员和最终用户)能够看到和使用的局部数据的逻辑结构和特征的描述。
用大白话说:外模式就是用户眼中的数据库。
每个用户只能看到和操作与自己相关的那部分数据。就像:
- 你登录淘宝只能看自己的订单,看不到别人的
- 你登录网银只能看自己的账户余额,看不到别人的
- 你登录学生系统只能看自己的成绩,看不到其他同学的
外模式的特点
1. 用户视图
外模式是用户与数据库系统的接口。不同用户可以有不同的外模式:
sql
-- 学生的外模式(视图)
CREATE VIEW StudentView AS
SELECT 学号, 姓名, 课程名, 成绩
FROM 学生表
JOIN 选课表 ON 学生表.学号 = 选课表.学号
JOIN 课程表 ON 选课表.课程号 = 课程表.课程号;
-- 教务管理员的外模式(视图)
CREATE VIEW AdminView AS
SELECT 学号, 姓名, 班级,
AVG(成绩) as 平均分,
COUNT(*) as 选课数
FROM 学生表
JOIN 选课表 ON 学生表.学号 = 选课表.学号
GROUP BY 学号, 姓名, 班级;
同样是成绩数据,学生只能看自己的详细成绩,而教务管理员能看所有学生的统计数据。
2. 局部数据描述
外模式只描述用户关心的那部分数据,而不是整个数据库:
完整的学生表(模式层):
┌──────┬──────┬──────┬──────────┬──────┬───────────┬──────┐
│ 学号 │ 姓名 │ 性别 │ 出生日期 │ 班级 │ 身份证号 │ 密码 │
└──────┴──────┴──────┴──────────┴──────┴───────────┴──────┘
学生自己看到的(外模式):
┌──────┬──────┬──────┐
│ 学号 │ 姓名 │ 班级 │
└──────┴──────┴──────┘
教务员看到的(外模式):
┌──────┬──────┬──────┬──────────┬──────┐
│ 学号 │ 姓名 │ 性别 │ 出生日期 │ 班级 │
└──────┴──────┴──────┴──────────┴──────┘
3. 数据安全保护
外模式是实现数据安全的天然屏障:
- 用户只能访问外模式中定义的数据
- 敏感数据(如密码、身份证号)可以不出现在任何外模式中
- 这是一种"最小权限原则"的体现
外模式的设计原则
设计外模式时应遵循以下原则:
- 最小必要原则:只暴露用户需要的数据
- 安全性原则:隐藏敏感信息
- 简洁性原则:视图结构要便于理解和使用
- 稳定性原则:外模式应保持相对稳定,减少对应用程序的影响
外模式与应用程序的关系
应用程序通过外模式访问数据库:
┌─────────────────┐
│ 应用程序A │ ←→ 外模式1(学生视图)
└─────────────────┘
↓
┌─────────────────┐
│ 应用程序B │ ←→ 外模式2(教师视图)
└─────────────────┘
↓
┌─────────────────┐
│ 应用程序C │ ←→ 外模式3(管理员视图)
└─────────────────┘
关键点:
✅ 一个外模式可以被多个应用程序使用
✅ 一个应用程序只能使用一个外模式
✅ 应用程序不能直接访问模式层
外模式实例分析
让我们看一个电商系统的外模式设计:
sql
-- 基础表结构(模式层)
-- 用户表、商品表、订单表、订单明细表、评价表...
-- 外模式1:买家视图
CREATE VIEW BuyerView AS
SELECT
o.订单号, o.下单时间, o.总金额, o.状态,
p.商品名, d.数量, d.单价
FROM 订单表 o
JOIN 订单明细表 d ON o.订单号 = d.订单号
JOIN 商品表 p ON d.商品ID = p.商品ID
WHERE o.买家ID = CURRENT_USER_ID();
-- 买家只能看自己的订单
-- 外模式2:卖家视图
CREATE VIEW SellerView AS
SELECT
o.订单号, o.下单时间,
u.收货地址, u.联系电话, -- 卖家需要配送信息
p.商品名, d.数量,
d.成本价, d.售价, (d.售价 - d.成本价) as 利润 -- 卖家关心利润
FROM 订单表 o
JOIN 订单明细表 d ON o.订单号 = d.订单号
JOIN 商品表 p ON d.商品ID = p.商品ID
JOIN 用户表 u ON o.买家ID = u.用户ID
WHERE p.卖家ID = CURRENT_SELLER_ID();
-- 卖家能看到配送地址和利润信息
-- 外模式3:平台运营视图
CREATE VIEW OperationView AS
SELECT
DATE(下单时间) as 日期,
COUNT(*) as 订单数,
SUM(总金额) as GMV,
SUM(平台佣金) as 佣金收入
FROM 订单表
GROUP BY DATE(下单时间);
-- 运营只关心整体数据,看不到个人信息
2.3 模式/概念模式(Conceptual Schema)
模式的定义与作用
模式(Schema) ,也称为概念模式(Conceptual Schema) 或 逻辑模式(Logical Schema),是数据库中全体数据的逻辑结构和特征的描述,是所有用户的公共数据视图。
用大白话说:模式就是数据库管理员眼中的数据库,记录了"数据库里有哪些表、表里有哪些字段、表与表之间什么关系"。
模式是三级架构的中间层 ,也是核心层:
- 向上,为外模式提供数据源
- 向下,指导内模式如何存储
模式的特点
1. 全局逻辑结构描述
模式描述了数据库的整体逻辑蓝图:
sql
-- 这就是模式的定义(DDL语句)
CREATE TABLE 学生 (
学号 CHAR(10) PRIMARY KEY,
姓名 VARCHAR(20) NOT NULL,
性别 CHAR(1) CHECK (性别 IN ('男', '女')),
出生日期 DATE,
班级 VARCHAR(20)
);
CREATE TABLE 课程 (
课程号 CHAR(6) PRIMARY KEY,
课程名 VARCHAR(50) NOT NULL,
学分 INT CHECK (学分 > 0 AND 学分 <= 10)
);
CREATE TABLE 选课 (
学号 CHAR(10),
课程号 CHAR(6),
成绩 DECIMAL(5,2),
PRIMARY KEY (学号, 课程号),
FOREIGN KEY (学号) REFERENCES 学生(学号),
FOREIGN KEY (课程号) REFERENCES 课程(课程号)
);
2. 数据之间的联系
模式定义了实体之间的关系:
学生 ────< 选课 >──── 课程
一个学生可以选多门课程(一对多)
一门课程可以被多个学生选(一对多)
学生与课程之间是多对多的关系,通过选课表关联
3. 完整性约束
模式包含数据必须满足的约束条件:
| 约束类型 | 说明 | 示例 |
|---|---|---|
| 主键约束 | 唯一标识每条记录 | 学号是学生表的主键 |
| 外键约束 | 保证引用完整性 | 选课表的学号必须存在于学生表 |
| 唯一约束 | 字段值不能重复 | 身份证号唯一 |
| 非空约束 | 字段不能为空 | 姓名不能为空 |
| 检查约束 | 字段值必须满足条件 | 成绩在0-100之间 |
| 默认约束 | 未指定时使用默认值 | 选课时间默认为当前时间 |
4. 安全性定义
模式还可以包含访问控制定义:
sql
-- 授权示例
GRANT SELECT ON 学生 TO 普通教师;
GRANT SELECT, INSERT, UPDATE ON 成绩 TO 教务员;
GRANT ALL PRIVILEGES ON 学生 TO 管理员;
模式的设计原则
设计模式时应遵循以下原则:
- 规范化原则:遵循数据库范式,减少冗余
- 完整性原则:定义必要的约束条件
- 可扩展原则:结构设计要便于未来扩展
- 性能兼顾原则:逻辑设计要考虑查询性能
模式与数据模型的关系
模式是用某种数据模型来描述的:
数据模型 模式表现形式
─────────────────────────────────────
关系模型 → 表(Table)
面向对象模型 → 类(Class)
文档模型 → 集合(Collection)
图模型 → 节点和边(Node/Edge)
目前最常用的是关系模型 ,所以我们通常看到的模式就是一系列表定义。
模式实例分析
以一个在线教育平台为例:
┌─────────────────────────────────────────────────────────────┐
│ 模式设计 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户表 课程表 │
│ ┌────────────┐ ┌────────────┐ │
│ │ 用户ID(PK) │ │ 课程ID(PK) │ │
│ │ 用户名 │ │ 课程名 │ │
│ │ 密码 │ │ 讲师ID(FK) │←──┐ │
│ │ 邮箱 │ │ 价格 │ │ │
│ │ 用户类型 │ │ 分类ID(FK) │ │ │
│ └─────┬──────┘ └────────────┘ │ │
│ │ 1 │ 1 │ │
│ │ │ │ │
│ │ N │ N │ │
│ ┌─────┴──────┐ ┌──────┴─────┐ │ │
│ │ 订单表 │ │ 章节表 │ │ │
│ │ 订单ID(PK) │ │ 章节ID(PK) │ │ │
│ │ 用户ID(FK) │ │ 课程ID(FK) │ │ │
│ │ 课程ID(FK) │──────────────│ 标题 │ │ │
│ │ 金额 │ │ 视频URL │ │ │
│ │ 状态 │ │ 排序 │ │ │
│ └────────────┘ └────────────┘ │ │
│ │ │
│ 讲师表 ←─────────────────────────────────────┘ │
│ ┌────────────┐ │
│ │ 讲师ID(PK) │ │
│ │ 用户ID(FK) │ (讲师也是用户的一种) │
│ │ 简介 │ │
│ │ 头衔 │ │
│ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
2.4 内模式(Internal Schema)
内模式的定义与作用
内模式(Internal Schema) ,也称为存储模式(Storage Schema),是数据在数据库内部的表示方式,即对数据的物理结构和存储方式的描述。
用大白话说:内模式就是存储工程师眼中的数据库,关心的是"数据存在哪个文件里、用什么格式、怎么组织才能查得快"。
内模式是三级架构的最底层,直接面向物理存储。
内模式的特点
1. 物理存储结构
内模式定义了数据如何在磁盘上存储:
内模式结构示例(MySQL InnoDB):
数据库目录结构:
/var/lib/mysql/school/
├── student.ibd # 学生表数据+索引文件
├── course.ibd # 课程表数据+索引文件
├── selection.ibd # 选课表数据+索引文件
├── db.opt # 数据库配置
└── *.frm # 表结构定义(MySQL 5.x)
每个.ibd文件内部结构:
┌─────────────────────────────────────┐
│ 表空间头 │
├─────────────────────────────────────┤
│ 段(Segment):存放索引段和数据段 │
├─────────────────────────────────────┤
│ 区(Extent):64个连续页 = 1MB │
├─────────────────────────────────────┤
│ 页(Page):16KB,基本IO单位 │
├─────────────────────────────────────┤
│ 行(Row):实际的数据记录 │
└─────────────────────────────────────┘
2. 存储路径
内模式定义数据文件的存放位置:
# 不同操作系统的默认路径
Linux: /var/lib/mysql/
Windows: C:\ProgramData\MySQL\MySQL Server 8.0\Data\
macOS: /usr/local/mysql/data/
# 可以自定义存储位置
[mysqld]
datadir=/data/mysql/ # 数据文件目录
innodb_data_home_dir=/ssd/ # InnoDB数据放SSD上
3. 索引组织方式
内模式定义如何建立索引来加速查询:
索引类型对比:
B+树索引(最常用):
┌─────────────────┐
│ 10 | 20 | 30 │ ← 根节点
└───┬─────┬───────┘
┌───────┘ └───────┐
┌────┴─────┐ ┌─────┴────┐
│ 5 | 8 │ │ 25 | 28 │ ← 叶子节点
└──────────┘ └──────────┘
↓ ↓
[数据行] [数据行]
Hash索引(等值查询快):
┌────────┐
│ Hash() │──→ 桶0: 数据
│ │──→ 桶1: 数据
│ │──→ 桶2: 数据
└────────┘
全文索引(文本搜索):
倒排索引结构,适合关键词搜索
4. 数据压缩与加密
内模式可以定义数据的压缩和加密策略:
sql
-- 创建压缩表
CREATE TABLE logs (
id INT,
message TEXT
) ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
-- 创建加密表空间(MySQL 8.0+)
CREATE TABLESPACE secure_space
ADD DATAFILE 'secure.ibd'
ENCRYPTION='Y';
内模式的设计考量
设计内模式时需要考虑:
| 考量因素 | 选择 | 适用场景 |
|---|---|---|
| 存储引擎 | InnoDB / MyISAM | 事务需求 vs 读密集 |
| 索引类型 | B+树 / Hash / 全文 | 范围查询 vs 等值查询 vs 搜索 |
| 行格式 | Compact / Dynamic / Compressed | 普通 / 大字段 / 压缩 |
| 分区策略 | Range / List / Hash | 按时间 / 按分类 / 均匀分布 |
| 存储位置 | HDD / SSD / 内存 | 成本 vs 性能 |
内模式与物理存储的关系
内模式(逻辑描述) 物理存储(实际实现)
────────────────────────────────────────────────────
表student → student.ibd文件
聚簇索引(学号) → B+树结构,叶子节点存完整行
二级索引(姓名) → B+树结构,叶子节点存主键值
二级索引(班级) → B+树结构,叶子节点存主键值
页大小16KB → 磁盘读写以16KB为单位
预读机制 → 顺序读取时预先加载相邻页
缓冲池 → 热点数据缓存在内存中
内模式实例分析
让我们看一个电商系统的内模式设计:
sql
-- 订单表的内模式设计
-- 1. 选择存储引擎
CREATE TABLE orders (
order_id BIGINT PRIMARY KEY,
user_id INT,
total_amount DECIMAL(10,2),
status TINYINT,
create_time DATETIME,
INDEX idx_user (user_id),
INDEX idx_time (create_time)
) ENGINE=InnoDB; -- 支持事务,数据安全
-- 2. 分区设计(按月分区)
ALTER TABLE orders
PARTITION BY RANGE (YEAR(create_time)*100 + MONTH(create_time)) (
PARTITION p202401 VALUES LESS THAN (202402),
PARTITION p202402 VALUES LESS THAN (202403),
PARTITION p202403 VALUES LESS THAN (202404),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
-- 3. 存储优化
-- 热数据(近3个月)放SSD
-- 冷数据(历史数据)放HDD
-- 这通常通过表空间配置实现
2.5 三级模式之间的关系
层次结构图解
现在我们把三级模式放在一起,看看它们是如何协作的:
┌─────────────────────────────────────────────────────────────────┐
│ 应用层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 学生App │ │ 教师App │ │ 管理后台 │ │ 报表系统 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└───────┼────────────┼────────────┼────────────┼─────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ 外模式层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │学生视图 │ │教师视图 │ │完整视图 │ │统计视图 │ │
│ │(部分字段)│ │(部分字段)│ │(所有字段)│ │(聚合数据)│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└───────┼────────────┼────────────┼────────────┼─────────────────┘
│ │ │ │
│ 外模式/模式映像(多对一) │
│ │ │ │
└────────────┴─────┬──────┴────────────┘
▼
┌─────────────────────────────────────────────────────────────────┐
│ 模式层 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 概念模式(唯一) │ │
│ │ 学生表 ←→ 选课表 ←→ 课程表 ←→ 教师表 │ │
│ │ │ │
│ │ 完整性约束 + 安全性定义 + 所有字段 │ │
│ └───────────────────────────┬────────────────────────────┘ │
└──────────────────────────────┼─────────────────────────────────┘
│
模式/内模式映像(一对一)
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 内模式层 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 存储模式(唯一) │ │
│ │ 索引定义 + 存储结构 + 分区策略 + 压缩配置 │ │
│ └───────────────────────────┬────────────────────────────┘ │
└──────────────────────────────┼─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 物理存储 │
│ 数据文件(.ibd) + 日志文件(.log) + 配置文件(.cnf) │
└─────────────────────────────────────────────────────────────────┘
各级模式的职责划分
| 模式 | 数量 | 描述对象 | 主要关注 | 使用者 |
|---|---|---|---|---|
| 外模式 | 多个 | 用户的局部数据 | 安全、便利 | 应用程序员、最终用户 |
| 模式 | 1个 | 数据库的全局结构 | 完整性、一致性 | DBA、系统分析师 |
| 内模式 | 1个 | 数据的存储方式 | 效率、空间 | DBA、系统工程师 |
三级模式的协作机制
当用户查询数据时,三级模式是这样协作的:
用户查询:SELECT 姓名, 成绩 FROM 学生成绩视图 WHERE 学号='20240001';
执行流程:
┌─────────────────────────────────────────────────────────────────┐
│ 1. 外模式解析 │
│ 用户查询的是"学生成绩视图"(外模式) │
│ DBMS找到对应的视图定义 │
└─────────────────────┬───────────────────────────────────────────┘
│ 外模式/模式映像
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. 模式转换 │
│ 将视图查询转换为基表查询: │
│ SELECT s.姓名, c.成绩 │
│ FROM 学生表 s JOIN 选课表 c ON s.学号 = c.学号 │
│ WHERE s.学号 = '20240001'; │
└─────────────────────┬───────────────────────────────────────────┘
│ 模式/内模式映像
▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. 内模式执行 │
│ - 选择执行计划(使用学号索引) │
│ - 定位数据文件位置 │
│ - 通过B+树索引找到数据页 │
│ - 读取数据块到内存 │
└─────────────────────┬───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. 物理读取 │
│ - 检查缓冲池是否有缓存 │
│ - 如无,从磁盘读取16KB数据页 │
│ - 按行格式解析数据 │
└─────────────────────────────────────────────────────────────────┘
│
▼
返回结果给用户
这个过程对用户是完全透明的。用户只需要写简单的SQL,剩下的全由DBMS自动完成!
第三章 两级映像
上一章我们学习了三级模式的结构,但三级模式本身并不能实现数据独立性。真正让数据独立性成为可能的,是连接各级模式的两级映像。
3.1 两级映像概述
映像的概念与作用
映像(Mapping) ,在数据库中指的是一种对应规则 或转换关系。
用大白话说:映像就是一个"翻译官",负责把一种表示方式转换成另一种表示方式。
生活中的映像例子:
地图 ←→ 实际地形
"地图上的1厘米 = 实际的1公里"
这就是地图与地形之间的映像关系
菜单 ←→ 后厨
"宫保鸡丁 = 鸡肉+花生+辣椒+..."
这就是菜名与配料之间的映像关系
数据库中:
外模式 ←→ 模式 ←→ 内模式
各层之间都需要映像来建立对应关系
映像的核心作用是:建立不同抽象层次之间的对应关系,使得上层不需要关心下层的具体实现细节。
为什么需要两级映像
让我们通过一个故事来理解为什么需要两级映像:
场景:一家餐厅
没有映像的情况:
┌──────────┐
│ 顾客 │ → 直接进厨房找食材 → 自己烹饪
└──────────┘
问题:
❌ 顾客需要知道食材在哪里
❌ 食材位置变了,顾客就找不到了
❌ 顾客需要会做菜
有映像的情况:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 顾客 │ ←→ │ 菜单 │ ←→ │ 厨房 │
└──────────┘ └──────────┘ └──────────┘
↑ ↑ ↑
外模式 映像 内模式
优点:
✅ 顾客只需要看菜单点餐(外模式)
✅ 厨房怎么做、食材放哪里,顾客不用管(内模式)
✅ 菜单作为"翻译官",完成转换(映像)
在数据库中,两级映像起着完全相同的作用:
| 映像 | 连接的层次 | 作用 |
|---|---|---|
| 外模式/模式映像 | 外模式 ←→ 模式 | 保证逻辑数据独立性 |
| 模式/内模式映像 | 模式 ←→ 内模式 | 保证物理数据独立性 |
两级映像在三级模式中的位置
┌─────────────────────────────────────────────────────────────┐
│ 应用程序 │
└───────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 外模式1 外模式2 外模式3 ... 外模式n │
│ (视图1) (视图2) (视图3) (视图n) │
└───────────────────────────┬─────────────────────────────────┘
│
┌─────────────┴─────────────┐
│ 外模式/模式映像 │ ← 第一级映像
│ (多个,每个外模式一个) │
└─────────────┬─────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 模式 │
│ (概念模式,唯一) │
└───────────────────────────┬─────────────────────────────────┘
│
┌─────────────┴─────────────┐
│ 模式/内模式映像 │ ← 第二级映像
│ (唯一) │
└─────────────┬─────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 内模式 │
│ (存储模式,唯一) │
└───────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 物理存储 │
└─────────────────────────────────────────────────────────────┘
3.2 外模式/模式映像
定义与作用
外模式/模式映像(External/Conceptual Mapping) 定义了外模式与模式之间的对应关系。
简单说,它告诉DBMS:用户看到的这个视图,是由底层哪些表的哪些字段组合而成的。
示例:学生成绩查询
外模式(学生看到的):
┌──────────────────────────────────────┐
│ 学生成绩视图 │
│ ┌────────┬────────┬────────┬──────┐ │
│ │ 学号 │ 姓名 │ 课程名 │ 成绩 │ │
│ └────────┴────────┴────────┴──────┘ │
└──────────────────────────────────────┘
↑ 外模式/模式映像:
│ 学号 ← 学生表.学号
│ 姓名 ← 学生表.姓名
│ 课程名 ← 课程表.课程名
│ 成绩 ← 选课表.成绩
↓
模式(实际的表结构):
┌────────────┐ ┌────────────┐ ┌────────────┐
│ 学生表 │ │ 课程表 │ │ 选课表 │
│ 学号 │ │ 课程号 │ │ 学号 │
│ 姓名 │ │ 课程名 │ │ 课程号 │
│ 性别 │ │ 学分 │ │ 成绩 │
│ 出生日期 │ │ 教师ID │ │ 选课时间 │
│ 班级 │ └────────────┘ └────────────┘
│ 身份证号 │
│ 密码 │
└────────────┘
映像的实现方式
在关系数据库中,外模式/模式映像主要通过**视图(VIEW)**来实现:
sql
-- 创建外模式/模式映像
CREATE VIEW 学生成绩视图 AS
SELECT
s.学号,
s.姓名,
c.课程名,
sc.成绩
FROM 学生表 s
JOIN 选课表 sc ON s.学号 = sc.学号
JOIN 课程表 c ON sc.课程号 = c.课程号;
-- 这个视图定义就是外模式/模式映像
-- 它描述了:
-- 1. 视图中的"学号"来自学生表的"学号"字段
-- 2. 视图中的"姓名"来自学生表的"姓名"字段
-- 3. 视图中的"课程名"来自课程表的"课程名"字段
-- 4. 视图中的"成绩"来自选课表的"成绩"字段
除了视图,还可以通过以下方式实现:
- 同义词(Synonym):给表或视图起别名
- 存储过程:封装复杂的数据访问逻辑
- ORM映射:应用层的对象-关系映射
外模式/模式映像的特点
1. 多对一关系
一个模式可以有多个外模式,但每个外模式只映射到一个模式:
外模式1(学生视图) ─────┐
│
外模式2(教师视图) ─────┼────→ 模式(数据库的完整逻辑结构)
│
外模式3(管理员视图)───┘
特点:
- 一个模式可以对应多个外模式(一对多)
- 每个外模式只对应一个模式(多对一)
- 不同外模式可以重叠(都包含某些字段)
2. 字段级映射
映像可以精确到字段级别:
sql
-- 映像可以包含字段重命名
CREATE VIEW EmployeeView AS
SELECT
emp_id AS 工号, -- 字段重命名
emp_name AS 姓名,
YEAR(CURDATE()) - YEAR(birth_date) AS 年龄, -- 计算字段
CASE gender
WHEN 'M' THEN '男'
WHEN 'F' THEN '女'
END AS 性别 -- 值转换
FROM employees;
3. 可以包含条件过滤
sql
-- 安全性视图:只显示本部门员工
CREATE VIEW DeptEmployeeView AS
SELECT emp_id, emp_name, salary
FROM employees
WHERE dept_id = CURRENT_USER_DEPT(); -- 行级安全
一个模式可以有多个外模式
同一个模式可以为不同用户群体创建不同的外模式:
sql
-- 基础模式:员工表
CREATE TABLE employees (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(50),
salary DECIMAL(10,2),
bonus DECIMAL(10,2),
dept_id INT,
manager_id INT,
ssn VARCHAR(20), -- 敏感信息:社保号
bank_account VARCHAR(30) -- 敏感信息:银行账号
);
-- 外模式1:普通员工视图(只能看基本信息)
CREATE VIEW EmployeeSelfView AS
SELECT emp_id, emp_name, dept_id
FROM employees
WHERE emp_id = CURRENT_USER_ID();
-- 外模式2:经理视图(能看下属信息,但看不到薪资细节)
CREATE VIEW ManagerView AS
SELECT emp_id, emp_name, dept_id
FROM employees
WHERE manager_id = CURRENT_USER_ID();
-- 外模式3:HR视图(能看所有信息,包括薪资)
CREATE VIEW HRView AS
SELECT emp_id, emp_name, salary, bonus, dept_id
FROM employees;
-- 外模式4:财务视图(能看银行账号,用于发工资)
CREATE VIEW FinanceView AS
SELECT emp_id, emp_name, salary, bonus, bank_account
FROM employees;
映像的修改与维护
当模式发生变化时,可以通过修改映像来保持外模式不变:
sql
-- 原始模式
CREATE TABLE student (
student_id CHAR(10),
name VARCHAR(20),
class VARCHAR(20)
);
-- 原始视图(外模式)
CREATE VIEW StudentView AS
SELECT student_id AS 学号, name AS 姓名 FROM student;
-- 假设模式变化:把name拆分成first_name和last_name
ALTER TABLE student
ADD first_name VARCHAR(10),
ADD last_name VARCHAR(10);
UPDATE student SET
first_name = SUBSTRING(name, 1, 1),
last_name = SUBSTRING(name, 2);
ALTER TABLE student DROP COLUMN name;
-- 修改映像,保持外模式不变
CREATE OR REPLACE VIEW StudentView AS
SELECT
student_id AS 学号,
CONCAT(first_name, last_name) AS 姓名 -- 映像适配变化
FROM student;
-- 应用程序继续使用"学号"和"姓名",完全不受影响!
3.3 模式/内模式映像
定义与作用
模式/内模式映像(Conceptual/Internal Mapping) 定义了模式与内模式之间的对应关系。
简单说,它告诉DBMS:逻辑上的表和字段,在物理存储上是如何组织的。
示例:学生表的存储
模式(逻辑视角):
┌────────────────────────────────────────┐
│ 学生表 │
│ 字段:学号, 姓名, 性别, 出生日期, 班级 │
│ 主键:学号 │
│ 外键:无 │
└────────────────────────────────────────┘
↑ 模式/内模式映像:
│ 学号 → 聚簇索引键
│ 姓名 → 创建二级索引
│ 存储格式 → COMPACT
│ 表空间 → innodb_file_per_table
↓
内模式(物理视角):
┌────────────────────────────────────────┐
│ 存储结构 │
│ 文件:student.ibd │
│ 索引:B+树聚簇索引(学号) │
│ B+树二级索引(姓名) │
│ 页大小:16KB │
│ 行格式:COMPACT │
└────────────────────────────────────────┘
映像的实现方式
模式/内模式映像通常由DBMS自动管理,DBA可以通过以下方式影响:
sql
-- 1. 索引定义(影响数据访问路径)
CREATE INDEX idx_name ON student(name);
CREATE INDEX idx_class ON student(class);
-- 2. 存储引擎选择(影响存储结构)
CREATE TABLE student (...) ENGINE=InnoDB;
-- 3. 分区策略(影响数据分布)
CREATE TABLE orders (...)
PARTITION BY RANGE (YEAR(order_date)) (
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025)
);
-- 4. 表空间配置(影响物理存储位置)
CREATE TABLESPACE ts_student
ADD DATAFILE '/ssd/student.ibd';
ALTER TABLE student TABLESPACE ts_student;
-- 5. 行格式选择(影响存储效率)
ALTER TABLE student ROW_FORMAT=COMPRESSED;
模式/内模式映像的特点
1. 一对一关系
一个数据库只有一个模式和一个内模式,所以模式/内模式映像也是唯一的:
┌─────────────┐
│ 模式 │ ←──── 唯一
└──────┬──────┘
│
│ 模式/内模式映像(唯一)
│
▼
┌─────────────┐
│ 内模式 │ ←──── 唯一
└─────────────┘
2. 由DBMS自动维护
大部分映像信息由DBMS自动生成和维护:
DBMS自动决定的内容:
├── 数据页的组织方式
├── 索引节点的分裂与合并
├── 空闲空间的管理
├── 缓冲池的使用策略
└── 数据文件的扩展
DBA可以影响的内容:
├── 索引的创建与删除
├── 分区策略
├── 表空间配置
├── 存储引擎选择
└── 压缩与加密选项
3. 对用户透明
普通用户和应用程序完全感知不到内模式的存在:
sql
-- 用户只需要写这样的SQL
SELECT * FROM student WHERE name = '张三';
-- DBMS在内部自动完成:
-- 1. 判断使用哪个索引(idx_name)
-- 2. 定位到正确的数据页
-- 3. 从磁盘读取数据(如果不在缓冲池)
-- 4. 解析行格式,提取字段值
-- 5. 返回结果
-- 用户完全不需要知道这些细节!
模式与内模式之间的对应关系
让我们看看模式中的各个元素是如何映射到内模式的:
| 模式元素 | 内模式实现 | 说明 |
|---|---|---|
| 表(Table) | 数据文件 | 如 student.ibd |
| 行(Row) | 记录格式 | Compact / Dynamic / Compressed |
| 列(Column) | 字段存储 | 定长/变长、NULL位图 |
| 主键(Primary Key) | 聚簇索引 | B+树结构 |
| 索引(Index) | 二级索引 | B+树 / Hash / 全文 |
| 外键(Foreign Key) | 约束检查 | 运行时验证 |
| 视图(View) | 查询定义 | 存储在数据字典 |
映像的修改与维护
当内模式发生变化时,模式保持不变:
sql
-- 场景:性能优化,添加索引
-- 原始状态(无索引)
-- 查询:SELECT * FROM student WHERE class = '计算机1班';
-- 执行计划:全表扫描,慢!
-- 修改内模式:添加索引
CREATE INDEX idx_class ON student(class);
-- 现在的执行计划:索引范围扫描,快!
-- 但是模式没有任何变化,应用程序也不需要修改
-- 再比如:更换存储引擎
ALTER TABLE student ENGINE=InnoDB; -- 从MyISAM迁移
-- 物理存储完全改变了,但逻辑上表结构不变
-- 应用程序继续正常工作
3.4 两级映像的实现机制
DBMS如何管理映像
DBMS通过**数据字典(Data Dictionary)**来管理所有的映像信息:
数据字典的内容:
┌───────────────────────────────────────────────────────────┐
│ 数据字典 │
├───────────────────────────────────────────────────────────┤
│ 1. 模式信息 │
│ - 表定义:表名、字段名、数据类型、约束 │
│ - 关系定义:主键、外键、索引 │
│ │
│ 2. 外模式信息 │
│ - 视图定义:视图名、查询语句 │
│ - 权限信息:谁可以访问哪个视图 │
│ │
│ 3. 内模式信息 │
│ - 存储结构:文件位置、表空间配置 │
│ - 索引信息:索引类型、索引结构 │
│ - 分区信息:分区策略、分区边界 │
│ │
│ 4. 映像信息 │
│ - 外模式/模式映像:视图到表的转换规则 │
│ - 模式/内模式映像:表到存储结构的对应关系 │
└───────────────────────────────────────────────────────────┘
在MySQL中,这些信息存储在information_schema和mysql系统数据库中:
sql
-- 查看表的元信息(模式信息)
SELECT * FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'school';
-- 查看列信息
SELECT * FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'school' AND TABLE_NAME = 'student';
-- 查看视图定义(外模式信息)
SELECT * FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = 'school';
-- 查看索引信息(内模式信息)
SELECT * FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'school';
映像信息的存储
映像信息在DBMS中的存储:
┌────────────────────────────────────────────────────────────┐
│ DBMS内部结构 │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 查询编译器 │ │ 数据字典缓存 │ │
│ │ │ │ ┌───────────┐ │ │
│ │ SQL解析 │←───│ │ 表元数据 │ │ │
│ │ 语义检查 │ │ │ 视图定义 │ │ │
│ │ 视图展开 │ │ │ 索引信息 │ │ │
│ │ 查询优化 │ │ └───────────┘ │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ │ │ 加载 │
│ ▼ │ │
│ ┌─────────────────┐ ┌────────┴────────┐ │
│ │ 执行引擎 │ │ 系统表空间 │ │
│ └─────────────────┘ │ (ibdata1) │ │
│ │ ┌────────────┐ │ │
│ │ │数据字典表 │ │ │
│ │ │持久化存储 │ │ │
│ │ └────────────┘ │ │
│ └─────────────────┘ │
└────────────────────────────────────────────────────────────┘
数据访问的转换过程
当用户发起查询时,两级映像是这样工作的:
用户SQL: SELECT 姓名, 成绩 FROM 学生成绩视图 WHERE 学号 = '2024001';
═══════════════════════════════════════════════════════════════════
步骤1:SQL解析
═══════════════════════════════════════════════════════════════════
输入:用户的SQL语句
检查:语法是否正确
输出:语法树
═══════════════════════════════════════════════════════════════════
步骤2:外模式/模式映像转换
═══════════════════════════════════════════════════════════════════
从数据字典中找到视图定义:
CREATE VIEW 学生成绩视图 AS
SELECT s.学号, s.姓名, c.课程名, sc.成绩
FROM 学生表 s JOIN 选课表 sc ON ...
将视图查询展开为基表查询:
SELECT s.姓名, sc.成绩
FROM 学生表 s
JOIN 选课表 sc ON s.学号 = sc.学号
WHERE s.学号 = '2024001';
═══════════════════════════════════════════════════════════════════
步骤3:查询优化
═══════════════════════════════════════════════════════════════════
分析可用索引、统计信息
生成最优执行计划:
1. 使用学生表的主键索引定位学号='2024001'
2. 使用选课表的外键索引找到对应记录
3. 执行连接操作
═══════════════════════════════════════════════════════════════════
步骤4:模式/内模式映像转换
═══════════════════════════════════════════════════════════════════
将逻辑操作转换为物理操作:
- 学生表 → student.ibd文件
- 主键索引 → B+树的根页面(page 3)
- 学号='2024001' → 在B+树中二分查找
═══════════════════════════════════════════════════════════════════
步骤5:物理IO执行
═══════════════════════════════════════════════════════════════════
1. 检查缓冲池是否有目标页
2. 如无,从磁盘读取数据页到内存
3. 解析页中的记录格式
4. 提取所需字段值
═══════════════════════════════════════════════════════════════════
步骤6:结果返回
═══════════════════════════════════════════════════════════════════
按外模式的列名返回结果:
┌────────┬────────┐
│ 姓名 │ 成绩 │
├────────┼────────┤
│ 张三 │ 85 │
│ 张三 │ 92 │
└────────┴────────┘
整个过程可以用一张流程图概括:
┌────────────────────────────────────────────────────────────────┐
│ 用户SQL查询 │
└────────────────────────────┬───────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ SQL解析器 │
│ (语法分析) │
└────────────────────────────┬───────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ 视图展开器 │
│ 应用【外模式/模式映像】 │
│ 视图 → 基表查询 │
└────────────────────────────┬───────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ 查询优化器 │
│ 生成执行计划 │
└────────────────────────────┬───────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ 执行引擎 │
│ 应用【模式/内模式映像】 │
│ 逻辑操作 → 物理操作 │
└────────────────────────────┬───────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ 存储引擎 │
│ 物理IO操作 │
└────────────────────────────┬───────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ 查询结果 │
└────────────────────────────────────────────────────────────────┘
第四章 数据独立性
三级模式和两级映像的最终目的是什么?答案就是:实现数据独立性。数据独立性是现代数据库系统最重要的特征之一,也是数据库区别于文件系统的关键所在。
4.1 数据独立性概述
数据独立性的定义
数据独立性(Data Independence) 是指应用程序与数据之间相互独立、互不影响的特性。
用大白话说:数据怎么存、存在哪里发生变化时,使用数据的程序不需要跟着改。
没有数据独立性的情况(文件系统时代):
应用程序代码:
┌────────────────────────────────────────────────┐
│ FILE *f = fopen("/data/users.dat", "rb"); │ ← 硬编码文件路径
│ fseek(f, 100, SEEK_SET); │ ← 硬编码偏移量
│ fread(&name, 20, 1, f); │ ← 硬编码字段长度
│ fread(&age, 4, 1, f); │
└────────────────────────────────────────────────┘
问题:
❌ 文件路径变了 → 程序要改
❌ 字段顺序变了 → 程序要改
❌ 字段长度变了 → 程序要改
❌ 存储格式变了 → 程序要改
有数据独立性的情况(数据库时代):
应用程序代码:
┌────────────────────────────────────────────────┐
│ SELECT name, age FROM users WHERE id = 100; │ ← 只关心逻辑结构
└────────────────────────────────────────────────┘
优点:
✅ 文件路径变了 → 程序不用改
✅ 字段顺序变了 → 程序不用改
✅ 存储格式变了 → 程序不用改
✅ 添加了索引 → 程序不用改
数据独立性的意义
数据独立性的意义可以用一个公式来概括:
维护成本 = 变化次数 × 每次变化影响的范围
没有数据独立性:
变化影响范围 = 所有相关程序
维护成本 = 极高
有数据独立性:
变化影响范围 = 仅数据库内部
维护成本 = 极低
具体来说,数据独立性带来以下好处:
| 好处 | 说明 |
|---|---|
| 降低维护成本 | 数据库变化时,应用程序不需要修改 |
| 提高开发效率 | 开发者只需关注业务逻辑,不用管存储细节 |
| 增强系统稳定性 | 减少因变更引入的bug |
| 便于性能优化 | DBA可以自由优化存储结构而不影响应用 |
| 支持多应用共享 | 不同应用可以独立演进 |
数据独立性的分类
数据独立性分为两种类型,分别对应两级映像:
┌─────────────────────────────────────────────────────────────────┐
│ 数据独立性 │
├────────────────────────────┬────────────────────────────────────┤
│ 逻辑数据独立性 │ 物理数据独立性 │
│ (Logical Independence) │ (Physical Independence) │
├────────────────────────────┼────────────────────────────────────┤
│ 模式变化时,外模式不变 │ 内模式变化时,模式不变 │
│ 依赖:外模式/模式映像 │ 依赖:模式/内模式映像 │
│ 涉及:表结构、字段变更 │ 涉及:存储结构、索引变更 │
└────────────────────────────┴────────────────────────────────────┘
4.2 逻辑数据独立性
定义与概念
逻辑数据独立性(Logical Data Independence) 是指当数据库的模式(逻辑结构)发生变化时,外模式和应用程序可以保持不变。
用大白话说:表结构怎么改,只要视图定义适配一下,应用程序就不用动。
逻辑独立性示意图:
应用程序 ←→ 外模式(视图) ←→ 模式(表结构)
↑ ↑ ↑
不变 适配 变化
关键:通过修改"外模式/模式映像"来吸收模式的变化
实现原理(依赖外模式/模式映像)
逻辑数据独立性的实现依赖于外模式/模式映像:
sql
-- 【场景】学生表需要重构,把姓名拆成姓和名
-- 原始模式
CREATE TABLE student (
id INT PRIMARY KEY,
name VARCHAR(20), -- 原来是完整姓名
class VARCHAR(20)
);
-- 原始外模式
CREATE VIEW StudentView AS
SELECT id AS 学号, name AS 姓名, class AS 班级
FROM student;
-- 应用程序使用外模式
-- SELECT 学号, 姓名 FROM StudentView WHERE 学号 = 1;
现在需要把name拆分为first_name和last_name:
sql
-- 步骤1:修改模式(表结构变化)
ALTER TABLE student ADD first_name VARCHAR(10);
ALTER TABLE student ADD last_name VARCHAR(10);
UPDATE student SET first_name = LEFT(name, 1), last_name = SUBSTRING(name, 2);
ALTER TABLE student DROP COLUMN name;
-- 新的模式结构:
-- student(id, first_name, last_name, class)
-- 步骤2:修改映像(保持外模式不变)
CREATE OR REPLACE VIEW StudentView AS
SELECT
id AS 学号,
CONCAT(first_name, last_name) AS 姓名, -- 映像适配
class AS 班级
FROM student;
-- 应用程序代码完全不用改!
-- 仍然执行:SELECT 学号, 姓名 FROM StudentView WHERE 学号 = 1;
-- 返回结果与之前完全一致
逻辑独立性的体现
逻辑独立性主要体现在以下场景:
| 模式变化类型 | 映像调整方式 | 外模式是否变化 |
|---|---|---|
| 增加新字段 | 视图不选择新字段 | 不变 |
| 删除字段 | 如果视图未使用则不影响 | 不变 |
| 拆分字段 | 视图中用函数合并 | 不变 |
| 合并字段 | 视图中用函数拆分 | 不变 |
| 重命名字段 | 视图中使用别名 | 不变 |
| 拆分表 | 视图中用JOIN连接 | 不变 |
| 合并表 | 视图中选择部分字段 | 不变 |
应用场景举例
场景1:表的垂直拆分
sql
-- 原始设计:用户信息都在一张表
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(100),
email VARCHAR(100),
phone VARCHAR(20),
address TEXT,
bio TEXT -- 个人简介,很长
);
-- 外模式
CREATE VIEW UserBasicView AS
SELECT id, username, email, phone FROM users;
-- 重构:把大字段拆到单独的表(提升查询性能)
CREATE TABLE user_profile (
user_id INT PRIMARY KEY,
address TEXT,
bio TEXT,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 迁移数据
INSERT INTO user_profile SELECT id, address, bio FROM users;
ALTER TABLE users DROP COLUMN address, DROP COLUMN bio;
-- 修改映像,保持外模式不变
-- UserBasicView不涉及拆分的字段,完全不用改!
-- 如果有用到bio的视图:
CREATE OR REPLACE VIEW UserFullView AS
SELECT u.*, p.address, p.bio
FROM users u
LEFT JOIN user_profile p ON u.id = p.user_id;
-- 应用程序继续正常工作
场景2:字段类型变更
sql
-- 原始设计:状态用数字表示
CREATE TABLE orders (
id INT PRIMARY KEY,
status INT -- 0:待支付, 1:已支付, 2:已发货, 3:已完成
);
CREATE VIEW OrderView AS
SELECT id AS 订单号,
CASE status
WHEN 0 THEN '待支付'
WHEN 1 THEN '已支付'
WHEN 2 THEN '已发货'
WHEN 3 THEN '已完成'
END AS 状态
FROM orders;
-- 重构:状态改用字符串(更易读)
ALTER TABLE orders MODIFY status VARCHAR(20);
UPDATE orders SET status = CASE status
WHEN '0' THEN 'PENDING'
WHEN '1' THEN 'PAID'
WHEN '2' THEN 'SHIPPED'
WHEN '3' THEN 'COMPLETED'
END;
-- 修改映像
CREATE OR REPLACE VIEW OrderView AS
SELECT id AS 订单号,
CASE status
WHEN 'PENDING' THEN '待支付'
WHEN 'PAID' THEN '已支付'
WHEN 'SHIPPED' THEN '已发货'
WHEN 'COMPLETED' THEN '已完成'
END AS 状态
FROM orders;
-- 应用程序看到的结果完全一样!
优势与局限性
优势:
- ✅ 表结构重构不影响应用程序
- ✅ 支持渐进式重构
- ✅ 多个应用可以看到不同的视图
- ✅ 便于数据库规范化调整
局限性:
- ❌ 如果视图逻辑过于复杂,可能影响性能
- ❌ 某些极端重构仍需要修改外模式
- ❌ 实时性要求高的场景可能不适合复杂视图
- ❌ 某些数据库的视图功能有限制(如不支持更新)
4.3 物理数据独立性
定义与概念
物理数据独立性(Physical Data Independence) 是指当数据库的内模式(物理存储结构)发生变化时,模式和外模式可以保持不变。
用大白话说:数据怎么存、存在哪里、用什么索引,改了之后应用程序和表结构都不用动。
物理独立性示意图:
模式(表结构) ←→ 内模式(存储结构)
↑ ↑
不变 变化
关键:通过修改"模式/内模式映像"来吸收内模式的变化
实现原理(依赖模式/内模式映像)
物理数据独立性的实现依赖于模式/内模式映像:
sql
-- 【场景】性能优化:为频繁查询的字段添加索引
-- 原始状态
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
total_amount DECIMAL(10,2),
create_time DATETIME
);
-- 查询经常按user_id和create_time筛选,但很慢...
-- EXPLAIN SELECT * FROM orders
-- WHERE user_id = 100 AND create_time > '2024-01-01';
-- 结果:全表扫描,type=ALL
-- 修改内模式:添加索引
CREATE INDEX idx_user_time ON orders(user_id, create_time);
-- 现在查询变快了!
-- EXPLAIN SELECT * FROM orders
-- WHERE user_id = 100 AND create_time > '2024-01-01';
-- 结果:索引范围扫描,type=range
-- 但是:
-- 1. 模式(表结构)没有任何变化
-- 2. 外模式(视图)没有任何变化
-- 3. 应用程序的SQL没有任何变化
-- 4. 只有执行效率提升了!
物理独立性的体现
物理独立性主要体现在以下场景:
| 内模式变化类型 | 对模式的影响 | 对应用程序的影响 |
|---|---|---|
| 添加/删除索引 | 无 | 无(性能变化) |
| 更换存储引擎 | 无 | 无(事务特性可能变化) |
| 修改分区策略 | 无 | 无(性能变化) |
| 更改存储位置 | 无 | 无 |
| 启用压缩/加密 | 无 | 无 |
| 调整页大小 | 无 | 无 |
| 更换存储介质 | 无 | 无(性能变化) |
应用场景举例
场景1:存储引擎迁移
sql
-- 原始设计:使用MyISAM(不支持事务)
CREATE TABLE logs (
id INT PRIMARY KEY,
message TEXT,
create_time DATETIME
) ENGINE=MyISAM;
-- 业务需求变化:需要支持事务
-- 迁移到InnoDB
-- 修改内模式
ALTER TABLE logs ENGINE=InnoDB;
-- 物理存储完全改变了:
-- MyISAM: logs.MYD (数据) + logs.MYI (索引)
-- InnoDB: logs.ibd (数据+索引)
-- 但是:
-- 1. 表结构(模式)完全不变
-- 2. 所有SQL语句继续正常工作
-- 3. 应用程序代码不需要任何修改
场景2:分区优化
sql
-- 原始设计:单表存储所有订单
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
create_time DATETIME,
total_amount DECIMAL(10,2)
);
-- 数据量增大,查询变慢
-- 修改内模式:按时间分区
ALTER TABLE orders
PARTITION BY RANGE (YEAR(create_time)) (
PARTITION p2022 VALUES LESS THAN (2023),
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
-- 物理存储变化:
-- 之前:orders.ibd(单个文件)
-- 之后:orders#p#p2022.ibd, orders#p#p2023.ibd, ...
-- 查询2024年数据时,自动只扫描p2024分区
-- SELECT * FROM orders WHERE create_time >= '2024-01-01';
-- 应用程序完全不知道数据被分区了,SQL不用改
场景3:热数据迁移到SSD
sql
-- 业务场景:近3个月的订单查询频繁,历史订单查询少
-- 方案:把热数据放SSD,冷数据放HDD
-- 创建不同的表空间
CREATE TABLESPACE ts_hot
ADD DATAFILE '/ssd/orders_hot.ibd';
CREATE TABLESPACE ts_cold
ADD DATAFILE '/hdd/orders_cold.ibd';
-- 对于分区表,可以把不同分区放到不同表空间
ALTER TABLE orders REORGANIZE PARTITION p2024 INTO (
PARTITION p2024 VALUES LESS THAN (2025) TABLESPACE ts_hot
);
ALTER TABLE orders REORGANIZE PARTITION p2022 INTO (
PARTITION p2022 VALUES LESS THAN (2023) TABLESPACE ts_cold
);
-- 应用程序:SELECT * FROM orders WHERE ...
-- 完全不知道数据在SSD还是HDD,SQL不用改!
优势与局限性
优势:
- ✅ DBA可以自由优化存储结构
- ✅ 性能调优不影响应用开发
- ✅ 硬件升级对应用透明
- ✅ 支持在线优化,减少停机时间
局限性:
- ❌ 某些存储引擎特性变化可能影响应用行为
- ❌ 分区键选择不当可能导致查询不使用分区裁剪
- ❌ 某些DDL操作可能需要锁表
- ❌ 不同存储引擎的事务特性不同
4.4 数据独立性的实现保障
DBMS提供的支持
DBMS通过以下机制保障数据独立性:
┌────────────────────────────────────────────────────────────────┐
│ DBMS的数据独立性支持 │
├────────────────────────────────────────────────────────────────┤
│ │
│ 1. 视图机制(View) │
│ - 定义外模式 │
│ - 实现外模式/模式映像 │
│ - 支持视图更新(部分场景) │
│ │
│ 2. 数据字典(Data Dictionary) │
│ - 存储所有模式定义 │
│ - 存储映像关系 │
│ - 提供元数据查询 │
│ │
│ 3. 查询优化器(Query Optimizer) │
│ - 自动选择执行计划 │
│ - 利用索引加速查询 │
│ - 对应用透明 │
│ │
│ 4. 存储引擎抽象(Storage Engine) │
│ - 统一的SQL接口 │
│ - 可插拔的存储实现 │
│ - 物理存储对上层透明 │
│ │
└────────────────────────────────────────────────────────────────┘
映像机制的作用
映像机制是实现数据独立性的核心:
映像机制的核心作用 = 解耦 + 适配
┌─────────────┐
│ 应用程序 │ ─── 只依赖外模式,不依赖模式
└──────┬──────┘
│
│ 外模式/模式映像(解耦点1)
│ 作用:吸收模式变化,保持外模式稳定
▼
┌─────────────┐
│ 模式 │ ─── 只描述逻辑结构,不描述物理存储
└──────┬──────┘
│
│ 模式/内模式映像(解耦点2)
│ 作用:吸收内模式变化,保持模式稳定
▼
┌─────────────┐
│ 内模式 │ ─── 可以自由调整物理存储
└─────────────┘
映像调整的典型流程:
变化发生 → 判断影响层次 → 调整对应映像 → 上层保持不变
示例1:添加索引(内模式变化)
→ 影响:内模式
→ 调整:模式/内模式映像(自动)
→ 结果:模式不变,应用不变
示例2:拆分表字段(模式变化)
→ 影响:模式
→ 调整:外模式/模式映像(修改视图)
→ 结果:外模式不变,应用不变
示例3:增加新的外模式(新需求)
→ 影响:外模式
→ 调整:创建新视图
→ 结果:其他外模式和模式都不变
数据字典的作用
数据字典(Data Dictionary) ,也称为系统目录(System Catalog),是存储数据库元数据的特殊数据库。
数据字典存储的内容:
┌────────────────────────────────────────────────────────────┐
│ 数据字典 │
├────────────────────────────────────────────────────────────┤
│ 模式信息 │
│ ├── 数据库列表 │
│ ├── 表定义(表名、字段、类型、约束) │
│ ├── 索引定义 │
│ └── 关系定义(主键、外键) │
├────────────────────────────────────────────────────────────┤
│ 外模式信息 │
│ ├── 视图定义(视图名、查询语句) │
│ └── 同义词定义 │
├────────────────────────────────────────────────────────────┤
│ 内模式信息 │
│ ├── 存储结构信息 │
│ ├── 文件位置和大小 │
│ └── 统计信息(用于查询优化) │
├────────────────────────────────────────────────────────────┤
│ 权限信息 │
│ ├── 用户和角色 │
│ └── 访问权限 │
└────────────────────────────────────────────────────────────┘
在MySQL中查询数据字典:
sql
-- 查看所有表
SELECT TABLE_NAME, ENGINE, TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'school';
-- 查看表的列信息
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_KEY
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'school' AND TABLE_NAME = 'student';
-- 查看视图定义
SELECT TABLE_NAME, VIEW_DEFINITION
FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = 'school';
-- 查看索引信息
SELECT INDEX_NAME, COLUMN_NAME, SEQ_IN_INDEX
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'school' AND TABLE_NAME = 'student';
4.5 数据独立性的实际应用
数据库结构调整案例
案例:电商系统的用户表重构
背景:
- 初始设计时,用户信息都放在一张表
- 随着业务发展,表字段越来越多,查询变慢
- 需要进行垂直拆分
原始结构:
┌────────────────────────────────────┐
│ users │
│ id, name, email, phone, password │
│ address, city, country, zipcode │
│ bio, avatar, preferences │
│ created_at, updated_at │
└────────────────────────────────────┘
目标结构:
┌────────────────────┐ ┌────────────────────┐
│ users_basic │ │ users_profile │
│ id, name, email │ │ user_id, bio │
│ phone, password │ │ avatar, preferences│
│ created_at │ │ updated_at │
└────────────────────┘ └────────────────────┘
┌────────────────────┐
│ users_address │
│ user_id, address │
│ city, country, zip │
└────────────────────┘
重构步骤(利用数据独立性):
sql
-- 步骤1:创建新表(不影响现有外模式)
CREATE TABLE users_basic AS
SELECT id, name, email, phone, password, created_at FROM users;
CREATE TABLE users_profile AS
SELECT id AS user_id, bio, avatar, preferences, updated_at FROM users;
CREATE TABLE users_address AS
SELECT id AS user_id, address, city, country, zipcode FROM users;
-- 步骤2:创建兼容视图(保持外模式不变)
CREATE VIEW users AS
SELECT
b.id, b.name, b.email, b.phone, b.password,
a.address, a.city, a.country, a.zipcode,
p.bio, p.avatar, p.preferences,
b.created_at, p.updated_at
FROM users_basic b
LEFT JOIN users_profile p ON b.id = p.user_id
LEFT JOIN users_address a ON b.id = a.user_id;
-- 步骤3:删除原表(可选,确认无问题后执行)
-- DROP TABLE users_old;
-- 结果:
-- ✅ 应用程序的SQL完全不需要修改
-- ✅ 查询 SELECT * FROM users WHERE id = 1 继续正常工作
-- ✅ 内部已经是三张表,但对外表现为一张表
存储优化案例
案例:日志表的性能优化
背景:
- 系统日志表数据量巨大(上亿条)
- 查询响应时间从毫秒级降到秒级
- 需要在不影响应用的情况下优化
问题分析:
- 没有合适的索引
- 数据没有分区
- 热数据和冷数据混在一起
优化步骤(利用物理数据独立性):
sql
-- 步骤1:添加索引(内模式变化,对应用透明)
CREATE INDEX idx_create_time ON system_logs(create_time);
CREATE INDEX idx_level_time ON system_logs(log_level, create_time);
-- 应用的SQL不需要修改,查询自动变快
-- 步骤2:实施分区(内模式变化,对应用透明)
ALTER TABLE system_logs
PARTITION BY RANGE (YEAR(create_time) * 100 + MONTH(create_time)) (
PARTITION p202401 VALUES LESS THAN (202402),
PARTITION p202402 VALUES LESS THAN (202403),
-- ...
PARTITION p_future VALUES LESS THAN MAXVALUE
);
-- 应用的SQL不需要修改,按时间查询自动只扫描相关分区
-- 步骤3:归档历史数据(内模式变化,对应用透明)
-- 把超过1年的数据移到归档表
CREATE TABLE system_logs_archive LIKE system_logs;
INSERT INTO system_logs_archive
SELECT * FROM system_logs
WHERE create_time < DATE_SUB(NOW(), INTERVAL 1 YEAR);
DELETE FROM system_logs
WHERE create_time < DATE_SUB(NOW(), INTERVAL 1 YEAR);
-- 应用的SQL不需要修改
-- 步骤4:创建合并视图(如果需要查历史数据)
CREATE VIEW system_logs_all AS
SELECT * FROM system_logs
UNION ALL
SELECT * FROM system_logs_archive;
-- 需要查历史数据时使用这个视图
-- 结果:
-- ✅ 所有优化对应用程序完全透明
-- ✅ 查询性能从秒级恢复到毫秒级
-- ✅ 存储空间得到优化
应用系统升级案例
案例:系统从MySQL迁移到TiDB
背景:
- 业务发展,单机MySQL无法支撑
- 需要迁移到分布式数据库TiDB
- 要求:迁移过程中应用不停机、不修改代码
迁移策略(利用数据独立性):
┌────────────────────────────────────────────────────────────────┐
│ 迁移架构 │
├────────────────────────────────────────────────────────────────┤
│ │
│ 阶段1:双写 │
│ ┌──────────┐ │
│ │ 应用程序 │ │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ 写入 ┌──────────┐ │
│ │ MySQL │ ───────────→ │ TiDB │ │
│ │ (主库) │ 同步数据 │ (备库) │ │
│ └──────────┘ └──────────┘ │
│ ↑ │
│ │ 读取 │
│ │ │
│ │
│ 阶段2:切换读 │
│ ┌──────────┐ │
│ │ 应用程序 │ ──── 读 ────→ TiDB │
│ └────┬─────┘ │
│ │ 写 │
│ ▼ │
│ MySQL ───同步──→ TiDB │
│ │
│ 阶段3:完全切换 │
│ ┌──────────┐ │
│ │ 应用程序 │ ──── 读写 ───→ TiDB │
│ └──────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
为什么可以平滑迁移?
- TiDB兼容MySQL协议(外模式兼容)
- 应用使用的SQL语句不需要修改
- 物理存储完全不同,但对应用透明(物理独立性)
- 表结构定义相同(模式相同)
这个案例完美体现了数据独立性的价值:底层存储从单机换成了分布式集群,但应用程序一行代码都不用改!
第五章 三级模式在主流数据库中的实现
理论需要落地实践。本章我们来看看三级模式在MySQL、Oracle、PostgreSQL和SQL Server这四大主流数据库中是如何具体实现的。
5.1 MySQL中的实现
MySQL的体系结构
MySQL采用可插拔存储引擎架构,这正是模式/内模式分离的典范:
┌─────────────────────────────────────────────────────────────────┐
│ MySQL体系结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 连接层 │ │
│ │ 连接管理、认证授权、安全管理 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 服务层 │ │
│ │ SQL解析器 → 查询优化器 → 查询执行器 │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 视图处理(外模式) 元数据管理(模式) │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 存储引擎层(内模式) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ InnoDB │ │ MyISAM │ │ Memory │ │ 其他... │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 文件系统 │ │
│ │ 数据文件(.ibd) 日志文件(redo/undo) 配置文件 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
外模式的实现(视图View)
MySQL通过**视图(VIEW)**实现外模式:
sql
-- 创建基础表(模式)
CREATE TABLE employees (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
email VARCHAR(100),
department_id INT,
salary DECIMAL(10,2),
hire_date DATE,
ssn VARCHAR(20) -- 敏感信息
);
-- 外模式1:普通员工视图(隐藏敏感信息)
CREATE VIEW v_employee_public AS
SELECT id, name, email, department_id, hire_date
FROM employees;
-- 外模式2:HR视图(包含薪资)
CREATE VIEW v_employee_hr AS
SELECT id, name, email, department_id, salary, hire_date
FROM employees;
-- 外模式3:部门统计视图(聚合数据)
CREATE VIEW v_department_stats AS
SELECT
department_id,
COUNT(*) AS employee_count,
AVG(salary) AS avg_salary,
MIN(hire_date) AS oldest_hire
FROM employees
GROUP BY department_id;
-- 查看视图定义
SHOW CREATE VIEW v_employee_public;
-- 视图存储在information_schema中
SELECT * FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = 'mydb';
模式的实现(表结构、约束)
MySQL通过**表定义(DDL)**实现模式:
sql
-- 创建数据库
CREATE DATABASE company CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建表(模式定义)
CREATE TABLE departments (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL UNIQUE,
manager_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE employees (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
department_id INT,
salary DECIMAL(10,2) CHECK (salary > 0),
hire_date DATE NOT NULL,
-- 外键约束
CONSTRAINT fk_dept FOREIGN KEY (department_id)
REFERENCES departments(id) ON DELETE SET NULL
);
-- 查看模式信息
DESCRIBE employees;
-- 从数据字典查看详细信息
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_KEY, EXTRA
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'company' AND TABLE_NAME = 'employees';
内模式的实现(存储引擎、索引)
MySQL通过存储引擎 和索引实现内模式:
sql
-- 选择存储引擎(影响物理存储方式)
CREATE TABLE logs (
id INT PRIMARY KEY,
message TEXT
) ENGINE=InnoDB; -- 支持事务、行锁、外键
CREATE TABLE sessions (
id VARCHAR(64) PRIMARY KEY,
data TEXT
) ENGINE=MEMORY; -- 内存存储,速度快
-- 创建索引(影响数据访问路径)
CREATE INDEX idx_emp_name ON employees(name);
CREATE INDEX idx_emp_dept_hire ON employees(department_id, hire_date);
CREATE FULLTEXT INDEX idx_log_msg ON logs(message);
-- 分区(影响数据物理分布)
CREATE TABLE orders (
id INT,
user_id INT,
amount DECIMAL(10,2),
order_date DATE,
PRIMARY KEY (id, order_date)
) PARTITION BY RANGE (YEAR(order_date)) (
PARTITION p2022 VALUES LESS THAN (2023),
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025)
);
-- 查看内模式信息
SHOW TABLE STATUS FROM company;
SHOW INDEX FROM employees;
-- 查看InnoDB表空间信息
SELECT * FROM information_schema.INNODB_TABLESPACES;
MySQL三级模式对照表
| 三级模式 | MySQL实现 | 存储位置 |
|---|---|---|
| 外模式 | VIEW | information_schema.VIEWS |
| 模式 | TABLE定义 | information_schema.TABLES/COLUMNS |
| 内模式 | 存储引擎、索引、分区 | *.ibd文件、索引结构 |
5.2 Oracle中的实现
Oracle的体系结构
Oracle采用Schema概念,与三级模式有着天然的对应:
┌─────────────────────────────────────────────────────────────────┐
│ Oracle体系结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 实例(Instance) │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ SGA(System Global Area) │ │ │
│ │ │ 共享池 | 数据库缓冲区 | 重做日志缓冲区 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 后台进程: PMON | SMON | DBWR | LGWR | CKPT │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 数据库(Database) │ │
│ │ 数据文件(.dbf) | 控制文件(.ctl) | 重做日志(.log) │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 逻辑结构: │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Database → Tablespace → Segment → Extent → Block ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
外模式的实现
Oracle通过视图和**同义词(Synonym)**实现外模式:
sql
-- 创建视图
CREATE VIEW emp_public_view AS
SELECT employee_id, first_name, last_name, email, department_id
FROM employees
WHERE department_id = SYS_CONTEXT('USERENV', 'CLIENT_INFO');
-- 创建同义词(别名)
CREATE SYNONYM emp FOR hr.employees;
CREATE PUBLIC SYNONYM departments FOR hr.departments;
-- 物化视图(预计算的视图,提升查询性能)
CREATE MATERIALIZED VIEW mv_sales_summary
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
AS
SELECT product_id, SUM(quantity) total_qty, SUM(amount) total_amount
FROM sales
GROUP BY product_id;
-- 虚拟私有数据库(VPD)实现行级安全
BEGIN
DBMS_RLS.ADD_POLICY(
object_schema => 'HR',
object_name => 'EMPLOYEES',
policy_name => 'EMP_POLICY',
function_schema => 'SEC',
policy_function => 'EMPLOYEE_SECURITY',
statement_types => 'SELECT,UPDATE,DELETE'
);
END;
/
模式的实现
Oracle中Schema与用户绑定,每个用户拥有自己的模式:
sql
-- 创建用户(同时创建Schema)
CREATE USER hr IDENTIFIED BY password
DEFAULT TABLESPACE users
TEMPORARY TABLESPACE temp
QUOTA UNLIMITED ON users;
-- 在用户的Schema中创建表
CREATE TABLE hr.employees (
employee_id NUMBER PRIMARY KEY,
first_name VARCHAR2(50),
last_name VARCHAR2(50) NOT NULL,
email VARCHAR2(100) UNIQUE,
salary NUMBER(10,2) CONSTRAINT sal_positive CHECK (salary > 0),
department_id NUMBER REFERENCES hr.departments(department_id)
);
-- 创建序列
CREATE SEQUENCE hr.emp_seq START WITH 1 INCREMENT BY 1;
-- 创建触发器
CREATE TRIGGER hr.trg_emp_id
BEFORE INSERT ON hr.employees
FOR EACH ROW
BEGIN
:NEW.employee_id := hr.emp_seq.NEXTVAL;
END;
/
-- 查看Schema中的对象
SELECT object_name, object_type
FROM dba_objects
WHERE owner = 'HR';
内模式的实现
Oracle通过表空间、段、区、块的层次结构实现内模式:
sql
-- 创建表空间
CREATE TABLESPACE ts_data
DATAFILE '/u01/oracle/data/ts_data01.dbf' SIZE 100M
AUTOEXTEND ON NEXT 50M MAXSIZE 1G
EXTENT MANAGEMENT LOCAL AUTOALLOCATE
SEGMENT SPACE MANAGEMENT AUTO;
-- 指定表的存储参数
CREATE TABLE hr.employees (
...
) TABLESPACE ts_data
STORAGE (INITIAL 64K NEXT 64K PCTINCREASE 0)
PCTFREE 20 PCTUSED 40;
-- 创建索引
CREATE INDEX hr.idx_emp_name ON hr.employees(last_name, first_name)
TABLESPACE ts_index;
-- 分区表
CREATE TABLE hr.sales (
sale_id NUMBER,
sale_date DATE,
amount NUMBER
) PARTITION BY RANGE (sale_date) (
PARTITION p_2023 VALUES LESS THAN (TO_DATE('2024-01-01','YYYY-MM-DD')),
PARTITION p_2024 VALUES LESS THAN (TO_DATE('2025-01-01','YYYY-MM-DD'))
);
-- 查看存储信息
SELECT tablespace_name, file_name, bytes/1024/1024 MB
FROM dba_data_files;
SELECT segment_name, segment_type, tablespace_name, bytes/1024 KB
FROM dba_segments WHERE owner = 'HR';
5.3 PostgreSQL中的实现
PostgreSQL的体系结构
PostgreSQL使用Database Cluster概念组织数据:
┌─────────────────────────────────────────────────────────────────┐
│ PostgreSQL体系结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Postmaster(主进程) │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ Backend进程 | Writer | WAL Writer | Checkpointer │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 共享内存(Shared Memory) │ │
│ │ Shared Buffers | WAL Buffers | Lock Table │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 数据库集簇(Database Cluster) │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │postgres │ │template0│ │ mydb │ ... │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ ↓ ↓ ↓ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ Schemas: public | pg_catalog | custom_schema │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
外模式的实现
PostgreSQL通过视图 和Schema隔离实现外模式:
sql
-- 创建Schema(命名空间)
CREATE SCHEMA sales;
CREATE SCHEMA hr;
-- 在不同Schema中创建视图
CREATE VIEW sales.customer_orders AS
SELECT c.name, o.order_date, o.total
FROM public.customers c
JOIN public.orders o ON c.id = o.customer_id;
-- 创建可更新视图
CREATE VIEW hr.active_employees AS
SELECT * FROM hr.employees WHERE status = 'active'
WITH CHECK OPTION; -- 确保INSERT/UPDATE符合视图条件
-- 外部表(访问外部数据源)
CREATE EXTENSION file_fdw;
CREATE SERVER csv_server FOREIGN DATA WRAPPER file_fdw;
CREATE FOREIGN TABLE external_logs (
log_date DATE,
message TEXT
) SERVER csv_server
OPTIONS (filename '/var/log/app.csv', format 'csv');
-- 行安全策略(Row Level Security)
ALTER TABLE employees ENABLE ROW LEVEL SECURITY;
CREATE POLICY emp_isolation_policy ON employees
USING (department_id = current_setting('app.department_id')::INT);
模式的实现
PostgreSQL中Schema是逻辑命名空间,可以包含表、视图、函数等:
sql
-- 创建表(支持丰富的数据类型)
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE,
info JSONB, -- JSON二进制格式
tags TEXT[], -- 数组类型
created_at TIMESTAMPTZ DEFAULT NOW(),
search_vector TSVECTOR -- 全文搜索
);
-- 继承(PostgreSQL特色)
CREATE TABLE managers (
bonus DECIMAL(10,2)
) INHERITS (employees);
-- 复合类型
CREATE TYPE address AS (
street VARCHAR(100),
city VARCHAR(50),
country VARCHAR(50)
);
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
shipping_address address,
billing_address address
);
-- 查看系统目录
SELECT * FROM pg_tables WHERE schemaname = 'public';
SELECT * FROM information_schema.columns WHERE table_name = 'employees';
内模式的实现
PostgreSQL通过表空间 和索引实现内模式:
sql
-- 创建表空间
CREATE TABLESPACE fast_storage LOCATION '/ssd/pg_data';
CREATE TABLESPACE archive_storage LOCATION '/hdd/pg_archive';
-- 表指定表空间
CREATE TABLE hot_data (
id INT PRIMARY KEY,
data TEXT
) TABLESPACE fast_storage;
-- 多种索引类型
CREATE INDEX idx_btree ON employees(name); -- B-tree(默认)
CREATE INDEX idx_hash ON employees USING hash(email); -- Hash索引
CREATE INDEX idx_gin ON employees USING gin(tags); -- GIN索引(数组/全文)
CREATE INDEX idx_gist ON locations USING gist(coordinates); -- GiST索引(几何)
-- 分区表(声明式分区)
CREATE TABLE sales (
id SERIAL,
sale_date DATE NOT NULL,
amount DECIMAL(10,2)
) PARTITION BY RANGE (sale_date);
CREATE TABLE sales_2024 PARTITION OF sales
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01')
TABLESPACE fast_storage;
-- TOAST(大对象存储)
-- PostgreSQL自动将大字段存储在TOAST表中
SELECT relname, reltoastrelid::regclass
FROM pg_class WHERE relname = 'employees';
5.4 SQL Server中的实现
SQL Server的体系结构
SQL Server采用数据库-文件组-文件的层次结构:
┌─────────────────────────────────────────────────────────────────┐
│ SQL Server体系结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 协议层 │ │
│ │ TDS协议 | Named Pipes | TCP/IP | Shared Memory │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 关系引擎 │ │
│ │ 命令解析器 → 查询优化器 → 查询执行器 │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 缓冲管理器 | 事务管理器 | 锁管理器 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 存储引擎 │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 访问方法 | 行版本管理 | 页/区管理 | 文件管理 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 数据库文件 │ │
│ │ 主数据文件(.mdf) | 次要数据文件(.ndf) | 日志文件(.ldf) │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
外模式的实现
SQL Server通过视图 和Schema实现外模式:
sql
-- 创建Schema
CREATE SCHEMA Sales;
CREATE SCHEMA HR;
-- 创建视图
CREATE VIEW Sales.vw_OrderSummary
AS
SELECT
o.OrderID,
c.CustomerName,
SUM(od.Quantity * od.UnitPrice) AS TotalAmount
FROM Sales.Orders o
JOIN Sales.Customers c ON o.CustomerID = c.CustomerID
JOIN Sales.OrderDetails od ON o.OrderID = od.OrderID
GROUP BY o.OrderID, c.CustomerName;
GO
-- 索引视图(物化视图)
CREATE VIEW Sales.vw_ProductSales
WITH SCHEMABINDING -- 必须绑定Schema
AS
SELECT
ProductID,
SUM(Quantity) AS TotalQuantity,
COUNT_BIG(*) AS SalesCount
FROM dbo.OrderDetails
GROUP BY ProductID;
GO
CREATE UNIQUE CLUSTERED INDEX IX_ProductSales
ON Sales.vw_ProductSales(ProductID);
-- 同义词
CREATE SYNONYM dbo.Emp FOR HumanResources.Employees;
模式的实现
SQL Server中Schema是对象的逻辑容器:
sql
-- 创建表
CREATE TABLE HR.Employees (
EmployeeID INT IDENTITY(1,1) PRIMARY KEY,
FirstName NVARCHAR(50) NOT NULL,
LastName NVARCHAR(50) NOT NULL,
Email NVARCHAR(100) UNIQUE,
Salary MONEY CHECK (Salary > 0),
DepartmentID INT FOREIGN KEY REFERENCES HR.Departments(DepartmentID),
HireDate DATE DEFAULT GETDATE()
);
-- 计算列
ALTER TABLE HR.Employees
ADD FullName AS (FirstName + ' ' + LastName);
-- 临时表
CREATE TABLE #TempOrders (
OrderID INT,
Amount MONEY
);
-- 表变量
DECLARE @OrderTable TABLE (
OrderID INT,
Amount MONEY
);
-- 查看元数据
SELECT * FROM sys.tables WHERE schema_id = SCHEMA_ID('HR');
SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('HR.Employees');
内模式的实现
SQL Server通过文件组 和索引实现内模式:
sql
-- 创建数据库指定文件组
CREATE DATABASE CompanyDB
ON PRIMARY (
NAME = 'CompanyDB_Primary',
FILENAME = 'D:\Data\CompanyDB.mdf',
SIZE = 100MB,
MAXSIZE = UNLIMITED,
FILEGROWTH = 50MB
),
FILEGROUP FG_Data (
NAME = 'CompanyDB_Data',
FILENAME = 'E:\Data\CompanyDB_Data.ndf',
SIZE = 500MB
),
FILEGROUP FG_Index (
NAME = 'CompanyDB_Index',
FILENAME = 'F:\Data\CompanyDB_Index.ndf',
SIZE = 200MB
)
LOG ON (
NAME = 'CompanyDB_Log',
FILENAME = 'G:\Log\CompanyDB.ldf',
SIZE = 100MB
);
-- 表指定文件组
CREATE TABLE Sales.Orders (
OrderID INT PRIMARY KEY,
OrderDate DATE,
CustomerID INT
) ON FG_Data;
-- 创建各种索引
CREATE CLUSTERED INDEX IX_Orders_Date
ON Sales.Orders(OrderDate) ON FG_Index;
CREATE NONCLUSTERED INDEX IX_Orders_Customer
ON Sales.Orders(CustomerID)
INCLUDE (OrderDate) ON FG_Index;
-- 分区
CREATE PARTITION FUNCTION pf_OrderDate (DATE)
AS RANGE RIGHT FOR VALUES ('2023-01-01', '2024-01-01', '2025-01-01');
CREATE PARTITION SCHEME ps_OrderDate
AS PARTITION pf_OrderDate
TO (FG_Archive, FG_Data, FG_Data, FG_Data);
CREATE TABLE Sales.PartitionedOrders (
OrderID INT,
OrderDate DATE,
Amount MONEY
) ON ps_OrderDate(OrderDate);
-- 数据压缩
ALTER TABLE Sales.Orders REBUILD WITH (DATA_COMPRESSION = PAGE);
5.5 四大数据库三级模式实现对比
| 特性 | MySQL | Oracle | PostgreSQL | SQL Server |
|---|---|---|---|---|
| 外模式 | ||||
| 视图 | VIEW | VIEW | VIEW | VIEW |
| 物化视图 | 不原生支持 | MATERIALIZED VIEW | MATERIALIZED VIEW | 索引视图 |
| 同义词 | 不支持 | SYNONYM | 不支持 | SYNONYM |
| 行级安全 | 视图+过滤 | VPD | RLS策略 | 安全策略 |
| 模式 | ||||
| Schema概念 | DATABASE | USER=Schema | Schema | Schema |
| 表继承 | 不支持 | 不支持 | INHERITS | 不支持 |
| JSON支持 | JSON类型 | JSON类型 | JSONB(高效) | JSON类型 |
| 内模式 | ||||
| 存储引擎 | 可插拔 | 固定 | 固定 | 固定 |
| 表空间 | 表空间 | Tablespace | Tablespace | 文件组 |
| 索引类型 | B+树/Hash/全文 | B树/位图/函数 | B树/Hash/GIN/GiST | B树/列存储/全文 |
| 分区 | 支持 | 支持 | 声明式分区 | 支持 |
第六章 实践案例分析
学以致用,本章通过三个完整的实践案例,展示如何在实际项目中应用三级模式和两级映像。
6.1 案例一:电商系统数据库设计
业务需求分析
一个典型的电商系统需要支持:
-
用户注册、登录、个人信息管理
-
商品浏览、搜索、详情查看
-
购物车、订单、支付
-
商家管理、库存管理
-
数据分析、报表生成
业务角色及其数据需求:
┌─────────────────────────────────────────────────────────────────┐
│ 电商系统角色 │
├─────────────┬─────────────┬─────────────┬─────────────────────┤
│ 普通用户 │ 商家 │ 运营 │ 管理员 │
├─────────────┼─────────────┼─────────────┼─────────────────────┤
│ 个人信息 │ 店铺信息 │ 销售数据 │ 所有数据 │
│ 订单记录 │ 商品管理 │ 用户分析 │ 用户管理 │
│ 收货地址 │ 订单处理 │ 库存监控 │ 系统配置 │
│ 购物车 │ 财务结算 │ 促销管理 │ 权限管理 │
└─────────────┴─────────────┴─────────────┴─────────────────────┘
三级模式设计
模式设计(核心表结构)
sql
-- =====================================================
-- 模式层:核心表结构设计
-- =====================================================
-- 用户表
CREATE TABLE users (
user_id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
email VARCHAR(100) UNIQUE,
phone VARCHAR(20) UNIQUE,
real_name VARCHAR(50),
id_card VARCHAR(20),
avatar_url VARCHAR(255),
status TINYINT DEFAULT 1 COMMENT '1:正常 0:禁用',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_phone (phone),
INDEX idx_email (email)
) ENGINE=InnoDB;
-- 用户地址表
CREATE TABLE user_addresses (
address_id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
receiver_name VARCHAR(50) NOT NULL,
receiver_phone VARCHAR(20) NOT NULL,
province VARCHAR(50),
city VARCHAR(50),
district VARCHAR(50),
detail_address VARCHAR(255),
is_default TINYINT DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users(user_id)
) ENGINE=InnoDB;
-- 商家表
CREATE TABLE merchants (
merchant_id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL UNIQUE,
shop_name VARCHAR(100) NOT NULL,
business_license VARCHAR(50),
contact_phone VARCHAR(20),
status TINYINT DEFAULT 0 COMMENT '0:审核中 1:正常 2:冻结',
commission_rate DECIMAL(5,4) DEFAULT 0.05,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id)
) ENGINE=InnoDB;
-- 商品分类表
CREATE TABLE categories (
category_id INT PRIMARY KEY AUTO_INCREMENT,
parent_id INT DEFAULT 0,
name VARCHAR(50) NOT NULL,
level TINYINT NOT NULL,
sort_order INT DEFAULT 0
) ENGINE=InnoDB;
-- 商品表
CREATE TABLE products (
product_id BIGINT PRIMARY KEY AUTO_INCREMENT,
merchant_id BIGINT NOT NULL,
category_id INT NOT NULL,
name VARCHAR(200) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL,
original_price DECIMAL(10,2),
stock INT NOT NULL DEFAULT 0,
sales INT DEFAULT 0,
status TINYINT DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (merchant_id) REFERENCES merchants(merchant_id),
FOREIGN KEY (category_id) REFERENCES categories(category_id),
INDEX idx_category (category_id),
INDEX idx_merchant (merchant_id),
FULLTEXT INDEX idx_name (name)
) ENGINE=InnoDB;
-- 订单表
CREATE TABLE orders (
order_id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
merchant_id BIGINT NOT NULL,
total_amount DECIMAL(10,2) NOT NULL,
pay_amount DECIMAL(10,2),
freight_amount DECIMAL(10,2) DEFAULT 0,
status TINYINT DEFAULT 0 COMMENT '0:待支付 1:已支付 2:已发货 3:已完成 4:已取消',
receiver_name VARCHAR(50),
receiver_phone VARCHAR(20),
receiver_address VARCHAR(255),
pay_time DATETIME,
ship_time DATETIME,
complete_time DATETIME,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id),
FOREIGN KEY (merchant_id) REFERENCES merchants(merchant_id),
INDEX idx_user (user_id),
INDEX idx_merchant (merchant_id),
INDEX idx_status (status),
INDEX idx_created (created_at)
) ENGINE=InnoDB;
-- 订单明细表
CREATE TABLE order_items (
item_id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
product_name VARCHAR(200),
product_price DECIMAL(10,2),
quantity INT NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(order_id)
) ENGINE=InnoDB;
外模式设计(不同角色的视图)
sql
-- =====================================================
-- 外模式层:为不同角色创建视图
-- =====================================================
-- 外模式1:用户视图(隐藏敏感信息)
CREATE VIEW v_user_profile AS
SELECT
user_id,
username,
email,
phone,
LEFT(real_name, 1) || '**' AS real_name_masked, -- 脱敏
avatar_url,
created_at
FROM users
WHERE status = 1;
-- 外模式2:用户订单视图
CREATE VIEW v_user_orders AS
SELECT
o.order_id,
o.user_id,
m.shop_name,
o.total_amount,
o.pay_amount,
CASE o.status
WHEN 0 THEN '待支付'
WHEN 1 THEN '已支付'
WHEN 2 THEN '已发货'
WHEN 3 THEN '已完成'
WHEN 4 THEN '已取消'
END AS status_text,
o.created_at AS order_time
FROM orders o
JOIN merchants m ON o.merchant_id = m.merchant_id;
-- 外模式3:商家订单管理视图
CREATE VIEW v_merchant_orders AS
SELECT
o.order_id,
o.merchant_id,
u.username AS buyer_name,
u.phone AS buyer_phone,
o.total_amount,
o.status,
o.receiver_name,
o.receiver_phone,
o.receiver_address,
o.created_at,
o.pay_time,
o.ship_time
FROM orders o
JOIN users u ON o.user_id = u.user_id;
-- 外模式4:商品列表视图(前台展示)
CREATE VIEW v_product_list AS
SELECT
p.product_id,
p.name,
p.price,
p.original_price,
p.sales,
m.shop_name,
c.name AS category_name
FROM products p
JOIN merchants m ON p.merchant_id = m.merchant_id
JOIN categories c ON p.category_id = c.category_id
WHERE p.status = 1 AND p.stock > 0;
-- 外模式5:运营统计视图
CREATE VIEW v_sales_statistics AS
SELECT
DATE(o.created_at) AS stat_date,
COUNT(DISTINCT o.order_id) AS order_count,
COUNT(DISTINCT o.user_id) AS buyer_count,
SUM(o.pay_amount) AS total_revenue,
AVG(o.pay_amount) AS avg_order_amount
FROM orders o
WHERE o.status IN (1, 2, 3)
GROUP BY DATE(o.created_at);
-- 外模式6:库存预警视图
CREATE VIEW v_stock_alert AS
SELECT
p.product_id,
p.name,
p.stock,
p.sales,
m.shop_name,
m.contact_phone
FROM products p
JOIN merchants m ON p.merchant_id = m.merchant_id
WHERE p.stock < 10 AND p.status = 1;
内模式设计(索引策略、分区设计)
sql
-- =====================================================
-- 内模式层:物理存储优化
-- =====================================================
-- 1. 订单表分区(按月分区,便于归档)
ALTER TABLE orders
PARTITION BY RANGE (YEAR(created_at) * 100 + MONTH(created_at)) (
PARTITION p202401 VALUES LESS THAN (202402),
PARTITION p202402 VALUES LESS THAN (202403),
PARTITION p202403 VALUES LESS THAN (202404),
-- ... 更多分区
PARTITION p_future VALUES LESS THAN MAXVALUE
);
-- 2. 热数据索引优化
CREATE INDEX idx_orders_recent ON orders(created_at DESC, status);
CREATE INDEX idx_products_hot ON products(sales DESC, created_at DESC);
-- 3. 商品搜索优化(全文索引)
ALTER TABLE products ADD FULLTEXT INDEX idx_search (name, description);
-- 4. 表空间规划
-- 订单数据放SSD
CREATE TABLESPACE ts_orders ADD DATAFILE '/ssd/orders.ibd';
-- 历史数据放HDD
CREATE TABLESPACE ts_archive ADD DATAFILE '/hdd/archive.ibd';
-- 5. 归档策略
CREATE TABLE orders_archive LIKE orders;
-- 定期将3个月前的已完成订单移到归档表
两级映像的应用
外模式/模式映像示例:
用户查询订单:
SELECT * FROM v_user_orders WHERE user_id = 123;
映像展开后:
SELECT o.order_id, o.user_id, m.shop_name, ...
FROM orders o JOIN merchants m ON ...
WHERE o.user_id = 123;
用户感知:只看到订单ID、店铺名、金额、状态
实际执行:从orders和merchants表联合查询
─────────────────────────────────────────────────────────────────
模式/内模式映像示例:
执行:SELECT * FROM orders WHERE created_at > '2024-01-01';
内模式处理:
1. 分区裁剪:只扫描p202401及以后的分区
2. 索引选择:使用idx_orders_recent索引
3. 物理读取:从SSD上的ts_orders表空间读取
用户感知:一张orders表
实际执行:只读取了相关分区的数据文件
6.2 案例二:银行系统数据库设计
业务需求分析
银行系统对数据安全性 和一致性有极高要求:
银行系统的特殊要求:
安全性要求:
├── 客户信息严格保密
├── 账户余额精确到分
├── 交易记录不可篡改
├── 操作必须有审计日志
└── 不同岗位权限严格隔离
一致性要求:
├── 转账必须原子性(要么都成功,要么都失败)
├── 余额永远不能为负
├── 每日对账必须平衡
└── 跨系统数据一致
三级模式设计
模式设计
sql
-- =====================================================
-- 银行核心表设计
-- =====================================================
-- 客户信息表
CREATE TABLE customers (
customer_id BIGINT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
id_type CHAR(1) NOT NULL, -- 1:身份证 2:护照 3:军官证
id_number VARCHAR(20) NOT NULL UNIQUE,
phone VARCHAR(20) NOT NULL,
email VARCHAR(100),
address VARCHAR(255),
risk_level TINYINT DEFAULT 1, -- 1:低 2:中 3:高
kyc_status TINYINT DEFAULT 0, -- KYC认证状态
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by BIGINT NOT NULL, -- 创建人(员工ID)
INDEX idx_id_number (id_number),
INDEX idx_phone (phone)
) ENGINE=InnoDB;
-- 账户表
CREATE TABLE accounts (
account_id BIGINT PRIMARY KEY,
account_number VARCHAR(20) NOT NULL UNIQUE,
customer_id BIGINT NOT NULL,
account_type TINYINT NOT NULL, -- 1:储蓄 2:活期 3:定期 4:贷款
currency CHAR(3) DEFAULT 'CNY',
balance DECIMAL(18,2) NOT NULL DEFAULT 0,
available_balance DECIMAL(18,2) NOT NULL DEFAULT 0,
status TINYINT DEFAULT 1, -- 1:正常 2:冻结 3:注销
open_date DATE NOT NULL,
close_date DATE,
branch_id INT NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id),
CONSTRAINT chk_balance CHECK (balance >= 0),
INDEX idx_customer (customer_id),
INDEX idx_branch (branch_id)
) ENGINE=InnoDB;
-- 交易流水表(核心,不可修改)
CREATE TABLE transactions (
trans_id BIGINT PRIMARY KEY,
trans_no VARCHAR(32) NOT NULL UNIQUE, -- 交易流水号
account_id BIGINT NOT NULL,
trans_type TINYINT NOT NULL, -- 1:存款 2:取款 3:转入 4:转出 5:利息
amount DECIMAL(18,2) NOT NULL,
balance_before DECIMAL(18,2) NOT NULL,
balance_after DECIMAL(18,2) NOT NULL,
related_account BIGINT, -- 对方账户
trans_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
channel TINYINT NOT NULL, -- 1:柜台 2:ATM 3:网银 4:手机银行
operator_id BIGINT, -- 操作员
branch_id INT,
remark VARCHAR(255),
-- 交易表只能INSERT,不能UPDATE/DELETE
INDEX idx_account (account_id),
INDEX idx_time (trans_time),
INDEX idx_trans_no (trans_no)
) ENGINE=InnoDB;
-- 审计日志表
CREATE TABLE audit_logs (
log_id BIGINT PRIMARY KEY AUTO_INCREMENT,
operator_id BIGINT NOT NULL,
operator_name VARCHAR(50),
action_type VARCHAR(50) NOT NULL,
target_table VARCHAR(50),
target_id BIGINT,
old_value JSON,
new_value JSON,
ip_address VARCHAR(50),
action_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_operator (operator_id),
INDEX idx_time (action_time)
) ENGINE=InnoDB;
外模式设计(严格权限隔离)
sql
-- =====================================================
-- 银行系统外模式:严格的权限控制
-- =====================================================
-- 柜员视图:只能看到客户基本信息,身份证号脱敏
CREATE VIEW v_teller_customer AS
SELECT
customer_id,
name,
CONCAT(LEFT(id_number, 6), '********', RIGHT(id_number, 4)) AS id_number_masked,
CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone_masked,
risk_level
FROM customers;
-- 柜员视图:账户信息(可办理业务)
CREATE VIEW v_teller_account AS
SELECT
a.account_id,
a.account_number,
a.customer_id,
c.name AS customer_name,
a.account_type,
a.balance,
a.status
FROM accounts a
JOIN customers c ON a.customer_id = c.customer_id;
-- 客户自助视图:只能看自己的账户
CREATE VIEW v_customer_account AS
SELECT
account_number,
CASE account_type
WHEN 1 THEN '储蓄账户'
WHEN 2 THEN '活期账户'
WHEN 3 THEN '定期账户'
END AS account_type_name,
balance,
available_balance
FROM accounts
WHERE customer_id = CURRENT_CUSTOMER_ID(); -- 函数获取当前登录客户
-- 客户交易查询视图:只显示近3个月
CREATE VIEW v_customer_transactions AS
SELECT
trans_no,
CASE trans_type
WHEN 1 THEN '存款'
WHEN 2 THEN '取款'
WHEN 3 THEN '转入'
WHEN 4 THEN '转出'
END AS trans_type_name,
amount,
balance_after AS balance,
trans_time,
remark
FROM transactions
WHERE account_id IN (
SELECT account_id FROM accounts
WHERE customer_id = CURRENT_CUSTOMER_ID()
)
AND trans_time > DATE_SUB(NOW(), INTERVAL 3 MONTH);
-- 风控视图:大额交易监控
CREATE VIEW v_risk_monitor AS
SELECT
t.trans_id,
t.trans_no,
a.account_number,
c.name AS customer_name,
c.risk_level,
t.amount,
t.trans_type,
t.channel,
t.trans_time
FROM transactions t
JOIN accounts a ON t.account_id = a.account_id
JOIN customers c ON a.customer_id = c.customer_id
WHERE t.amount >= 50000 -- 大额交易阈值
OR (c.risk_level >= 2 AND t.amount >= 10000);
-- 审计视图:敏感操作追踪
CREATE VIEW v_audit_trail AS
SELECT
l.action_time,
l.operator_name,
l.action_type,
l.target_table,
l.ip_address,
CASE
WHEN l.target_table = 'accounts' THEN a.account_number
WHEN l.target_table = 'customers' THEN c.name
END AS target_info
FROM audit_logs l
LEFT JOIN accounts a ON l.target_table = 'accounts' AND l.target_id = a.account_id
LEFT JOIN customers c ON l.target_table = 'customers' AND l.target_id = c.customer_id;
数据独立性的应用
sql
-- =====================================================
-- 数据独立性应用:身份证号加密升级
-- =====================================================
-- 需求:监管要求,身份证号必须加密存储
-- 挑战:不能影响现有业务系统
-- 步骤1:添加加密字段(模式变更)
ALTER TABLE customers ADD COLUMN id_number_encrypted VARBINARY(255);
-- 步骤2:迁移数据
UPDATE customers SET id_number_encrypted = AES_ENCRYPT(id_number, @key);
-- 步骤3:修改外模式映像(保持接口不变)
CREATE OR REPLACE VIEW v_teller_customer AS
SELECT
customer_id,
name,
-- 解密后再脱敏
CONCAT(
LEFT(AES_DECRYPT(id_number_encrypted, @key), 6),
'********',
RIGHT(AES_DECRYPT(id_number_encrypted, @key), 4)
) AS id_number_masked,
CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone_masked,
risk_level
FROM customers;
-- 步骤4:删除原明文字段(确认无问题后)
-- ALTER TABLE customers DROP COLUMN id_number;
-- 结果:
-- ✅ 物理存储:身份证号已加密
-- ✅ 柜员系统:功能完全不变,继续使用v_teller_customer
-- ✅ 应用程序:无需任何修改
安全性考量
银行系统三级模式安全架构:
┌───────────────────────────────────────────────────────────────┐
│ 应用层安全 │
│ 身份认证 → 权限校验 → 操作审计 │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 外模式层安全 │
│ 视图权限控制:不同角色只能访问授权的视图 │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ 柜员视图 │ │ 客户视图 │ │ 风控视图 │ │ 审计视图 │ │
│ │(脱敏数据) │ │(仅自己) │ │(监控数据) │ │(完整日志) │ │
│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 模式层安全 │
│ 约束保护:CHECK约束防止余额为负 │
│ 触发器:关键操作自动记录审计日志 │
│ 外键:确保数据引用完整性 │
└───────────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────┐
│ 内模式层安全 │
│ 存储加密:TDE透明数据加密 │
│ 备份加密:备份文件加密存储 │
│ 访问控制:存储层ACL │
└───────────────────────────────────────────────────────────────┘
6.3 案例三:数据库结构变更场景
需求变更背景
场景:社交平台用户系统升级
原始设计(2年前):
┌────────────────────────────────────┐
│ users │
│ id, username, email, password │
│ nickname, avatar, bio │
│ birthday, gender, location │
│ created_at, updated_at │
└────────────────────────────────────┘
问题:
1. 表字段越来越多(已有50+字段)
2. 查询性能下降
3. 不同业务模块都在往这张表加字段
4. 表结构耦合严重,改动风险大
目标:
1. 拆分为多个子表
2. 保持现有API兼容
3. 零停机迁移
如何利用三级模式实现无缝升级
第一步:设计目标模式(新表结构)
sql
-- 新的模式设计:用户表垂直拆分
-- 核心账户表(高频访问)
CREATE TABLE users_core (
user_id BIGINT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) UNIQUE,
phone VARCHAR(20) UNIQUE,
password_hash VARCHAR(255) NOT NULL,
status TINYINT DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 用户资料表(展示用)
CREATE TABLE users_profile (
user_id BIGINT PRIMARY KEY,
nickname VARCHAR(50),
avatar_url VARCHAR(255),
bio TEXT,
gender TINYINT,
birthday DATE,
location VARCHAR(100),
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users_core(user_id)
);
-- 用户设置表(低频访问)
CREATE TABLE users_settings (
user_id BIGINT PRIMARY KEY,
notification_email TINYINT DEFAULT 1,
notification_push TINYINT DEFAULT 1,
privacy_profile TINYINT DEFAULT 0, -- 0:公开 1:仅好友 2:私密
language VARCHAR(10) DEFAULT 'zh-CN',
timezone VARCHAR(50) DEFAULT 'Asia/Shanghai',
FOREIGN KEY (user_id) REFERENCES users_core(user_id)
);
-- 用户统计表(异步更新)
CREATE TABLE users_stats (
user_id BIGINT PRIMARY KEY,
followers_count INT DEFAULT 0,
following_count INT DEFAULT 0,
posts_count INT DEFAULT 0,
likes_count INT DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users_core(user_id)
);
第二步:创建兼容视图(外模式不变)
sql
-- 创建与原表完全兼容的视图
CREATE VIEW users AS
SELECT
c.user_id AS id,
c.username,
c.email,
c.password_hash AS password,
p.nickname,
p.avatar_url AS avatar,
p.bio,
p.birthday,
p.gender,
p.location,
c.created_at,
p.updated_at
FROM users_core c
LEFT JOIN users_profile p ON c.user_id = p.user_id;
-- 创建可更新视图的触发器
DELIMITER //
CREATE TRIGGER tr_users_insert
INSTEAD OF INSERT ON users
FOR EACH ROW
BEGIN
INSERT INTO users_core (user_id, username, email, password_hash, created_at)
VALUES (NEW.id, NEW.username, NEW.email, NEW.password, NEW.created_at);
INSERT INTO users_profile (user_id, nickname, avatar_url, bio, birthday, gender, location)
VALUES (NEW.id, NEW.nickname, NEW.avatar, NEW.bio, NEW.birthday, NEW.gender, NEW.location);
END //
CREATE TRIGGER tr_users_update
INSTEAD OF UPDATE ON users
FOR EACH ROW
BEGIN
UPDATE users_core SET
username = NEW.username,
email = NEW.email,
password_hash = NEW.password
WHERE user_id = NEW.id;
UPDATE users_profile SET
nickname = NEW.nickname,
avatar_url = NEW.avatar,
bio = NEW.bio,
birthday = NEW.birthday,
gender = NEW.gender,
location = NEW.location,
updated_at = NOW()
WHERE user_id = NEW.id;
END //
DELIMITER ;
第三步:数据迁移
sql
-- 迁移脚本
-- 3.1 创建新表(已完成)
-- 3.2 复制数据到新表
INSERT INTO users_core (user_id, username, email, password_hash, status, created_at)
SELECT id, username, email, password, status, created_at FROM users_old;
INSERT INTO users_profile (user_id, nickname, avatar_url, bio, gender, birthday, location)
SELECT id, nickname, avatar, bio, gender, birthday, location FROM users_old;
INSERT INTO users_settings (user_id)
SELECT id FROM users_old;
INSERT INTO users_stats (user_id)
SELECT id FROM users_old;
-- 3.3 验证数据一致性
SELECT
(SELECT COUNT(*) FROM users_old) AS old_count,
(SELECT COUNT(*) FROM users_core) AS new_count,
(SELECT COUNT(*) FROM users) AS view_count;
-- 3.4 重命名表
RENAME TABLE users TO users_old_backup;
-- 视图users已存在,指向新表
-- 3.5 验证应用功能
-- 所有使用users表的SQL都应该正常工作
映像调整策略
迁移过程中的映像变化:
阶段1:准备(双写)
┌─────────────┐ ┌─────────────┐
│ 应用程序 │ ──→ │ users表 │
└─────────────┘ └─────────────┘
│
↓ 同步
┌─────────────┐
│ 新表结构 │
└─────────────┘
阶段2:切换(视图替代)
┌─────────────┐ ┌─────────────┐
│ 应用程序 │ ──→ │ users视图 │ ← 外模式/模式映像
└─────────────┘ └─────────────┘
│
↓ 映射
┌─────────────────────────────┐
│ users_core + users_profile │
└─────────────────────────────┘
阶段3:优化(逐步迁移)
新功能直接使用新表结构
旧代码通过视图继续工作
逐步将旧代码迁移到新API
阶段4:清理
删除兼容视图
删除旧表备份
应用程序兼容性保障
sql
-- 兼容性检查清单
-- 1. 基本查询兼容
SELECT * FROM users WHERE id = 123; -- ✅ 通过视图正常工作
-- 2. 插入兼容(通过触发器)
INSERT INTO users (username, email, password, nickname)
VALUES ('test', 'test@example.com', 'hash', 'Test'); -- ✅ 正常
-- 3. 更新兼容(通过触发器)
UPDATE users SET nickname = 'NewName' WHERE id = 123; -- ✅ 正常
-- 4. 删除兼容
DELETE FROM users WHERE id = 123; -- ⚠️ 需要确保外键级联
-- 5. 事务兼容
START TRANSACTION;
UPDATE users SET email = 'new@example.com' WHERE id = 123;
COMMIT; -- ✅ 正常
-- 6. 性能验证
EXPLAIN SELECT * FROM users WHERE username = 'test';
-- 确保仍然使用索引
第七章 常见问题与最佳实践
在实际项目中应用三级模式时,经常会遇到各种问题。本章总结了常见的设计问题和经过验证的最佳实践。
7.1 设计常见问题
外模式设计过于复杂
问题表现:
sql
-- 反例:过于复杂的视图
CREATE VIEW v_super_complex AS
SELECT
u.id, u.name, u.email,
(SELECT COUNT(*) FROM orders WHERE user_id = u.id) AS order_count,
(SELECT SUM(amount) FROM orders WHERE user_id = u.id) AS total_spent,
(SELECT MAX(created_at) FROM orders WHERE user_id = u.id) AS last_order,
(SELECT AVG(rating) FROM reviews WHERE user_id = u.id) AS avg_rating,
-- ... 更多子查询
CASE
WHEN (SELECT COUNT(*) FROM orders WHERE user_id = u.id) > 100 THEN 'VIP'
WHEN (SELECT COUNT(*) FROM orders WHERE user_id = u.id) > 10 THEN '活跃'
ELSE '普通'
END AS user_level
FROM users u;
-- 问题:
-- 1. 每个字段都有子查询,性能极差
-- 2. 逻辑过于复杂,难以维护
-- 3. 无法使用索引优化
解决方案:
sql
-- 正例:拆分为多个简单视图 + 物化统计表
-- 1. 创建统计表(定期更新)
CREATE TABLE user_stats (
user_id INT PRIMARY KEY,
order_count INT DEFAULT 0,
total_spent DECIMAL(10,2) DEFAULT 0,
last_order_time DATETIME,
avg_rating DECIMAL(3,2),
user_level VARCHAR(20),
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 2. 创建简单视图
CREATE VIEW v_user_profile AS
SELECT u.id, u.name, u.email, s.user_level, s.order_count
FROM users u
LEFT JOIN user_stats s ON u.id = s.user_id;
-- 3. 定时任务更新统计
-- 每小时执行一次,增量更新
模式设计冗余
问题表现:
sql
-- 反例:字段冗余
CREATE TABLE orders (
order_id INT PRIMARY KEY,
user_id INT,
user_name VARCHAR(50), -- 冗余!用户名应该从users表获取
user_email VARCHAR(100), -- 冗余!
product_id INT,
product_name VARCHAR(100), -- 冗余!
product_price DECIMAL(10,2), -- 这个可能需要保留(历史价格)
total_amount DECIMAL(10,2)
);
-- 问题:
-- 1. 用户改名后,订单中的名字不一致
-- 2. 数据冗余导致存储浪费
-- 3. 更新异常风险
解决方案:
sql
-- 正例:遵循范式设计,必要时保留快照
CREATE TABLE orders (
order_id INT PRIMARY KEY,
user_id INT, -- 外键引用
product_id INT, -- 外键引用
product_price_snapshot DECIMAL(10,2), -- 下单时的价格(需要保留)
product_name_snapshot VARCHAR(100), -- 下单时的名称(可选)
total_amount DECIMAL(10,2)
);
-- 需要用户名时,通过JOIN获取
CREATE VIEW v_order_detail AS
SELECT o.*, u.name AS user_name, p.name AS current_product_name
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN products p ON o.product_id = p.id;
内模式优化不足
问题表现:
常见的内模式问题:
❌ 没有索引或索引不合理
- 频繁查询的字段没有索引
- 创建了过多不必要的索引
- 索引列顺序不合理
❌ 没有分区策略
- 亿级大表没有分区
- 查询需要全表扫描
❌ 存储配置不当
- 热数据和冷数据混在一起
- 没有使用SSD存储热数据
- 表空间配置混乱
解决方案:
sql
-- 索引优化清单
-- 1. 为WHERE条件中的字段创建索引
CREATE INDEX idx_user_email ON users(email);
-- 2. 复合索引遵循最左前缀原则
-- 如果经常查询: WHERE status = 1 AND created_at > '2024-01-01'
CREATE INDEX idx_status_created ON orders(status, created_at);
-- 3. 覆盖索引减少回表
CREATE INDEX idx_covering ON orders(user_id, status, created_at, amount);
-- 4. 大表必须分区
ALTER TABLE logs PARTITION BY RANGE (TO_DAYS(log_time)) (...);
-- 5. 定期分析索引使用情况
SELECT * FROM sys.schema_unused_indexes;
SELECT * FROM sys.schema_redundant_indexes;
映像维护困难
问题表现:
映像维护的痛点:
❌ 视图定义散落各处,没有统一管理
❌ 修改表结构时,不知道哪些视图会受影响
❌ 视图之间有依赖关系,修改顺序不当导致报错
❌ 开发、测试、生产环境的视图定义不同步
解决方案:
sql
-- 1. 建立视图依赖关系查询
SELECT
view_name,
table_name,
column_name
FROM information_schema.VIEW_COLUMN_USAGE
WHERE table_schema = 'mydb';
-- 2. 使用版本控制管理DDL
-- 在Git中维护所有视图定义
-- 目录结构:
-- /database
-- /views
-- v_user_profile.sql
-- v_order_summary.sql
-- /migrations
-- 001_create_tables.sql
-- 002_create_views.sql
-- 3. 使用迁移工具
-- Flyway / Liquibase 等数据库迁移工具
-- 4. 修改表结构前,先检查依赖
SELECT TABLE_NAME
FROM information_schema.VIEWS
WHERE VIEW_DEFINITION LIKE '%users%';
7.2 最佳实践
外模式设计原则
外模式设计的5个原则:
┌─────────────────────────────────────────────────────────────────┐
│ 原则1:最小暴露原则 │
├─────────────────────────────────────────────────────────────────┤
│ 只暴露用户需要的字段,隐藏敏感信息 │
│ │
│ 正例:CREATE VIEW v_public AS │
│ SELECT id, name FROM users; -- 只暴露必要字段 │
│ │
│ 反例:CREATE VIEW v_public AS │
│ SELECT * FROM users; -- 暴露了password等敏感字段 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 原则2:单一职责原则 │
├─────────────────────────────────────────────────────────────────┤
│ 每个视图只服务于一个业务场景 │
│ │
│ 正例:v_user_list(列表展示) │
│ v_user_detail(详情页) │
│ v_user_stats(统计报表) │
│ │
│ 反例:v_user_everything(包含所有场景的字段) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 原则3:命名规范原则 │
├─────────────────────────────────────────────────────────────────┤
│ 使用统一的命名规范,便于识别和管理 │
│ │
│ 推荐命名: │
│ v_<业务域>_<用途> │
│ 例如:v_order_list, v_user_profile, v_report_daily │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 原则4:性能友好原则 │
├─────────────────────────────────────────────────────────────────┤
│ 视图的SQL要能够高效执行 │
│ │
│ 检查清单: │
│ □ 避免在视图中使用子查询 │
│ □ 确保JOIN的字段有索引 │
│ □ 避免使用SELECT * │
│ □ 复杂聚合考虑用物化视图 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 原则5:文档化原则 │
├─────────────────────────────────────────────────────────────────┤
│ 为每个视图编写注释说明 │
│ │
│ -- ============================================ │
│ -- 视图名称:v_order_summary │
│ -- 用途:订单汇总报表 │
│ -- 创建人:张三 │
│ -- 创建日期:2024-01-01 │
│ -- 依赖表:orders, users, products │
│ -- ============================================ │
│ CREATE VIEW v_order_summary AS ... │
└─────────────────────────────────────────────────────────────────┘
模式设计规范
sql
-- 模式设计规范清单
-- 1. 表命名规范
-- 使用小写字母和下划线
-- 表名使用复数形式
CREATE TABLE users (...);
CREATE TABLE order_items (...);
-- 2. 字段命名规范
-- 主键统一命名为 id 或 表名_id
-- 外键命名为 关联表名_id
-- 时间字段统一后缀 _at 或 _time
CREATE TABLE orders (
id BIGINT PRIMARY KEY, -- 或 order_id
user_id BIGINT, -- 外键
created_at TIMESTAMP, -- 创建时间
updated_at TIMESTAMP, -- 更新时间
deleted_at TIMESTAMP -- 软删除时间
);
-- 3. 约束规范
-- 每个表必须有主键
-- 外键关系必须显式定义
-- 使用CHECK约束保证数据完整性
CREATE TABLE products (
id BIGINT PRIMARY KEY,
price DECIMAL(10,2) CHECK (price >= 0),
stock INT CHECK (stock >= 0),
status TINYINT CHECK (status IN (0, 1, 2))
);
-- 4. 注释规范
-- 表和重要字段必须有注释
CREATE TABLE orders (
id BIGINT PRIMARY KEY COMMENT '订单ID',
status TINYINT COMMENT '状态:0待支付 1已支付 2已发货 3已完成 4已取消',
amount DECIMAL(10,2) COMMENT '订单金额,单位:元'
) COMMENT '订单表';
-- 5. 索引规范
-- 主键自动创建索引
-- 外键必须创建索引
-- 经常查询的字段创建索引
-- 避免在低基数字段创建索引
内模式优化策略
内模式优化策略矩阵:
┌─────────────────┬────────────────────────────────────────────┐
│ 优化方向 │ 具体策略 │
├─────────────────┼────────────────────────────────────────────┤
│ │ • 为WHERE/JOIN/ORDER BY字段创建索引 │
│ 索引优化 │ • 使用覆盖索引减少回表 │
│ │ • 定期清理无用索引 │
│ │ • 监控慢查询,针对性优化 │
├─────────────────┼────────────────────────────────────────────┤
│ │ • 时间序列数据按时间分区 │
│ 分区策略 │ • 大表按业务维度分区 │
│ │ • 定期归档历史分区 │
│ │ • 利用分区裁剪提升查询性能 │
├─────────────────┼────────────────────────────────────────────┤
│ │ • 热数据放SSD,冷数据放HDD │
│ 存储分层 │ • 使用表空间隔离不同业务 │
│ │ • 定期将冷数据迁移到归档存储 │
│ │ • 考虑使用列式存储存放分析数据 │
├─────────────────┼────────────────────────────────────────────┤
│ │ • 对历史数据启用压缩 │
│ 数据压缩 │ • 选择合适的压缩算法 │
│ │ • 权衡压缩率与CPU开销 │
├─────────────────┼────────────────────────────────────────────┤
│ │ • 调整缓冲池大小 │
│ 内存优化 │ • 配置合理的连接池 │
│ │ • 使用内存表存储会话数据 │
└─────────────────┴────────────────────────────────────────────┘
映像管理建议
1. 版本化管理
数据库变更的版本化管理:
/database
/migrations
V001__create_users_table.sql
V002__create_orders_table.sql
V003__create_user_profile_view.sql
V004__add_index_on_orders.sql
V005__modify_user_view.sql
/rollback
V003__rollback.sql
V005__rollback.sql
使用Flyway/Liquibase等工具管理迁移
每次变更都有对应的版本号和回滚脚本
2. 变更影响分析
sql
-- 变更前必须执行的检查
-- 检查视图依赖
SELECT
ROUTINE_NAME,
ROUTINE_TYPE
FROM information_schema.ROUTINES
WHERE ROUTINE_DEFINITION LIKE '%users%';
-- 检查外键依赖
SELECT
TABLE_NAME,
COLUMN_NAME,
CONSTRAINT_NAME,
REFERENCED_TABLE_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_NAME = 'users';
-- 生成变更影响报告
3. 环境一致性
确保各环境映像定义一致:
开发环境 ←→ 测试环境 ←→ 生产环境
↑ ↑ ↑
└───────────┴───────────┘
使用相同的迁移脚本
CI/CD流程:
1. 开发提交DDL变更
2. 自动化测试验证
3. 代码审查
4. 测试环境部署验证
5. 生产环境发布
7.3 性能考量
映像层次对性能的影响
映像层次与性能的关系:
无视图(直接查表):
SQL: SELECT * FROM users WHERE id = 1
执行路径:SQL → 表 → 磁盘
开销:最小
简单视图:
SQL: SELECT * FROM v_users WHERE id = 1
执行路径:SQL → 视图展开 → 表 → 磁盘
开销:极小(视图展开几乎无开销)
复杂视图(多表JOIN):
SQL: SELECT * FROM v_user_orders WHERE user_id = 1
执行路径:SQL → 视图展开 → 多表JOIN → 磁盘
开销:取决于JOIN效率和索引
嵌套视图(视图引用视图):
SQL: SELECT * FROM v_user_summary
执行路径:SQL → 外层视图 → 内层视图 → 表 → 磁盘
开销:可能很大,应避免多层嵌套
如何平衡数据独立性与性能
平衡策略矩阵:
┌─────────────────────────────────────────────────────────────────┐
│ 数据独立性 vs 性能 │
├───────────────────────┬─────────────────────────────────────────┤
│ 高独立性 │ 高性能 │
├───────────────────────┼─────────────────────────────────────────┤
│ 复杂视图封装业务逻辑 │ 直接查询基表 │
│ 完全解耦 │ 紧耦合,但速度快 │
├───────────────────────┴─────────────────────────────────────────┤
│ │
│ 平衡点选择策略 │
│ │
│ 1. 读多写少的场景:使用物化视图 │
│ - 预计算结果,查询速度快 │
│ - 定时刷新,保证数据最终一致 │
│ │
│ 2. 高并发场景:简化视图 + 缓存 │
│ - 视图只做最基本的字段映射 │
│ - 业务逻辑放应用层 │
│ - 使用Redis缓存热点数据 │
│ │
│ 3. 分析场景:使用专门的分析库 │
│ - OLTP库:简单视图,保证事务性能 │
│ - OLAP库:复杂视图,支持复杂分析 │
│ │
│ 4. 核心交易场景:直接使用基表 │
│ - 交易核心路径不使用视图 │
│ - 保证最高性能 │
│ - 独立性通过应用层抽象实现 │
│ │
└─────────────────────────────────────────────────────────────────┘
优化建议
1. 视图性能优化
sql
-- 优化前:子查询视图
CREATE VIEW v_user_stats AS
SELECT
user_id,
(SELECT COUNT(*) FROM orders WHERE user_id = u.id) AS order_count
FROM users u;
-- 优化后:JOIN视图
CREATE VIEW v_user_stats AS
SELECT
u.id AS user_id,
COALESCE(o.order_count, 0) AS order_count
FROM users u
LEFT JOIN (
SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id
) o ON u.id = o.user_id;
-- 更优:物化统计表
-- 通过触发器或定时任务维护
2. 查询优化检查清单
sql
-- 使用EXPLAIN分析执行计划
EXPLAIN SELECT * FROM v_user_orders WHERE user_id = 123;
-- 检查要点:
-- 1. type列:避免ALL(全表扫描)
-- 2. rows列:扫描行数要尽量少
-- 3. Extra列:避免Using filesort、Using temporary
-- 4. key列:确保使用了正确的索引
-- 性能优化SQL模板
SELECT /*+ INDEX(t idx_name) */
t.id, t.name
FROM table t
WHERE t.status = 1
AND t.created_at > '2024-01-01'
ORDER BY t.created_at DESC
LIMIT 100;
3. 监控与告警
sql
-- 慢查询监控
SELECT
query,
exec_count,
avg_latency,
rows_examined_avg
FROM sys.statements_with_runtimes_in_95th_percentile;
-- 未使用索引的查询
SELECT
query,
full_scan,
exec_count
FROM sys.statements_with_full_table_scans
ORDER BY exec_count DESC;
-- 设置告警阈值
-- 慢查询 > 1秒:告警
-- 全表扫描 > 10000行:告警
第八章 面试与考试重点
无论是准备软考、数据库原理课程考试,还是技术面试,三级模式与两级映像都是必考知识点。本章梳理常见考点和答题技巧。
8.1 概念辨析题
三级模式的区别
考点:区分外模式、模式、内模式
| 对比项 | 外模式 | 模式 | 内模式 |
|---|---|---|---|
| 别名 | 子模式、用户模式 | 逻辑模式、概念模式 | 存储模式、物理模式 |
| 描述对象 | 用户视图 | 全局逻辑结构 | 物理存储结构 |
| 面向对象 | 应用程序/用户 | 数据库设计者 | 系统管理员 |
| 数量 | 多个(每个应用可有自己的外模式) | 唯一一个 | 唯一一个 |
| 实现方式 | VIEW | TABLE | 索引、存储、分区 |
| 关注重点 | 数据的逻辑表示 | 数据的完整定义 | 数据的存储效率 |
典型题目:
题目:判断下列说法的正误
1. 一个数据库可以有多个模式( )
答案:✗
解析:模式只有一个,描述全局逻辑结构
2. 外模式是模式的子集( )
答案:✓
解析:外模式是模式的部分视图
3. 内模式与具体DBMS无关( )
答案:✗
解析:内模式依赖于具体DBMS的存储实现
两级映像的作用
考点:两级映像与数据独立性的关系
映像作用对照表:
┌─────────────────────┬─────────────────────┬─────────────────────┐
│ 映像 │ 作用 │ 保证的独立性 │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ 外模式/模式映像 │ 定义外模式与模式的 │ 逻辑数据独立性 │
│ │ 对应关系 │ │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ 模式/内模式映像 │ 定义模式与内模式的 │ 物理数据独立性 │
│ │ 对应关系 │ │
└─────────────────────┴─────────────────────┴─────────────────────┘
记忆口诀:
"外模式上,内模式下,模式居中连接它"
"上层改变改上映像,下层改变改下映像"
典型题目:
题目:当数据库的存储结构发生变化时,应该修改( )
A. 外模式
B. 模式
C. 外模式/模式映像
D. 模式/内模式映像
答案:D
解析:存储结构属于内模式范畴,变化时需要修改模式/内模式映像,
以保证模式不变,从而保证物理数据独立性。
数据独立性的类型
考点:逻辑独立性与物理独立性的区别
数据独立性对比:
┌─────────────────┬───────────────────────┬───────────────────────┐
│ 类型 │ 逻辑数据独立性 │ 物理数据独立性 │
├─────────────────┼───────────────────────┼───────────────────────┤
│ 定义 │ 模式变化不影响外模式 │ 内模式变化不影响模式 │
│ │ 和应用程序 │ 和应用程序 │
├─────────────────┼───────────────────────┼───────────────────────┤
│ 涉及层次 │ 外模式 ↔ 模式 │ 模式 ↔ 内模式 │
├─────────────────┼───────────────────────┼───────────────────────┤
│ 实现机制 │ 外模式/模式映像 │ 模式/内模式映像 │
├─────────────────┼───────────────────────┼───────────────────────┤
│ 典型场景 │ 增加/删除表字段 │ 增加索引、改变存储 │
│ │ 拆分/合并表 │ 修改分区策略 │
├─────────────────┼───────────────────────┼───────────────────────┤
│ 实现难度 │ 较难 │ 较易 │
└─────────────────┴───────────────────────┴───────────────────────┘
8.2 设计分析题
给定场景设计三级模式
题型示例:
题目:某图书馆管理系统需要管理图书信息、读者信息和借阅记录。
请设计该系统的三级模式结构。
参考答案:
一、模式设计(全局逻辑结构)
CREATE TABLE books (
book_id INT PRIMARY KEY,
isbn VARCHAR(20) UNIQUE,
title VARCHAR(200) NOT NULL,
author VARCHAR(100),
publisher VARCHAR(100),
publish_date DATE,
category VARCHAR(50),
location VARCHAR(50), -- 馆藏位置
status TINYINT -- 0:可借 1:已借出 2:维护中
);
CREATE TABLE readers (
reader_id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
id_card VARCHAR(20) UNIQUE,
phone VARCHAR(20),
email VARCHAR(100),
reader_type TINYINT, -- 1:学生 2:教师 3:公众
max_borrow INT, -- 最大借书数量
register_date DATE
);
CREATE TABLE borrows (
borrow_id INT PRIMARY KEY,
book_id INT,
reader_id INT,
borrow_date DATE NOT NULL,
due_date DATE NOT NULL,
return_date DATE,
fine DECIMAL(10,2) DEFAULT 0,
FOREIGN KEY (book_id) REFERENCES books(book_id),
FOREIGN KEY (reader_id) REFERENCES readers(reader_id)
);
二、外模式设计(不同用户视图)
-- 读者视图:只能看到可借图书
CREATE VIEW v_available_books AS
SELECT book_id, title, author, publisher, category, location
FROM books WHERE status = 0;
-- 读者视图:查看自己的借阅记录
CREATE VIEW v_my_borrows AS
SELECT b.title, br.borrow_date, br.due_date, br.return_date
FROM borrows br
JOIN books b ON br.book_id = b.book_id
WHERE br.reader_id = CURRENT_READER_ID();
-- 管理员视图:借阅统计
CREATE VIEW v_borrow_stats AS
SELECT
b.category,
COUNT(*) AS borrow_count,
COUNT(DISTINCT br.reader_id) AS reader_count
FROM borrows br
JOIN books b ON br.book_id = b.book_id
GROUP BY b.category;
-- 管理员视图:逾期未还
CREATE VIEW v_overdue AS
SELECT r.name, r.phone, b.title, br.due_date,
DATEDIFF(NOW(), br.due_date) AS overdue_days
FROM borrows br
JOIN readers r ON br.reader_id = r.reader_id
JOIN books b ON br.book_id = b.book_id
WHERE br.return_date IS NULL AND br.due_date < NOW();
三、内模式设计(物理存储优化)
-- 常用查询索引
CREATE INDEX idx_books_isbn ON books(isbn);
CREATE INDEX idx_books_title ON books(title);
CREATE INDEX idx_borrows_reader ON borrows(reader_id);
CREATE INDEX idx_borrows_date ON borrows(borrow_date);
-- 借阅记录按年份分区
ALTER TABLE borrows PARTITION BY RANGE (YEAR(borrow_date)) (...);
-- 热数据与冷数据分离
-- 当年借阅记录放SSD,历史记录放HDD
分析数据独立性
题型示例:
题目:图书馆系统需要进行以下调整,分析哪些需要修改应用程序:
调整1:为books表增加一个price字段
答案:不需要修改应用程序
分析:模式变更,但外模式(视图)没有引用price字段,
外模式/模式映像自动适应,体现逻辑数据独立性
调整2:将books表的location字段从VARCHAR(50)改为VARCHAR(100)
答案:不需要修改应用程序
分析:字段类型兼容性变更,不影响外模式,逻辑独立性
调整3:将borrows表从单表改为按年分区
答案:不需要修改应用程序
分析:内模式变更,修改模式/内模式映像即可,
模式和外模式都不变,体现物理数据独立性
调整4:将readers表拆分为readers_basic和readers_detail两个表
答案:不需要修改应用程序(如果正确使用视图)
分析:模式变更,需要修改外模式/模式映像,
创建兼容视图:
CREATE VIEW readers AS
SELECT * FROM readers_basic
JOIN readers_detail USING(reader_id);
应用程序通过视图访问,无需修改,体现逻辑数据独立性
8.3 常见面试问题
问题1:什么是三级模式?
标准答案模板:
三级模式是数据库系统的体系结构,由ANSI/SPARC在1975年提出,
包含三个层次:
1. 外模式(External Schema)
- 也称为子模式或用户模式
- 是用户看到的数据视图
- 一个数据库可以有多个外模式
- 通过视图(VIEW)实现
2. 模式(Schema)
- 也称为逻辑模式或概念模式
- 是数据库的全局逻辑结构
- 一个数据库只有一个模式
- 通过表定义(CREATE TABLE)实现
3. 内模式(Internal Schema)
- 也称为存储模式或物理模式
- 是数据的物理存储结构
- 一个数据库只有一个内模式
- 通过索引、存储引擎、分区等实现
三级模式的核心目的是实现数据独立性,使应用程序与数据的
物理存储和逻辑结构解耦。
加分回答:
在实际工作中,我们经常使用视图来为不同的业务系统提供不同的
数据视图,这就是外模式的应用。比如在电商系统中,普通用户看到
的商品列表视图会隐藏成本价,而运营人员的视图则包含完整信息。
问题2:为什么要有两级映像?
标准答案模板:
两级映像是连接三级模式的桥梁,包括:
1. 外模式/模式映像
- 定义外模式与模式之间的对应关系
- 当模式改变时,只需调整映像,外模式可保持不变
- 保证了逻辑数据独立性
2. 模式/内模式映像
- 定义模式与内模式之间的对应关系
- 当内模式改变时,只需调整映像,模式可保持不变
- 保证了物理数据独立性
两级映像使得数据库系统具有良好的可维护性和可扩展性。
当底层存储结构优化或上层业务逻辑变化时,可以通过调整
映像来适应变化,而不需要修改其他层次的定义。
问题3:如何理解数据独立性?
标准答案模板:
数据独立性是指应用程序与数据之间相互独立,互不影响的特性。
包括两个层次:
1. 逻辑数据独立性
- 模式变化时,应用程序不需要修改
- 例如:表增加字段、表结构调整
- 通过外模式/模式映像实现
- 实现难度较高
2. 物理数据独立性
- 内模式变化时,应用程序不需要修改
- 例如:添加索引、改变存储方式、分区调整
- 通过模式/内模式映像实现
- 实现难度较低
数据独立性的意义:
- 降低应用程序与数据库的耦合度
- 提高系统的可维护性
- 支持数据库的平滑升级和优化
问题4:逻辑独立性和物理独立性的区别?
简洁对比答案:
逻辑数据独立性:
- 位置:外模式与模式之间
- 场景:表字段增删、表拆分合并
- 目的:保护应用程序不受逻辑结构变化影响
- 实现:外模式/模式映像(视图)
- 难度:较难(可能需要重新设计视图)
物理数据独立性:
- 位置:模式与内模式之间
- 场景:加索引、改存储引擎、分区
- 目的:保护模式不受存储方式变化影响
- 实现:模式/内模式映像(DBMS自动管理)
- 难度:较易(通常由DBMS自动处理)
一句话总结:
逻辑独立性保护"应用程序",物理独立性保护"逻辑结构"。
8.4 真题解析
软考相关真题
【真题1】数据库系统设计师 2023年
题目:在数据库三级模式结构中,描述数据库中全体数据的逻辑结构
和特征的是( )
A. 外模式
B. 内模式
C. 模式
D. 存储模式
答案:C
解析:
- 模式(也称概念模式、逻辑模式)描述全局逻辑结构
- 外模式是模式的子集,面向用户
- 内模式(存储模式)描述物理存储
- 本题考查模式的定义
【真题2】软件设计师 2022年
题目:数据库系统的三级模式结构中,保证数据与程序物理独立性的
是( )
A. 外模式
B. 外模式/模式映像
C. 模式/内模式映像
D. 内模式
答案:C
解析:
- 物理独立性:内模式变化不影响模式
- 由"模式/内模式映像"保证
- 逻辑独立性由"外模式/模式映像"保证
- 本题考查映像与独立性的对应关系
【真题3】数据库系统工程师 2021年
题目:下列关于数据库三级模式的叙述,正确的是( )
A. 一个数据库可以有多个模式
B. 一个数据库只能有一个外模式
C. 内模式可以有多个
D. 外模式是模式的子集
答案:D
解析:
A错误:一个数据库只有一个模式
B错误:一个数据库可以有多个外模式
C错误:一个数据库只有一个内模式
D正确:外模式是模式的部分视图,是子集
数据库原理课程典型试题
【试题1】简答题
题目:简述数据库三级模式两级映像体系结构,并说明其优点。
参考答案要点:
一、三级模式
1. 外模式:用户视图,可有多个
2. 模式:全局逻辑结构,唯一
3. 内模式:物理存储结构,唯一
二、两级映像
1. 外模式/模式映像:保证逻辑独立性
2. 模式/内模式映像:保证物理独立性
三、优点
1. 保证数据独立性
2. 有利于数据共享
3. 减少数据冗余
4. 保障数据安全
5. 简化用户接口
(答题技巧:三级模式画图,两级映像说作用,优点分点列出)
【试题2】分析题
题目:某公司数据库管理员需要将某张大表从普通表改为分区表,
请分析这一操作属于哪个层次的变更?是否需要修改应用程序?
为什么?
参考答案:
1. 层次分析
这属于内模式层次的变更。分区是物理存储的组织方式,
改变分区策略属于存储结构的调整,不影响表的逻辑定义。
2. 是否修改应用程序
不需要修改应用程序。
3. 原因分析
- 分区表对应用程序透明,SQL语句完全相同
- 模式(表的逻辑定义)没有改变
- 由DBMS通过模式/内模式映像自动处理分区路由
- 这正是物理数据独立性的体现
4. 实际操作
DBA只需执行ALTER TABLE语句添加分区,
应用程序无需任何修改即可继续正常运行。
【试题3】设计题
题目:设计一个学生选课系统的三级模式,要求:
- 学生只能看到自己的选课信息
- 教师能看到所授课程的所有选课学生
- 管理员能看到全部信息
参考答案框架:
模式(核心表):
- students(sid, name, dept, ...)
- teachers(tid, name, dept, ...)
- courses(cid, name, tid, credits, ...)
- enrollments(sid, cid, grade, term, ...)
外模式(视图):
- v_student_courses:学生查看自己的课程
- v_teacher_students:教师查看选课学生
- v_admin_all:管理员完整视图
内模式(存储优化):
- 索引:enrollments表的sid和cid
- 分区:按学期分区
总结
三级模式结构的核心要点回顾
数据库三级模式体系结构总结:
┌─────────────────────────────────────────────────────────────────┐
│ 三级模式架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 外模式(多个) │ │
│ │ 用户视图 → VIEW → 面向应用程序 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↕ 外模式/模式映像 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 模式(唯一) │ │
│ │ 全局结构 → TABLE → 面向数据库设计者 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↕ 模式/内模式映像 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 内模式(唯一) │ │
│ │ 物理存储 → 索引/分区 → 面向系统管理员 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
核心记忆点:
✓ 外模式:用户看到的,可以有多个
✓ 模式:数据库的全貌,只有一个
✓ 内模式:存储细节,只有一个
两级映像的关键作用
两级映像的核心价值:
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 外模式 ←───── 外模式/模式映像 ─────→ 逻辑数据独立性 │
│ ↑ │
│ │ 视图定义(VIEW) │
│ │ • 隐藏不需要的数据 │
│ │ • 提供定制化视图 │
│ │ • 保护模式变更不影响应用 │
│ ↓ │
│ 模式 ←───── 模式/内模式映像 ─────→ 物理数据独立性 │
│ ↑ │
│ │ DBMS自动管理 │
│ │ • 索引选择透明 │
│ │ • 分区路由透明 │
│ │ • 存储优化透明 │
│ ↓ │
│ 内模式 │
│ │
└─────────────────────────────────────────────────────────────────┘
一句话总结:
映像是"翻译器",把一层的变化"翻译"成另一层能理解的形式,
从而实现各层之间的独立性。
数据独立性的实现意义
数据独立性带来的实际价值:
对于开发者:
├── 不需要关心数据如何存储
├── 表结构调整时代码改动最小化
├── 可以专注于业务逻辑而非存储细节
└── 降低开发和维护成本
对于DBA:
├── 可以自由优化存储结构
├── 可以调整索引策略而不影响应用
├── 可以进行分区、压缩等优化
└── 可以进行数据库迁移和升级
对于架构师:
├── 系统具有良好的可扩展性
├── 可以平滑进行技术栈升级
├── 降低系统的整体耦合度
└── 提高系统的可维护性
对于企业:
├── 降低IT系统的运维成本
├── 提高业务的敏捷性
├── 保护历史投资
└── 支持业务快速变化
在实际开发中的应用建议
实践建议清单:
1. 视图使用规范
□ 为不同用户角色创建专门的视图
□ 视图命名使用统一前缀(如v_)
□ 避免在视图中使用复杂子查询
□ 为视图编写清晰的注释文档
2. 模式设计规范
□ 遵循数据库范式设计
□ 主键、外键、约束要完整
□ 字段命名统一规范
□ 表和字段要有注释
3. 内模式优化策略
□ 为高频查询字段创建索引
□ 大表要考虑分区策略
□ 定期分析和优化索引
□ 热冷数据分层存储
4. 变更管理流程
□ 使用版本控制管理DDL
□ 变更前做影响分析
□ 保持环境一致性
□ 制定回滚方案
5. 思维方式转变
□ 把视图当作API来设计
□ 把表结构当作实现细节
□ 充分利用DBMS的透明性
□ 权衡独立性与性能
附录
附录A:术语表
| 中文术语 | 英文术语 | 简要说明 |
|---|---|---|
| 三级模式 | Three-Level Schema | 数据库体系结构的三个层次 |
| 外模式 | External Schema | 用户视图,也称子模式 |
| 模式 | Schema | 全局逻辑结构,也称概念模式 |
| 内模式 | Internal Schema | 物理存储结构,也称存储模式 |
| 两级映像 | Two-Level Mapping | 连接三级模式的映射关系 |
| 外模式/模式映像 | External/Conceptual Mapping | 外模式到模式的映射 |
| 模式/内模式映像 | Conceptual/Internal Mapping | 模式到内模式的映射 |
| 数据独立性 | Data Independence | 数据与程序相互独立的特性 |
| 逻辑数据独立性 | Logical Data Independence | 模式变化不影响应用程序 |
| 物理数据独立性 | Physical Data Independence | 内模式变化不影响模式 |
| 视图 | View | 外模式的主要实现方式 |
| 数据字典 | Data Dictionary | 存储元数据的系统表 |
| DBMS | Database Management System | 数据库管理系统 |
| DDL | Data Definition Language | 数据定义语言 |
| DML | Data Manipulation Language | 数据操作语言 |
| 索引 | Index | 加速数据访问的数据结构 |
| 分区 | Partition | 将大表分割为多个部分 |
| 表空间 | Tablespace | 逻辑存储容器 |
| 物化视图 | Materialized View | 预计算并存储的视图 |
附录B:参考资料
经典教材推荐
| 书名 | 作者 | 说明 |
|---|---|---|
| 《数据库系统概念》 | Silberschatz等 | 数据库领域经典教材,全面系统 |
| 《数据库系统原理》 | 王珊、萨师煊 | 国内权威教材,软考必备 |
| 《高性能MySQL》 | Baron Schwartz等 | MySQL深入实践指南 |
| 《Oracle Database概念》 | Oracle官方 | Oracle体系结构详解 |
| 《PostgreSQL指南》 | 社区 | PostgreSQL最佳实践 |
在线资源链接
官方文档:
• MySQL文档:https://dev.mysql.com/doc/
• PostgreSQL文档:https://www.postgresql.org/docs/
• Oracle文档:https://docs.oracle.com/en/database/
• SQL Server文档:https://docs.microsoft.com/sql/
学习资源:
• CSDN数据库专区
• 掘金数据库话题
• GitHub数据库最佳实践
认证考试:
• 软考官网(软件设计师、数据库系统工程师)
• Oracle认证(OCP/OCM)
• MySQL认证
附录C:思维导图
三级模式与两级映像关系图
┌─────────────────────────────────────┐
│ 数据库系统 │
└─────────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ 三级模式 │ │ 两级映像 │
└───────────────┘ └───────────────┘
│ │
┌───────────┼───────────┐ ┌─────────┴─────────┐
▼ ▼ ▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐ ┌─────────────┐ ┌─────────────┐
│外模式 │ │ 模式 │ │内模式 │ │外模式/模式 │ │模式/内模式 │
│ │ │ │ │ │ │ 映像 │ │ 映像 │
└───────┘ └───────┘ └───────┘ └─────────────┘ └─────────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐ ┌─────────────┐ ┌─────────────┐
│ VIEW │ │ TABLE │ │ 索引 │ │逻辑数据独立│ │物理数据独立│
│ │ │ │ │ 分区 │ │ 性 │ │ 性 │
└───────┘ └───────┘ └───────┘ └─────────────┘ └─────────────┘
数据独立性实现机制图
┌─────────────────────────────────────────────────────────────────────┐
│ 数据独立性实现机制 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 逻辑数据独立性 │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ 模式变更 外模式/模式映像调整 │ │
│ │ ┌──────────┐ ┌──────────────────────┐ │ │
│ │ │ 增加字段 │ ───────→ │ 视图定义不变 │ │ │
│ │ │ 表拆分 │ │ 或创建兼容视图 │ │ │
│ │ │ 改字段名 │ │ │ │ │
│ │ └──────────┘ └──────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────┐ │ │
│ │ │ 应用程序无需修改 │ │ │
│ │ └──────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 物理数据独立性 │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ 内模式变更 模式/内模式映像调整(DBMS自动) │ │
│ │ ┌──────────┐ ┌──────────────────────┐ │ │
│ │ │ 添加索引 │ ───────→ │ 查询优化器自动选择 │ │ │
│ │ │ 分区调整 │ │ 分区路由自动处理 │ │ │
│ │ │ 换存储 │ │ 存储访问自动适配 │ │ │
│ │ └──────────┘ └──────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────┐ │ │
│ │ │ 模式和应用都不变 │ │ │
│ │ └──────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
本文档详细介绍了数据库三级模式与两级映像的概念、原理和实践应用。适合用于软考备考、数据库课程学习以及技术面试准备。如有问题或建议,欢迎交流讨论。