本文基于 NCRE 计算机三级数据库考试中的题库内容(2025.09)进行编写,本文内容可能与实际考试会存在偏差,请以考试实际为准
第1章 数据库应用系统开发方法
1.1 数据库三级模式
-
数据库三级模式结构:外模式(子模式/用户模式)、模式(逻辑模式)、内模式(存储模式/物理模式),以及两层映像。
- 外模式/模式映像:解决外模式和模式之间的对应关系,保证应用程序不受模式改变影响,提供 逻辑独立性。
- 模式/内模式映像:解决模式和内模式之间的对应关系,保证应用程序不受存储结构改变影响,提供 物理独立性。
数据库的三级模式由外模式、模式、内模式构成。外模式是用户可见的部分数据的存在形式;模式可以等价为全体数据的逻辑结构且用户不可见,是三级模式的中间部分;内模式对应数据库的物理结构和存储方式。当模式改变时,由数据库管理员对各个外模式/模式的映像作相应的改变,但是外模式可以不变。
-
逻辑独立性 (Logical Data Independence)
- 定义:当数据库的 概念模式(模式) 发生变化时(比如增加字段、改变字段名、拆表合并表),应用程序和外模式不需要修改。
- 本质:外模式与模式之间通过 外模式/模式映像 隔离了变化。
- 举例:
- 原来学生表有字段:
学号, 姓名 - 后来数据库管理员在模式里加了
出生日期 - 应用里只用
学号, 姓名的地方照常运行,不受影响。
- 原来学生表有字段:
- 比喻:就像你在手机上看菜单,餐厅后台换了厨师或者食材名,你点餐界面依旧一样。
-
物理独立性 (Physical Data Independence)
- 定义:当数据库的 存储结构(内模式) 发生变化时(比如文件结构、索引方式、存储路径),应用程序和模式不需要修改。
- 本质:模式与内模式之间通过 模式/内模式映像 隔离了变化。
- 举例:
- 数据库存储学生表时,原来是 顺序存储,后来改为 B+树索引存储。
- 对应用层的 SQL 查询
SELECT 姓名 FROM 学生没有任何影响。
- 比喻:就像你在外卖App上下单,不管餐厅是用煤气炒还是电磁炉炒,你看到的菜单和吃到的菜不变。
1.2 数据库应用系统逻辑设计
数据库应用系统逻辑设计阶段工作内容分为三部分:
- 数据库逻辑结构设计: 从数据库的概念模型出发,设计表示为逻辑模式的数据库逻辑结构,主要内容是在 ER 图的基础上设计数据库的关系模式。包括:
- 概念模型 → 关系模式 (以 ER 图为基础)
- 模式优化(如范式、反范式)
- 外模式设计(为用户设计视图)
- 数据库事务概要设计:事务名称、事务所访问的关系表及关系属性、事务处理逻辑、事务用户(指使用、启动、调用该事务的软件模块或系统)。使用 read, write 表达存储操作
- 应用程序概要设计
1.3 数据库应用系统物理设计
物理设计阶段的主要活动有:
- 确定存储结构:如存储方式、索引、分区等
- 数据库事务详细设计:事务的实现细节,如并发控制、恢复策略
- 应用程序详细设计:逻辑转成可实现的物理方案(模块、接口、数据访问方式)
- 存取路径的选择和调整
- 确定数据存放位置和确定存储分配
1.4 数据库应用系统事务规范
事务规范包括了事务名称、事务描述、事务所访问的数据项、事务用户。
1.5 数据库应用系统的可行性分析
- 操作可行性分析: 论证是否具备DBAS开发所需的各类人员资源(项目管理人员、数据库系统分析员、应用编程人员等)、软件资源、硬件资源和工作环境等,以及为支持 DBAS 开发如何去改进加强这几方面资源。故答案为A选项。
- 经济可行性分析
- 技术可行性分析
- 开发方案可行性分析
1.6 数据库应用系统生命周期
- 项目规划:
- 确定项目的目标和范围,根据系统规划与定义的工作内容,具体说明项目的最终产品以及期望的时间、成本和质量目标
- 根据DBAS软件开发模型,分解和定义整个项目包括的工作活动和任务
- 估算完成该项目的规模及所需各种资源
- 制定合理的DBAS项目计划,包括进度、成本、质量等方面的预测和控制方案
- 需求分析
- 系统设计
- 实现与部署:(1)建立数据库结构 (2)数据加载 (3)事务和应用程序的编码及测试 (4)系统集成、测试与试运行 (5)系统部署
- 运行与维护
实施活动: 将系统从设计阶段过渡到实际运行阶段的所有工作。
1.7 螺旋模型
- 项目规划
- 目标设定:明确当前周期要达成的目标和任务。例如:在这个阶段,项目团队需要确定需要完成的功能、设计、开发任务等。
- 风险评估与规划(Risk Analysis & Planning)
- 风险识别:评估当前项目面临的潜在风险,包括技术风险、管理风险、人员风险等。团队会识别出可能导致项目失败的问题,比如技术实现困难、资源紧张、预算超支等。
- 风险评估:对于识别出的风险进行量化和优先级排序,评估它们对项目的潜在影响。
- 制定应对措施:针对每个风险,制定具体的应对策略,可能包括技术研究、原型开发、调整时间表等。
- 工程开发与测试(Engineering & Development)
- 设计与开发:根据上一阶段的目标和需求,进行系统的设计、编码和开发工作。开发阶段可能包括数据库设计、界面设计、算法实现等。
- 系统实现:包括开发的具体实施,如编写代码、实现功能模块等。
- 测试:在开发的过程中,进行不断的测试,包括单元测试、集成测试、系统测试等,确保系统的稳定性、功能性和性能符合预期。
- 客户评审与反馈(Customer Evaluation & Feedback)
- 客户评审:每个周期的末尾,开发团队会向客户展示当前阶段的成果。这可能是一个 原型,或者一个 部分功能实现,目的是获取客户的反馈,确保项目开发方向正确。
- 反馈与调整:客户对结果的反馈可能导致需求的调整或设计的修改。开发团队根据客户反馈对开发计划进行调整,为下一个周期的开发做准备。
1.8 数据库视图
视图是从一个或几个基本表(或视图)中导出的虚表,数据库中只存放了视图的定义,不存放视图对应的数据。它是原始数据库数据的一种变换,是查看表中数据的另外一种方式。可以将视图看成是一个移动的窗口,通过它可以看到感兴趣的数据。
视图是从一个或多个实际表中获得的,这些表的数据存放在数据库中。那些用于产生视图的表叫做该视图的基表。一个视图也可以从另一个视图中产生。
视图的特点体现在三个方面:
- 简单性,看到的就是需要的。视图不仅可以简化用户对数据的理解,也可以简化对它们的操作。那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件
- 安全性,通过视图用户只能查询和修改他们所能见到的数据。数据库中的其它数据则既看不见也取不到
- 逻辑数据独立性,视图可帮助用户屏蔽真实表结构变化带来的影响
根据视图的特点可以达到以下的几点作用:
- 简化数据查询语句
- 使用户从多角度看待同一数据
- 提高数据安全性
- 提供了一定程度的逻辑独立性。
1.9 DBAS的性能指标
- 数据操作响应时间(或数据访问响应时间):从提交请求到返回结果的时间
- 系统吞吐量:指系统在单位时间内所完成的事务或查询的数量,单位为TPS
- 允许并发访问的最大用户数:在保证响应时间的前提下,系统最多允许多少用户同时访问数据库;
- 每TPS代价值,用于衡量系统性价(性能价格)比的指标。
1.10 从功能角度DBAS的四个层次实现
- 表示层:负责所有与用户交互的功能,用户对数据库应用系统的最直观感受均在这层实现
- 业务逻辑层:负责根据业务逻辑需要将表示层获取的数据进行组织后,传递给数据访问层,或将数据访问层获取的数据进行相应的加工处理后,传送给表示层用于展示。
- 数据访问层:负责与DBMS系统进行交互,提取或存入应用系统所需的数据。
- **数据持久层:**负责保存和管理应用系统数据。 故答案是表示。
第二章 需求分析
2.1 数据库应用系统需求建模工具
数据库应用系统需求建模工具主要有DFD、IDEFO和UML。
1. DFD (数据流图)
数据流图(Data Flow Diagram, DFD) 是用于表示信息系统中数据流动、处理过程、存储和外部实体之间关系的图形工具。DFD 是一种非常直观、简洁的表示方式,通常用于分析和设计阶段,帮助开发者理解系统如何处理数据。DFD 图采用自顶向下逐步细化的结构化分析方法。
DFD的主要元素:
- 外部项(External Entities):系统外部的参与者或其他系统(如用户、外部设备、其他应用系统等)。通常表示为圆角矩形或平行四边形框。
- 处理(Processes):对数据进行处理的操作,通常表示为矩形框。
- 数据流(Data Flows):数据在系统内部和外部之间流动的路径,通常用箭头表示。
- 数据存储(Data Stores):系统中存储的数据,如数据库、文件等,通常表示为两个平行的线条或圆角矩形
DFD的层次:
- 层级化:DFD 通常是逐层细化的,从 上下文图(最顶层)开始,逐渐展开成 分解图。上下文图只包含外部实体和系统的边界,而分解图会详细描述系统内部的每个过程。
DFD中的箭头只表示数据流,而不是控制流或约束。
- IDEFO (功能模型)
IDEFO (Integration Definition for Function Modeling) 是一种用于表示系统功能、过程和功能之间相互关系的建模方法。IDEFO 强调系统的 功能分解 和 过程建模,常用于描述业务流程和功能需求。
IDEF0需求建模方法由箭头和矩形框两种元素构成。矩形框代表功能活动,写在矩形框内的动词短语描述功能活动的名称。
IDEFO的主要元素:
- 功能块(Function Blocks):表示功能或过程,是模型中的核心。通常用于表示系统中的一个操作或活动。
- 输入(Inputs):数据流向功能的输入。
- 输出(Outputs):功能处理后产生的结果或输出。
- 控制(Controls):约束功能或过程执行的条件或规则。
- 机制(Mechanisms):支持功能执行的资源、工具或人员。
IDEFO 图通常是层次化的,能够清晰地显示功能的层次结构。顶层表示系统的主要功能,而下层则会逐步展开,详细描述每个子功能。
- UML (统一建模语言)
UML (Unified Modeling Language) 是一种标准的图形化建模语言,用于软件系统的 需求建模、设计 和 文档化。它为开发人员、设计师和分析师提供了一套统一的工具,广泛应用于面向对象系统的建模。
UML 包括多种图示,常见的有以下几类:
- 结构图:
- 类图(Class Diagram):表示系统的类及其关系。
- 对象图(Object Diagram):表示系统在某一时刻的对象状态。
- 组件图(Component Diagram):展示系统中各个组件的结构和依赖关系。
- 部署图(Deployment Diagram):展示系统如何在硬件设备上部署。
- 行为图:
- 用例图(Use Case Diagram):描述系统的功能需求和用户交互。
- 顺序图(Sequence Diagram):展示对象之间如何通过消息进行交互。
- 活动图(Activity Diagram):描述系统的工作流或业务流程。
- 状态图(State Diagram):表示对象在生命周期中的状态变化。
- 交互图:
- 时序图(Timing Diagram):描述系统各组件在特定时间内的行为。
- 通信图(Communication Diagram):展示系统中的对象和它们之间的交互。
2.2 数据库系统信息建模方法
IDEF1X(Integration Definition for Information Modeling)是一种用于 信息建模 的标准方法,它专门用于设计 实体-关系(ER)模型,以支持数据库系统的结构化设计。IDEF1X 是 IDEF 系列方法中的一部分,主要用于 数据建模 和 数据存储设计。
IDEF1X 的核心目标:
IDEF1X 的主要目的是通过模型化系统中的 数据实体 和它们之间的 关系,帮助设计数据库系统的结构,特别是在 关系型数据库 的上下文中。
IDEF1X 的特点:
- 强制性规则:IDEF1X 对实体和关系建模有严格的规范,保证了模型的标准化,便于不同团队或系统之间的交流。
- 面向对象:它采用 面向实体 的建模方式,每个实体代表数据库中的一类对象或数据记录。
- 支持多层次建模:IDEF1X 支持层次化的建模,能够表示不同粒度的设计内容,从总体设计到细节设计。
IDEF1X 的基本构成元素:
- 实体(Entity):
- 实体表示一个数据对象或概念,通常用矩形框表示。
- 实体具有属性,每个属性可以是 标识符 或 普通属性。
- 每个实体可以有一个 主键(Primary Key),用于唯一标识实体的实例。
- 属性(Attributes):
- 属性是实体的特征,用于描述实体的状态或性质。属性通常由 名称 和 数据类型 组成。
- 标识符属性(通常是主键)用于唯一标识实体中的每个实例。
- 普通属性用于描述实体的非唯一特征。
- 主属性是包含在任何一个候选码中的属性(并集)。
- 不包含在任何候选码中的属性称为非主(非码)属性。
- 关系(Relationships):
- 关系表示实体之间的关联,关系通常用线连接两个或多个实体。
- 每个关系有 基数(Cardinality),表示一个实体与另一个实体之间的联系,如“一对多”或“多对多”。
- 关系还可以通过 参与度(Participation)来表示,指实体是否必须参与关系。
- 基数(Cardinality):
- 基数表示实体之间关系的数量限制。常见的基数类型有:
- 一对一(1:1)
- 一对多(1
)→ 非标定型联系(不会产生从属实体集) - 多对多(M
) → 非确定联系(多对多联系可以通过引入一个第3个实体集来转化为两个一对多联系,这样可以通过辅助实体来表示这两个实体集之间的多对多关系。对于这两个一对多联系,它们属于标定型联系)
- 基数帮助确定实体之间的约束条件。
- 基数表示实体之间关系的数量限制。常见的基数类型有:
- 参与度(Participation):
- 完全参与(Total Participation):实体必须参与关系。
- 部分参与(Partial Participation):实体可选择是否参与关系。
IDEF1X 中的符号和表示方式:
- 矩形:表示 实体。
- 实体用实体名/实体号标识
- 独立实体用矩形框,从属实体用圆角矩形框表示
- 独立实体的主关键字没有外键,从属实体的主关键字含有外键
- 从属实体的实例依赖于独立实体实例存在而存在
- 椭圆:表示 属性。
- 菱形:表示 关系(用于表示实体之间的联系)。
- 线条:表示 实体、关系 和 属性 之间的连接。标定型联系用一条实线或者线段把他们连接起来
- 基数标注:通常位于连接关系和实体的线条两端,用于指示基数(如 “1”、“N” 或 “M”)。
IDEF1X 的建模规则:
- 每个实体必须有一个唯一标识符(主键),用于唯一标识该实体的实例。
- 每个实体之间的关系必须通过基数表示清楚,并确保所有关系的约束都符合业务需求。
- 每个实体只能有一个主键,并且主键只能由属性来组成。
- 关系必须有清晰的方向性和基数,这有助于理解实体之间的交互。
2.3 数据字典
数据字典(Data Dictionary)是数据库管理系统中用于存储关于数据库结构、约束、关系、数据类型等元数据(metadata)的重要工具。它提供了关于数据定义和数据存取的详细描述,并确保数据的一致性、完整性和规范性。
数据字典的主要作用:
- 元数据存储:
- 数据字典存储了有关数据库中所有数据元素的 定义 和 描述,例如表结构、字段类型、字段约束、索引、视图等。
- 提供数据访问信息:
- 它为开发者、管理员以及数据库管理系统(DBMS)提供了如何访问和操作数据的信息,包括字段名称、数据类型、默认值、索引等。
- 支持数据完整性和一致性:
- 通过对数据约束(如主键、外键、唯一性约束等)的描述,数据字典帮助确保数据的一致性和完整性。
- 增强数据库管理:
- 数据字典为数据库管理员提供了一个集中的视图,帮助他们管理数据库结构、监控和优化数据库。
数据字典的组成内容:
数据字典包含了数据库的多种元数据,通常包括以下几种信息:
- 表的结构:
- 包括每个表的名称、字段(列)名称、数据类型、长度、允许的空值(null)等。
- 字段属性:
- 字段名称、数据类型(如整数、字符、日期等)、默认值、是否为空、主键、外键、唯一性等。
- 关系和约束:
- 定义表之间的 关系(如外键约束)、约束条件(如主键、唯一约束、检查约束等)。
- 索引:
- 包括索引的名称、字段、索引类型(如唯一索引、普通索引、复合索引等)。
- 视图和存储过程:
- 记录数据库中视图(视图名称、所依赖的表、字段等)和存储过程(名称、参数、返回类型等)的信息。
- 触发器(Triggers):
- 记录触发器的定义,包括触发条件、执行操作等。
- 数据访问权限:
- 存储有关用户、角色及其在数据库中可以执行的操作(如SELECT、INSERT、UPDATE、DELETE等)的信息。
数据字典的类型:
- 系统数据字典:
- 由数据库管理系统自动生成和维护,用于记录数据库结构和对象(表、视图、存储过程等)的元数据。系统数据字典通常是隐藏的,普通用户无法直接访问。
- 用户数据字典:
- 由开发人员或数据库管理员为特定的应用程序或项目手动创建,描述项目相关的数据对象和结构。用户数据字典通常是公开的,可以为开发和维护提供参考。
数据字典的管理和使用:
- 维护:
- 数据字典通常由 数据库管理员(DBA)负责维护。在数据库结构变更时(如添加新表、修改字段、添加索引等),数据字典会进行更新。
- 查询和生成:
- 用户可以通过查询数据库的系统数据字典来获取元数据(例如在 SQL Server 中,可以查询
INFORMATION_SCHEMA视图)。很多数据库管理系统(如 MySQL、PostgreSQL)提供了查看数据字典的内置工具。
- 用户可以通过查询数据库的系统数据字典来获取元数据(例如在 SQL Server 中,可以查询
- 文档化:
- 在大型项目中,用户数据字典经常作为项目文档的一部分,用于描述数据库的设计和使用方法。它帮助开发人员理解数据的结构、关系以及如何有效地使用数据库。
数据字典的优点:
- 规范化数据结构:
- 通过数据字典的定义,可以确保数据库结构的一致性和规范性,减少因数据误用或不一致带来的错误。
- 提高数据库管理效率:
- 数据字典提供了一个中心化的信息存储,使得开发者和管理员能够快速访问和理解数据库的结构。
- 促进协作和沟通:
- 数据字典是团队成员之间沟通的桥梁,有助于开发者、DBA 和业务分析师之间的协作。
- 自动化和文档化支持:
- 数据字典可以自动生成文档,减少手动文档编写的工作量,提高工作效率。
2.4 实体之间关系
- 标定型联系(Identifying Relationship)
定义:
标定型联系是指子实体(弱实体)的主键部分或全部依赖于父实体的主键。也就是说,子实体无法独立存在,必须依赖于父实体才能被唯一标识。
特点:
- 子实体是弱实体(Weak Entity)。
- 子实体的主键由父实体的主键 + 自身的部分属性组成。
- 在ER图中通常用双线矩形表示弱实体,用双线菱形或带实心菱形的连线表示标定型联系。
举例:
- 实体:
学生(父实体),学生证(子实体)- 一个学生可以有一张学生证,学生证不能脱离学生存在。
- 学生证的主键可能包含学生的学号(来自“学生”)+ 发证年份。
- 因此,“学生”与“学生证”之间的联系是标定型联系。
✅ 关键点:子实体的主键包含父实体的主键 → 标定型联系。
- 非标定型联系(Non-Identifying Relationship)
定义:
非标定型联系是指子实体的主键不依赖于父实体的主键。子实体是强实体,可以独立存在,只是逻辑上与另一个实体有关联。
特点:
- 子实体是强实体(Strong Entity)。
- 联系通过外键实现,但外键不构成主键的一部分。
- 在ER图中用单线连接表示。
- 不会产生从属实体集。
- ER图中的一对多关系用 1
表示,UML 类图中用 1:* 表示
举例:
- 实体:
客户和订单- 每个订单属于一个客户,但订单有自己独立的主键(如订单号)。
- 订单表中包含客户ID作为外键,但订单号本身就能唯一标识订单。
- 因此,“客户”与“订单”之间是非标定型联系。
✅ 关键点:外键存在,但不参与主键 → 非标定型联系。
子类型:可选 vs 必选
- 可选非标定型联系:子实体可以没有父实体(外键可为空)。
- 例如:员工可以暂时不分配部门(部门ID可为空)。
- 必选非标定型联系:子实体必须关联一个父实体(外键不可为空)。
- 非确定联系(Non-Deterministic Relationship) & 多对多关系(Many-to-Many)
- 两个实体之间没有明确的一对一或一对多映射。
- 例如:
学生↔课程,一个学生可选多门课,一门课可被多个学生选。 - 这种关系在数据库中需要通过中间关联表来实现。
- 因为无法通过一方确定另一方,所以有人称其为“非确定”。
总结对比表:
| 特性 | 标定型联系 | 非标定型联系 | 非确定联系(非标准) |
|---|---|---|---|
| 是否依赖父实体主键 | 是(主键组成部分) | 否(仅外键) | 不明确 |
| 子实体类型 | 弱实体 | 强实体 | 视情况而定 |
| ER图表示 | 双线矩形 + 实心菱形 | 单线连接 | 无标准表示 |
| 示例 | 学生 ↔ 学生证 | 客户 ↔ 订单 | 学生 ↔ 课程(多对多) |
| 是否标准术语 | 是 | 是 | 否(慎用) |
第三章 数据库结构设计
3.1 无损连接&保持函数依赖
对模式进行分解时,既要保证分解具有”无损连接性”,又要保证分解“保持函数依赖”。所谓”无损连接”指分解的若干连接重组时可以精确恢复到原来的数据表,数据记录既没有增加也没有减少。“保持函数依赖”是指原关系模式含有的属性之间的隐含关系在分解后不能丢失。
3.2 数据模型三要素
- 数据结构:数据结构是所研究的对象类型的集合。它从语法角度表述了客观世界中数据对象本身的结构和数据对象之间的关联关系,是对系统静态特征的描述。
- 数据操作:数据操作是对数据库中对象的实例允许执行的操作的集合,主要指检索和更新(插入、删除、修改)两类操作。数据模型必须定义这些操作的确切含义、操作符号、操作规则(如优先级)以及实现操作的语言。数据操作是对系统动态特性的描述。
- 完整性约束:数据完整性约束是一组完整性规则的集合,规定数据库状态及状态变化所应满足的条件,以保证数据的正确性、有效性和相容性。
3.3 ER 图转换为实体关系
在ER图中,将实体和联系转换成关系模式的规则是:
- 每一个实体集转换为一个关系模式
- 一对一、一对多联系中一端并入多端的实体关系模式中
- 每一个多对多联系转换成一个关系模式。
3.4 数据范式
- 第一范式(1NF):
- 1NF 要求所有属性的值都是 原子值,即每个字段只包含一个值,不能有重复的记录或集合值。
- 第二范式(2NF):
- 2NF 要求 R 必须先满足 1NF,且 R 中的每一个非主属性必须完全依赖于 主键(即不存在部分依赖)。
- 第三范式(3NF):
- 3NF 要求 R 必须先满足 2NF,且每个非主属性必须直接依赖于 主键(即不存在传递依赖)。
- 博茨-科得范式(BCNF):
- BCNF 要求关系模式中的每一个非平凡的函数依赖 X → Y,其中 X 必须是 候选键。
3.5 完整性约束
在数据库中,完整性约束可以根据其作用范围分为三个层次:
| 级别 | 作用对象 | 说明 |
|---|---|---|
| 列级约束(Column-level) | 单个列 | 作用于某一列,如 NOT NULL、DEFAULT、CHECK(列上)等 |
| 元组级约束(Tuple-level / Row-level) | 单行(一条记录) | 作用于一个元组内的多个列,如 CHECK (salary > 0 AND dept = 'HR') |
| 关系级约束(Relation-level) | 整个关系(表) | 作用于整个表,涉及多行之间的逻辑关系,如 外键引用、断言、触发器、主键等 |
⚠️ 特别注意:关系级约束 是指跨越多行或多表的约束,比如:
- “每个部门最多有10名员工” → 涉及多行聚合,不能用简单的列或元组约束表达
- 外键约束虽然通常写在建表时,但它本质上是表与表之间的引用完整性约束,属于关系级
3.6 数据分布设计
数据分布设计任务主要体现在以下几个方面:
- 不同类型数据的物理分布:当系统采用 RAID 等多磁盘存储系统时,可以将基本表和建立在表上的索引分别放在不同的磁盘上。
- 应用数据的划分与分布:DDBS 采用水平划分或垂直划分两种方法。
- 派生属性数据分布
- 关系模式的去规范化:
- 根据实际需要对数据库中某些 3NF、BCNF 模式考虑是否可以降低其规范化程度,以提高查询效率,这种为关系模式的去规范化处理。
3.7 死锁
在进行DBAS系统设计时,通常为了避免死锁,可以考虑以下原则:
- 提高事务隔离性级别
- 按同一顺序访问资源
- 采用小事务模式,尽量缩短事务的长度,减少占有锁的时间
- 尽量使用记录级别的锁(行锁),少用表级别的锁
3.8 数据库文件组织结构
| 文件类型 | 特点 | 适用场景 |
|---|---|---|
| 堆文件(Heap File) | 数据无序存储,插入快,全表扫描效率尚可,更新/删除通过标记实现 | 小表、频繁插入/更新、全表扫描为主 |
| 顺序文件(Sorted File) | 记录按某个字段排序存储 | 范围查询多、有序输出,但插入/更新代价高(需重排) |
| 散列文件(Hash File) | 基于哈希函数定位记录,等值查询极快 | 精确查找(如主键查询),不支持范围扫描或全表扫描优化 |
| 聚集文件(Clustered File) | 将多个表的相关记录物理上存储在一起(如父-子记录) | 多表连接频繁,非单表场景 |
3.9 数据库索引类型
3.9.1 按存储结构和组织方式分类
- B+树索引(B+ Tree Index)
- 最常见、最广泛使用的索引类型,默认用于大多数关系型数据库(如 MySQL InnoDB、Oracle、SQL Server)。
- 特点:
- 支持等值查询、范围查询(如
BETWEEN,>,<)、排序(ORDER BY) - 树结构平衡,查询效率稳定(O(log n))
- 叶子节点形成链表,便于范围扫描
- 支持等值查询、范围查询(如
- 适用场景:
- 主键、外键、频繁查询的列
- 范围条件查询(如“工资在 5000~10000 之间”)
- 哈希索引(Hash Index)
- 基于哈希表实现,仅支持等值查询(
=),不支持范围查询或排序。 - 特点:
- 查找速度极快(O(1) 平均情况)
- 不支持
>、<、BETWEEN、LIKE(非前缀匹配)等操作 - 哈希冲突会影响性能
- 通常为内存表使用(如 MySQL 的 MEMORY 引擎)
- 适用场景:
- 精确匹配查询,如用户登录(
WHERE username = 'alice') - 缓存类数据、临时表
- 精确匹配查询,如用户登录(
3. 聚集索引(Clustered Index)
- 数据行的物理存储顺序与索引顺序一致
- 一个表只能有一个聚集索引
- 聚集索引的键值可以重复,可以是复合索引(多列)
- 优点:
- 范围查询快(数据物理连续)
- 主键查询效率极高
- 缺点:
- 插入可能需重排数据(页分裂)
- 非主键作为聚集索引时,其他索引会变大(二级索引包含主键值)
✅ 建议:选择单调递增的列(如自增ID)作为聚集键,减少页分裂
- 非聚集索引 / 二级索引(Non-Clustered Index / Secondary Index)
- 索引结构与数据行物理存储分离
- 索引中存储的是索引列列值 + 相对应的指针
- 一个表可以有多个非聚集索引
- 查询时先查索引,再回表(回表查询)获取完整数据
- 优化:覆盖索引可避免回表
- 覆盖索引(Covering Index)
- 不是一种独立索引类型,而是一种使用方式
- 指查询所需的所有列都包含在索引中,无需回表
- 显著提升性能
- 本质是复合索引的高效利用
3.9.2 按列的数量分类
1. 单列索引(Single-Column Index)
- 在一个列上创建的索引
- 简单直接,适合单条件查询
- 复合索引 / 联合索引(Composite Index)
- 在多个列上创建的索引
- 遵循最左前缀原则(Leftmost Prefix Rule),高选择性列放前面,低选择性列放后面(选择性 = 1- 重复率)
- 适合多条件查询
3.9.3 特殊用途索引
- 唯一索引(Unique Index)
- 确保索引列的值不重复
- 可用于主键或业务唯一键(如身份证号、邮箱)
- 插入重复值会失败
- 可以有多个唯一索引
- 主索引(Primary Index)
- 通常指在主键上建立的索引
- 自动是唯一索引,通常是聚集索引(但不是必须)
- 用于快速定位主键对应记录
⚠️ 注意:主索引 ≠ 聚集索引(可在非主键上建聚集索引)
- 全文索引(Full-Text Index)
- 用于文本内容的关键词搜索
- 支持自然语言搜索、布尔搜索
- 常用于文章、评论等大文本字段
- MySQL 使用
MATCH() ... AGAINST()语法
- 空间索引(Spatial Index)
- 用于地理空间数据(如经纬度、多边形)
- 支持 GIS 查询(如“附近10公里内的店铺”)
- MySQL 使用
MyISAM或InnoDB支持空间索引
- 位图索引(Bitmap Index)
- 适用于低基数列(如性别、状态码)
- 每个值对应一个位图,1 表示存在,0 表示不存在
- 在数据仓库中常见(如 Oracle)
- 不适用于高并发 OLTP 系统(更新代价高)
✅ 优点:多条件 AND/OR 查询极快
3.9.4 稠密索引 和 稀疏索引
🔹 一、基本概念对比
| 特性 | 稠密索引(Dense Index) | 稀疏索引(Sparse Index) |
|---|---|---|
| 每条记录是否都有索引项? | ✅ 是,每条数据记录都有一个索引项 | ❌ 否,只对某些数据块或关键点建立索引 |
| 索引大小 | 大(与数据量成正比) | 小(远小于数据量) |
| 存储开销 | 高 | 低 |
| 查询效率 | 高(直接定位) | 稍低(需块内扫描) |
| 是否要求数据有序? | 不强制,但通常有序 | ✅ 必须有序(按索引键排序) |
🔹 二、稠密索引(Dense Index)
定义:
每一条数据记录在主文件中都有一个对应的索引项。
结构特点:
- 索引项格式:
(键值, 指针)→ 指向数据文件中的具体记录 - 所有键值(包括重复值)都出现在索引中
- 通常也按键值有序排列
示例:
假设有一个按 ID 排序的学生表:
| ID | 姓名 |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
稠密索引:
| 键值(ID) | 指针(指向记录) |
|---|---|
| 1 | 指向第1条记录 |
| 2 | 指向第2条记录 |
| 3 | 指向第3条记录 |
每条记录都有一个索引项。
优点:
- 查询速度快:给定键值,可直接通过二分查找定位索引项,然后跳转到数据记录
- 支持快速查找、范围扫描
缺点:
- 存储空间大:索引大小与数据量成正比
- 插入/删除时维护成本高
适用场景:
- 数据量不大但查询频繁
- 需要极高查询性能的场景
- 二级索引(如非聚集索引)通常是稠密的
三、稀疏索引(Sparse Index)
定义:
只为数据文件中的每个数据块(或页)的第一个记录(锚点) 建立索引项,而不是每条记录。
结构特点:
- 索引项指向的是数据块的起始位置
- 要求数据文件必须按索引键有序存储
- 查找时先找到“目标块”,然后在块内顺序扫描
示例:
学生表(按 ID 有序存储),每块存 2 条记录:
数据文件(有序):
| 块1 | 块2 |
|---|---|
| (1, Alice) | (3, Carol) |
| (2, Bob) | (4, David) |
稀疏索引:
| 键值(ID) | 指针(指向块) |
|---|---|
| 1 | 指向块1 |
| 3 | 指向块2 |
注意:只记录每个块的最小键值(起始键)
查询过程(查找 ID=4):
- 在索引中找 ≤4 的最大起始键 → 找到
3(指向块2) - 进入块2,顺序扫描:
3 → 4找到目标
优点:
- 索引小:节省存储空间
- 维护成本低
- 适合大数据量场景
缺点:
- 必须要求数据有序(否则无法确定块的范围)
- 查询需要块内顺序扫描,速度略慢
- 不能直接定位到具体记录
适用场景:
- 大型有序文件
- 聚集索引的底层实现常结合稀疏索引思想
- 数据仓库、日志系统等
四、关键区别总结
| 对比项 | 稠密索引 | 稀疏索引 |
|---|---|---|
| 是否每条记录都有索引项 | ✅ 是 | ❌ 否(每块一个) |
| 数据是否必须有序 | ❌ 不强制(但通常有序) | ✅ 必须有序 |
| 空间开销 | 高 | 低 |
| 查询速度 | 快(直接定位) | 稍慢(需块内扫描) |
| 插入/删除维护成本 | 高 | 较低 |
| 典型应用 | 二级索引、哈希索引 | 聚集索引、B+树叶子节点前的层级 |
3.9.4 其他高级索引
| 索引类型 | 说明 |
|---|---|
| 函数索引(Function-Based Index) | 基于表达式或函数创建,如 UPPER(name),Oracle/PostgreSQL 支持 |
| 部分索引(Partial Index) | 只对满足条件的数据建索引,如 CREATE INDEX ... WHERE status = 'active'(PostgreSQL) |
| 倒排索引(Inverted Index) | 全文检索基础,ES、Lucene 使用 |
| 自适应哈希索引(Adaptive Hash Index) | InnoDB 内部自动创建,加速等值查询 |
3.9.5 索引选择建议
| 场景 | 推荐索引类型 |
|---|---|
| 主键查询 | 聚集索引 + B+树 |
| 精确匹配 | 哈希索引(内存表)或 B+树 |
| 范围查询 | B+树索引 |
| 多条件查询 | 复合索引(注意最左前缀) |
| 文本搜索 | 全文索引 |
| 低基数列(如状态) | 位图索引(数据仓库) |
| 减少回表 | 覆盖索引 |
| 高频更新小表 | 可能无需索引(堆文件扫描更快) |
3.9.6 SQL 中的索引实现方式
SQL Server 中根据索引的实现方式,索引技术可以分成有序索引和散列索引两大类。
3.10 事物操作原语
| 原语 | 对应的 SQL 操作 | 说明 |
|---|---|---|
| READ | SELECT | 表示从数据库中读取数据 |
| WRITE | UPDATE | 表示修改已有数据 |
| INSERT | INSERT | 表示插入新数据 |
| DELETE | DELETE | 表示删除数据 |
第四章 数据库应用系统功能设计与实施
4.1 三层 B/S 结构
三层 B/S(Browser/Server)结构是典型的 Web 应用架构,分为:
- 表现层(客户端):浏览器(如 Chrome、Firefox)
- 业务逻辑层(Web 应用服务器):如 Tomcat、Nginx、Node.js 等,处理业务逻辑
- 数据层(数据库服务器):如 MySQL、Oracle,负责数据存储与操作
4.2 封锁粒度
封锁粒度是指事务加锁的数据单位大小,常见的有:
| 粒度 | 说明 | 特点 |
|---|---|---|
| 行级锁 | 锁单行 | 粒度细,并发高,开销大 |
| 页级锁 | 锁一页(含多行) | 中等 |
| 表级锁 | 锁整张表 | 粒度粗,开销小,并发低 |
📌 什么是“提高封锁粒度”?
- “提高” = 变粗
- 例如:从行锁 → 页锁 → 表锁
- 即:使用更大范围的锁
封锁开销包括:
- 锁管理器的内存消耗
- 加锁/解锁的操作次数
- 锁表的维护成本
粒度越粗封锁开销越小
4.3 原型迭代法
- 快速构建低保真原型(草图、线框图)
- 与用户讨论、获取反馈
- 修改原型
- 逐步迭代到高保真设计
- 最后再进行详细设计和开发
4.4 业务逻辑层概要设计
业务逻辑层概要设计的原则主要体现在以下几个方面:
- 构件本身应由相关性很强的代码组成,一个构件或一个模块只负责完成一项任务,也就是常说的单一责任原则
- 组成系统业务逻辑层的各个构件应具备独立的功能,并且最大限度地减少与其他构件功能重叠
- 构件之间的接口应尽量简单明确
- 如果某两个构件间的关系比较复杂,应考虑进一步进行模块划分
- 如果构件过于复杂,可以将其细分。
4.5 数据库应用系统总体设计
在数据库应用系统开发过程中,总体设计(也称概要设计)阶段的主要任务是:
在需求分析和概念设计的基础上,进行系统的宏观架构设计,确定系统的技术路线、结构、资源配置等。
系统总体设计的主要内容包括确定 DBAS 体系结构、软硬件选型和配置设计、应用软件总体设计和业务规则初步设计。
4.6 事务的 ACID 四大性质
| 性质 | 含义 | 作用 |
|---|---|---|
| 原子性(Atomicity) | 事务要么全部执行,要么全部不执行 | 保证操作的完整性 |
| 一致性(Consistency) | 事务执行前后,数据库从一个一致状态变到另一个一致状态(满足约束) | 保证业务规则不被破坏 |
| 隔离性(Isolation) | 并发事务之间互不干扰 | 防止脏读、不可重复读、幻读 |
| 持久性(Durability) | 一旦事务提交,其结果永久保存,即使系统故障也不丢失 | 保证数据不因崩溃而丢失 |
4.7 两阶段加锁协议
两阶段加锁协议是数据库中用于控制并发事务的一种封锁协议,它规定事务的加锁和解锁行为分为两个阶段:
- 增长阶段(Growing Phase)
- 事务可以不断申请锁(读锁、写锁)
- 但不能释放任何锁
- 收缩阶段(Shrinking Phase)
- 事务可以释放锁
- 但不能再申请新的锁
🔁 一旦开始释放锁,就不能再加锁
两阶段加锁协议的作用
- 能够保证事务调度的 可串行化(Serializable)
- 即:并发执行的结果,等价于某个串行执行顺序的结果
- 这是并发控制的最高一致性目标
4.8 类的 UML 表示
- 类的 UML 表示(空心三角实线连接),指的是子类对父类关系的继承
- 接口的 UML 表示(空心三角虚线连接),与子类继承比较相似,区别主要在于多继承上
- 聚合关系的 UML 表示(空心菱形实线连接),主要表示一种弱的拥有关系,如A对象可以包含B对象,但B对象不是A对象的一部分
- 以及合成关系的 UML 表示(实心菱形实线连接),是一种强拥有,体现了严格的部分和整体关系。
第五章 UML与数据库应用系统
5.1 UML 的 14 种图
🌟 UML 的 14 种图(按 UML 2.x 标准)
UML 图分为两大类:
| 类别 | 说明 | 包含的图 |
|---|---|---|
| 结构图(Structural Diagrams) | 描述系统的静态结构,如类、对象、组件等 | 7 种 |
| 行为图(Behavioral Diagrams) | 描述系统的动态行为,如交互、状态变化、流程等 | 7 种 |
5.1.1 结构图(静态建模)
- 类图(Class Diagram)
- 用途:描述系统中的类、接口、属性、方法以及它们之间的关系(继承、关联、聚合、组合等)
- 应用场景:面向对象设计、数据库建模、系统架构设计
- ✅ 最常用、最核心的 UML 图
示例:学生 类与 课程 类之间的“选课”关联
- 对象图(Object Diagram)
- 用途:展示某一时刻系统中对象实例及其关系
- 是类图的实例化版本
- 用于说明复杂类图在特定场景下的具体状态
示例:学生A 和 课程X 的具体关联实例
- 组件图(Component Diagram)
- 用途:描述系统的物理组件及其依赖关系
- 组件可以是:可执行文件、库、Web 服务、模块等
- 用于构建、部署和维护系统
示例:用户界面.dll → 依赖 → 业务逻辑.dll
- 组合结构图(Composite Structure Diagram)
- 用途:展示一个类或组件的内部结构,以及其内部部分如何协作
- 常用于复杂类、代理模式、适配器模式等设计
示例:窗口 由 按钮、文本框 等部分组成
- 部署图(Deployment Diagram)
- 用途:描述系统的物理部署结构,如服务器、网络、设备、组件部署位置
- 用于系统运维、分布式系统设计
示例:Web 服务器部署在云主机,数据库在本地机房
- 包图(Package Diagram)
- 用途:将模型元素组织成包(Package),类似于文件夹或命名空间,表示包与包之间关系的类图
- 用于管理大型系统的模块划分
示例:com.company.user、com.company.order
- 制品图(Artifact Diagram)
- 用途:描述系统中的物理文件,如源代码、可执行文件、配置文件、JAR 包等
- 与部署图配合使用
示例:app.jar、config.xml
5.1.2 行为图(动态建模)
- 用例图
- 用途:描述系统外部用户(参与者) 与系统之间的交互功能
- 展示“谁用系统”、“能做什么”
- 是需求分析阶段的核心工具
示例:用户 → 登录、管理员 → 删除用户
- 顺序图
- 用途:描述对象之间消息传递的时间顺序
- 强调时序性,显示谁先调用谁
- 包含生命线(纵向/垂直的虚线,表示时间的持续过程)、激活条、消息箭头、对象(顶部的矩形框)
示例:用户登录时,界面 → 服务 → 数据库 的调用顺序
- 通信图/协作图
- 用途:描述对象之间的组织结构和消息传递
- 与顺序图类似,但更强调对象间的链接关系
- 消息用编号表示顺序
适合展示复杂对象协作
- 状态图
- 用途:描述一个对象在其生命周期内的**状态变化,**状态之间的转移是由事件驱动的
- 显示状态、事件、动作、转移
- 用于建模具有复杂状态的实体(如订单、电梯、状态机)
示例:订单:新建 → 支付 → 发货 → 完成
- 活动图
- 用途:描述业务流程或操作的控制流,陈述活动之间的流程控制转移,用于描述单个用例细节工作流程
- 类似于流程图,支持分支、并行、循环
- 可用于建模算法、工作流、业务规则
示例:用户注册流程:输入 → 验证 → 发邮件 → 成功
- 时序图
- 用途:描述对象状态或行为随时间变化的细节
- 强调时间约束和持续时间
- 常用于实时系统、嵌入式系统
示例:信号灯在不同时间段的状态变化
- 交互概览图
- 用途:将多个交互图(如顺序图、活动图)组合成一个高层流程
- 是“活动图”和“顺序图”的混合体
- 用于描述复杂的控制流程
示例:登录流程包含“输入验证”、“调用服务”、“返回结果”等子流程
📊 总结对比表
| 图类型 | 类别 | 主要用途 | 典型场景 |
|---|---|---|---|
| 类图 | 结构 | 类与关系 | 系统设计 |
| 对象图 | 结构 | 实例关系 | 某一时刻状态 |
| 组件图 | 结构 | 模块依赖 | 系统架构 |
| 部署图 | 结构 | 物理部署 | 运维、云部署 |
| 用例图 | 行为 | 功能需求 | 需求分析 |
| 顺序图 | 行为 | 时序交互 | 接口调用 |
| 通信图 | 行为 | 对象协作 | 复杂交互 |
| 状态图 | 行为 | 状态变迁 | 订单、状态机 |
| 活动图 | 行为 | 流程控制 | 业务流程 |
| 包图 | 结构 | 模块划分 | 代码组织 |
5.2 UML 用例模型
5.2.1 什么是用例模型?
用例模型 = 用例图 + 用例描述
- 用例模型由用例、角色和系统三部分组成
- 用例图(Use Case Diagram):图形化展示参与者、用例及它们之间的关系
- 用例描述(Use Case Specification):文字详细描述每个用例的流程、前置条件、后置条件、异常等
- 它描述的是系统外部功能需求,即:
- 谁(参与者) 使用系统
- 做什么(用例)
- 它不描述系统内部结构,无论是静态还是动态
- 用例是需求视角,表达系统的功能需求,不是系统内部实现视角
5.2.2 核心元素
- 参与者(Actor)
- 表示与系统交互的外部实体
- 可以是人、其他系统、硬件设备等
- 用小人图标表示
- 位于系统边界之外
示例:用户、管理员、支付网关、定时任务
- 用例(Use Case)
- 表示系统为参与者提供的一个完整功能
- 用椭圆或圆角矩形表示
- 位于系统边界之内
示例:登录、注册、下单、查询余额、导出报表
✅ 一个用例应:
- 代表一个完整业务目标
- 从用户角度出发(不是技术实现)
- 有明确的开始和结束
- 系统边界(System Boundary)
- 用一个矩形框表示系统范围
- 用例画在框内,参与者画在框外
- 用于区分“系统内功能”和“外部角色”
4. 关系(Relationships)
(1)使用关系
- 参与者与用例之间的连线
- 表示“谁使用哪个功能”
示例:用户 —— 登录
(2)组合关系
- 表示一个用例必须包含另一个用例的功能
- 用虚线箭头 +
<<include>>标注 - 是强制性调用
示例:
- “下单” 必须包含 “验证库存”
- “登录” 被多个用例包含
✅ 用于提取公共功能,避免重复
(3)扩展关系
- 表示一个用例在特定条件下扩展另一个用例
- 用虚线箭头 +
<<extend>>标注 - 是可选性调用
示例:
- 基础用例:“支付”
- 扩展用例:“使用优惠券”(仅当用户有券时触发)
✅ 用于描述可选行为或异常处理
(4)泛化关系(Generalization)
- 表示用例或参与者之间的继承关系
- 用空心三角箭头表示
- 子用例继承父用例的行为并可扩展
示例:
- 父用例:“支付”
- 子用例:“微信支付”、“支付宝支付”
5.2.3 用例图示例
+-----------------------+ | 网上购物系统 | | | | +-------------+ | | | 登录 |<----|---- (用户) | +-------------+ | | ▲ | | | <<include>> | | +-------------+ | | | 验证用户身份 | | | +-------------+ | | ▲ | | | <<extend>> | | +-------------+ | | | 发送验证码 | | | +-------------+ | +-----------------------+5.2.4 用例描述
用例图是骨架,用例描述是血肉。一个完整的用例描述包括:
| 项目 | 说明 |
|---|---|
| 用例名称 | 如“用户登录” |
| 参与者 | 如“注册用户” |
| 前置条件 | 执行前系统必须满足的条件,如“用户已注册” |
| 后置条件 | 执行后系统状态,如“用户处于登录状态” |
| 主事件流(基本路径) | 正常情况下的步骤,如“输入用户名密码 → 点击登录 → 进入主页” |
| 备选事件流(扩展路径) | 异常或分支情况,如“密码错误 → 提示重试” |
| 异常处理 | 如“系统超时 → 返回错误页面” |
| 业务规则 | 如“密码长度 ≥ 6” |
✍️ 示例(简化版):
用例名称:用户登录参与者:注册用户前置条件:用户已注册,系统运行正常后置条件:用户登录成功,进入个人主页
主事件流:1. 用户打开登录页面2. 输入用户名和密码3. 点击“登录”按钮4. 系统验证身份5. 跳转到个人主页
备选事件流:2a. 用户名不存在 → 提示“用户不存在”3a. 密码错误 → 提示“密码错误,请重试”4a. 系统异常 → 显示“系统繁忙,请稍后重试”5.2.5 用例模型的作用
| 作用 | 说明 |
|---|---|
| ✅ 需求捕获 | 从用户角度明确系统功能 |
| ✅ 沟通桥梁 | 便于开发、测试、客户理解需求 |
| ✅ 功能分解 | 通过包含/扩展关系组织复杂功能 |
| ✅ 测试依据 | 用例可直接转化为测试用例 |
| ✅ 项目估算 | 用例数量和复杂度可用于工作量估算 |
5.3 UML 建模概念架构
UML 建模概念架构中包括元元模型层、元模型层、模型层和用户模型层四个层次。
- 元元模型层。组成了 UML 的最基本的元素“事务”,代表要定义的所有事物。它是UML体系中最高的抽象层级
- 元模型层。组成了 UML 的基本元素,包括面向对象和面向组件的概念。这一层的每个概念都是元元模型中“事物”概念的实例
- 模型层。组成了 UML 的模型,这一层中的每个概念都是元模型层中概念的一个实例,这一层的模型通常叫作类模型或类型模型
- 用户模型层。层中的所有元素都是UML模型的实例。这层中的每个概念都是模型层的一个实例(通过分类)也是元模型层的一个实例。这一层的模型通常叫作对象模型或实例模型。
5.4 UML 中的四种关系
| 关系类型 | 含义 | 示例 | 图形表示 |
|---|---|---|---|
| 关联 | 两个类之间有结构或使用关系(“has-a”或“uses-a”) | 学生选课、教师教课 | 实线 |
| 依赖 | 一个类临时使用另一个类(如方法参数) | 类A调用类B的某个方法 | 虚线箭头 |
| 聚集 | “整体-部分”关系,部分可独立存在 | 汽车和轮胎 | 空心菱形 + 实线 |
| 概括 | “is-a”继承关系,子类继承父类 | 狗 → 动物,电话售票员 → 售票员 | 空心三角箭头 |
第六章 高级数据查询
6.1 WITH TIES
用于在排序分页或取前 N 行时,包含并列(相同排序值)的记录。
6.1.1 基本语法(SQL Server)
SELECT TOP (n) [WITH TIES] 列名FROM 表名ORDER BY 排序列 [ASC|DESC]⚠️ 注意:必须配合 ORDER BY 使用,否则报错。
6.1.2 作用解释
TOP (n):返回前 n 行TOP (n) WITH TIES:返回前 n 行,以及所有与第 n 行排序值相同的行
→ 即:“宁可多,不可漏” —— 不让并列者被遗漏
6.1.3 举个例子说明
假设有一个学生成绩表:
CREATE TABLE Scores ( StudentID INT, Name VARCHAR(50), Score INT);
INSERT INTO Scores VALUES(1, 'Alice', 95),(2, 'Bob', 90),(3, 'Carol', 90),(4, 'David', 85),(5, 'Eve', 90),(6, 'Frank', 80);我们想查询成绩最高的前 2 名学生
❌ 不使用 WITH TIES
SELECT TOP 2 Name, ScoreFROM ScoresORDER BY Score DESC;结果(可能):
Name | Score-------|------Alice | 95Bob | 90→ Carol 和 Eve 也是 90 分,但被排除了!
✅ 使用 WITH TIES
SELECT TOP 2 WITH TIES Name, ScoreFROM ScoresORDER BY Score DESC;结果:
Name | Score-------|------Alice | 95Bob | 90Carol | 90Eve | 90→ 因为第 2 名是 90 分,所有得 90 分的学生都被包含进来!
6.1.4 执行逻辑
- 先按
ORDER BY排序 - 取前 N 行
- 检查第 N 行的排序列值
- 把所有排序列值等于第 N 行值的记录都包含进来
→ 所以结果行数可能大于 N
✅ TOP N WITH TIES 的行为类似于 RANK() <= N
SELECT Name, ScoreFROM ScoresORDER BY Score DESCFETCH FIRST 2 ROWS WITH TIES;6.2 标量函数
在 SQL Server 中,标量函数(Scalar Function) 是:
- 接收 0 个或多个参数
- 返回单个值
- 可用于 SELECT、WHERE、ORDER BY 等子句中
标量函数可以返回几乎所有基础数据类型,如 int、varchar、datetime、decimal 等,不能返回 timestamp ,因为 timestamp 数据类型不是真正的“时间戳”,而是一个自动生成的二进制行版本号
6.3 INTO 关键字
在 SQL 中,INTO 是一个非常实用的关键字,主要用于 将查询结果保存到新表中。它常用于数据备份、临时表创建、数据归档、ETL 过程等场景。
6.3.1 基本语法
SELECT 列1, 列2, ...INTO 新表名FROM 原表[WHERE 条件][ORDER BY ...][GROUP BY ...]⚠️ 注意:
INTO必须紧跟在SELECT列表之后- 新表会自动创建,结构由查询结果决定
- 不能用于已有表(除非使用
INSERT INTO ... SELECT)
6.3.2 核心作用
- 创建新表并插入数据(一步完成)
SELECT *INTO Employees_BackupFROM EmployeesWHERE Department = 'Sales';→ 创建一个名为 Employees_Backup 的新表,并插入销售部员工数据。
- 仅复制表结构(不复制数据)
SELECT *INTO NewTableFROM OldTableWHERE 1 = 0; -- 条件永假,不复制数据→ 创建一个结构相同但无数据的新表。
- 创建临时表
SELECT *INTO #TempEmployeesFROM EmployeesWHERE Salary > 5000;→ 创建本地临时表 #TempEmployees(SQL Server 特有语法)
6.4 UNION 操作符
UNION 是 SQL 中用于合并多个 SELECT 查询结果集的重要操作符。它允许你将两个或多个查询的结果纵向“堆叠”在一起,形成一个统一的结果集。
6.4.1基本语法
SELECT column1, column2, ...FROM table1[WHERE condition]UNION [ALL]SELECT column1, column2, ...FROM table2[WHERE condition][ORDER BY ...];⚠️ 注意:
- 所有
SELECT语句的列数必须相同- 对应列的数据类型应兼容
- 列名以第一个 SELECT 的列名为准
6.4.2 UNION vs UNION ALL
| 操作符 | 作用 | 是否去重 | 性能 |
|---|---|---|---|
| UNION | 合并结果集,并自动去除重复行 | ✅ 去重 | ⚠️ 较慢(需排序去重) |
| UNION ALL | 合并结果集,保留所有重复行 | ❌ 不去重 | ✅ 更快 |
6.4.3 举例说明
假设有两个表:
Employees_China:
| ID | Name | Dept |
|---|---|---|
| 1 | Alice | Sales |
| 2 | Bob | HR |
Employees_USA:
| ID | Name | Dept |
|---|---|---|
| 3 | Carol | Sales |
| 2 | Bob | HR |
使用 UNION(去重):
SELECT ID, Name, Dept FROM Employees_ChinaUNIONSELECT ID, Name, Dept FROM Employees_USA;→ 结果:
ID | Name | Dept---|--------|-------1 | Alice | Sales2 | Bob | HR3 | Carol | Sales→ Bob 的重复记录被去除了
使用 UNION ALL(不去重):
SELECT ID, Name, Dept FROM Employees_ChinaUNION ALLSELECT ID, Name, Dept FROM Employees_USA;→ 结果:
ID | Name | Dept---|--------|-------1 | Alice | Sales2 | Bob | HR3 | Carol | Sales2 | Bob | HR ← 重复保留6.4.4 注意事项
- 列数和类型必须匹配
SELECT Name FROM Table1UNIONSELECT Age FROM Table2; -- ❌ 类型不兼容(字符串 vs 数字)✅ 正确做法:
SELECT Name, CAST(Age AS VARCHAR) FROM Table1UNIONSELECT Name, CAST(Salary AS VARCHAR) FROM Table2;- ORDER BY 只能出现在最后
SELECT Name FROM Table1 ORDER BY Name -- ❌ 错误!UNIONSELECT Name FROM Table2;✅ 正确写法:
SELECT Name FROM Table1UNIONSELECT Name FROM Table2ORDER BY Name; -- ✅ 只在最后写一次- 不能单独对某个 SELECT 使用 WHERE 后排序
如需排序中间结果,可使用子查询:
SELECT * FROM ( SELECT Name FROM Table1 WHERE Dept = 'Sales' ORDER BY Name -- ❌ 子查询中 ORDER BY 无意义) t1UNIONSELECT * FROM ( SELECT Name FROM Table2 WHERE Dept = 'HR' ORDER BY Name) t2;→ 子查询中的 ORDER BY 会被忽略,最终排序由外层 ORDER BY 决定。
6.5 SQL 约束
在 SQL Server 中,约束(Constraints) 是用于强制数据完整性的重要机制。它们定义在表的列或表级别上,确保插入、更新或删除数据时满足特定规则,从而保证数据库中数据的准确性、一致性和可靠性。
SQL Server 支持以下 5 种主要约束类型:
6.5.1 主键约束(PRIMARY KEY)
作用:
- 唯一标识表中的每一行(记录)
- 确保列(或列组合)的值 唯一且非空
特点:
- 一个表只能有一个主键
- 自动创建唯一聚集索引(默认,可改为非聚集)
- 可由单列或多列组成(复合主键)
示例:
CREATE TABLE Employees ( EmployeeID INT PRIMARY KEY, Name VARCHAR(50));或:
ALTER TABLE EmployeesADD CONSTRAINT PK_EmployeeID PRIMARY KEY (EmployeeID);6.5.2 外键约束(FOREIGN KEY)
作用
- 建立和强制表与表之间的引用完整性
- 确保子表中的值必须在主表的主键或唯一键中存在
特点
- 可定义
ON DELETE和ON UPDATE行为:CASCADE(级联删除/更新)SET NULLSET DEFAULTNO ACTION(默认)
示例:
CREATE TABLE Orders ( OrderID INT PRIMARY KEY, EmployeeID INT, FOREIGN KEY (EmployeeID) REFERENCES Employees(EmployeeID));或:
ALTER TABLE OrdersADD CONSTRAINT FK_Orders_EmployeesFOREIGN KEY (EmployeeID) REFERENCES Employees(EmployeeID);6.5.3 唯一约束(UNIQUE)
作用:
- 确保列(或列组合)的值在表中唯一(允许 NULL,但只能有一个 NULL)
特点:
- 与主键不同:允许 NULL,且一个表可有多个唯一约束
- 自动创建唯一非聚集索引(默认)
示例:
CREATE TABLE Users ( UserID INT PRIMARY KEY, Email VARCHAR(100) UNIQUE);或:
ALTER TABLE UsersADD CONSTRAINT UQ_Email UNIQUE (Email);⚠️ 注意:多个 NULL 值是否允许取决于数据库设置(SQL Server 默认允许多个 NULL,但逻辑上视为“不相等”)
6.5.4 检查约束(CHECK)
作用:
- 限制列中可接受的值范围或格式
- 使用逻辑表达式定义约束条件
特点:
- 可用于单列或多列
- 表达式结果必须为 TRUE 才允许插入/更新
- 不能包含子查询(在 CHECK 中)、聚合函数
示例:
CREATE TABLE Products ( ProductID INT PRIMARY KEY, Price MONEY CHECK (Price > 0), Category VARCHAR(20) CHECK (Category IN ('Electronics', 'Books', 'Clothing')));或:
ALTER TABLE ProductsADD CONSTRAINT CK_Price CHECK (Price > 0);6.5.5. 默认约束(DEFAULT)
作用:
- 为列指定默认值,当插入行时未提供该列值,则自动使用默认值
特点:
- 不是“强制完整性”,而是“提供默认值”
- 可用于任何数据类型
- 常用函数:
GETDATE()、NEWID()、常量等
示例:
CREATE TABLE Orders ( OrderID INT PRIMARY KEY, OrderDate DATETIME DEFAULT GETDATE(), Status VARCHAR(20) DEFAULT 'Pending');或:
ALTER TABLE OrdersADD CONSTRAINT DF_Status DEFAULT 'Pending' FOR Status;6.5.6 总结对比表:
| 约束类型 | 是否允许 NULL | 是否唯一 | 主要目的 | 是否自动建索引 |
|---|---|---|---|---|
| PRIMARY KEY | ❌ 不允许 | ✅ 唯一 | 唯一标识行 | ✅ 是(默认聚集) |
| FOREIGN KEY | ✅ 允许 | ❌ 不要求 | 引用完整性 | ❌ 否(建议手动建索引) |
| UNIQUE | ✅ 允许(通常只一个 NULL) | ✅ 唯一 | 防止重复值 | ✅ 是(非聚集) |
| CHECK | ✅ 允许 | ❌ 不要求 | 限制取值范围 | ❌ 否 |
| DEFAULT | ✅ 允许 | ❌ 不要求 | 提供默认值 | ❌ 否 |
6.6 连接
在 SQL 中,连接(JOIN) 是用于组合多个表中的数据的核心操作。不同类型的 JOIN 适用于不同场景,理解它们的区别对编写高效、正确的查询至关重要。
6.6.1 连接类型总览
SQL 中主要的连接类型包括:
| 连接类型 | 别名 | 说明 |
|---|---|---|
| INNER JOIN | 内连接 | 只返回匹配的行 |
| LEFT JOIN | 左外连接 | 返回左表所有行 + 匹配的右表行 |
| RIGHT JOIN | 右外连接 | 返回右表所有行 + 匹配的左表行 |
| FULL JOIN | 全外连接 | 返回左右表所有行,匹配则合并,不匹配则补 NULL |
| CROSS JOIN | 笛卡尔积 | 无条件组合,左表每行 × 右表每行 |
| SELF JOIN | 自连接 | 同一张表“自己连自己” |
| NATURAL JOIN | 自然连接 | 自动按同名列连接(不推荐) |
我们用两个示例表来演示:
表:Departments(部门表)
| DeptID | DeptName |
|---|---|
| 1 | Sales |
| 2 | Engineering |
| 3 | HR |
-
- 表:
Employees(员工表)**
- 表:
| EmpID | EmpName | DeptID | JobTitle |
|---|---|---|---|
| 101 | Alice | 1 | Manager |
| 102 | Bob | 1 | Clerk |
| 103 | Carol | 2 | Engineer |
| 104 | David | NULL | Intern |
6.6.2 INNER JOIN(内连接)
作用: 只返回两个表中连接条件匹配的行
📌 语法:
SELECT *FROM Departments DINNER JOIN Employees E ON D.DeptID = E.DeptID;-
- 结果:**
| DeptID | DeptName | EmpID | EmpName | DeptID | JobTitle |
|---|---|---|---|---|---|
| 1 | Sales | 101 | Alice | 1 | Manager |
| 1 | Sales | 102 | Bob | 1 | Clerk |
| 2 | Engineering | 103 | Carol | 2 | Engineer |
→ HR 部门(DeptID=3)没有员工 → 不出现
→ David(DeptID=NULL)不属于任何部门 → 不出现
6.6.3 LEFT JOIN(左外连接)
作用: 返回左表所有行,右表无匹配则补 NULL
- 语法:
SELECT *FROM Departments DLEFT JOIN Employees E ON D.DeptID = E.DeptID;结果:
| DeptID | DeptName | EmpID | EmpName | DeptID | JobTitle |
|---|---|---|---|---|---|
| 1 | Sales | 101 | Alice | 1 | Manager |
| 1 | Sales | 102 | Bob | 1 | Clerk |
| 2 | Engineering | 103 | Carol | 2 | Engineer |
| 3 | HR | NULL | NULL | NULL | NULL |
→ HR 部门无员工 → 右表字段为 NULL
→ David 不属于任何部门 → 不出现(因为他是右表,且左表无对应)
6.6.4 RIGHT JOIN(右外连接)
作用: 返回右表所有行,左表无匹配则补 NULL
语法:
SELECT *FROM Departments DRIGHT JOIN Employees E ON D.DeptID = E.DeptID;结果:
| DeptID | DeptName | EmpID | EmpName | DeptID | JobTitle |
|---|---|---|---|---|---|
| 1 | Sales | 101 | Alice | 1 | Manager |
| 1 | Sales | 102 | Bob | 1 | Clerk |
| 2 | Engineering | 103 | Carol | 2 | Engineer |
| NULL | NULL | 104 | David | NULL | Intern |
→ David 不属于任何部门 → 左表字段为 NULL
→ HR 部门无员工 → 不出现(因为它是左表,且右表无对应)
6.6.5 FULL JOIN(全外连接)
作用: 返回左右表所有行,无匹配则补 NULL
语法:
SELECT *FROM Departments DFULL JOIN Employees E ON D.DeptID = E.DeptID;结果:
| DeptID | DeptName | EmpID | EmpName | DeptID | JobTitle |
|---|---|---|---|---|---|
| 1 | Sales | 101 | Alice | 1 | Manager |
| 1 | Sales | 102 | Bob | 1 | Clerk |
| 2 | Engineering | 103 | Carol | 2 | Engineer |
| 3 | HR | NULL | NULL | NULL | NULL |
| NULL | NULL | 104 | David | NULL | Intern |
→ 所有部门和所有员工都出现
6.6.6 CROSS JOIN(交叉连接 / 笛卡尔积)
作用:无条件组合,左表每行 × 右表每行
语法:
SELECT *FROM Departments DCROSS JOIN Employees E;结果:
- 3 个部门 × 4 个员工 = 12 行
→ 每个员工都“属于”每个部门(无意义,除非有业务场景)
6.6.7 SELF JOIN(自连接)
-
- 作用:** 同一张表自己连接自己,常用于层级关系(如员工-经理)
示例表:Employees2
| EmpID | EmpName | ManagerID |
|---|---|---|
| 1 | Alice | NULL |
| 2 | Bob | 1 |
| 3 | Carol | 1 |
查询每个员工及其经理:
SELECT e1.EmpName AS Employee, e2.EmpName AS ManagerFROM Employees2 e1LEFT JOIN Employees2 e2 ON e1.ManagerID = e2.EmpID;结果:
| Employee | Manager |
|---|---|
| Alice | NULL |
| Bob | Alice |
| Carol | Alice |
6.6.8 NATURAL JOIN(自然连接)
作用: 自动按同名列进行 INNER JOIN(不推荐使用)
语法:
SELECT * FROM Departments NATURAL JOIN Employees;→ 自动按 DeptID 连接(因为两表都有此列)
为什么不推荐?
- 列名可能意外匹配
- 不明确,难维护
- 不同数据库行为不一致
6.6.9 连接类型对比表
| 连接类型 | 返回左表独有行 | 返回右表独有行 | 返回匹配行 | 用途 |
|---|---|---|---|---|
| INNER JOIN | ❌ | ❌ | ✅ | 只查有关联的数据 |
| LEFT JOIN | ✅ | ❌ | ✅ | 主表全保留,查“缺失” |
| RIGHT JOIN | ❌ | ✅ | ✅ | 从表全保留(少用) |
| FULL JOIN | ✅ | ✅ | ✅ | 查全量差异 |
| CROSS JOIN | ✅ | ✅ | ✅×✅ | 生成组合(慎用) |
| SELF JOIN | 取决于类型 | 取决于类型 | 取决于类型 | 层级/递归查询 |
| NATURAL JOIN | ❌ | ❌ | ✅ | 自动匹配(不推荐) |
6.6.10 如何选择连接类型?
| 场景 | 推荐连接 |
|---|---|
| 只关心有关联的数据 | INNER JOIN |
| 主表数据必须全显示(如:所有部门) | LEFT JOIN |
| 从表数据必须全显示(如:所有员工) | RIGHT JOIN(或换表顺序用 LEFT) |
| 查两个表的完整差异 | FULL JOIN |
| 生成所有组合 | CROSS JOIN |
| 查层级关系(员工-经理) | SELF JOIN |
6.10 DISTINCT
当然可以!DISTINCT 是 SQL 中一个非常常用的关键字,用于去除查询结果中的重复行,返回唯一值。
6.10.1 基本语法
SELECT DISTINCT 列1, 列2, ...FROM 表名[WHERE ...][ORDER BY ...];⚠️ 注意:
DISTINCT作用于所有 SELECT 列的组合- 必须紧跟在
SELECT之后- 不能用于聚合函数内部(如
COUNT(DISTINCT col)是合法的,但DISTINCT COUNT(col)非法)
6.10.2 核心作用
去除结果集中完全重复的行,只保留唯一组合
6.10.3 举例说明
假设有一个学生选课表 Enrollments:
| Student | Course |
|---|---|
| Alice | Math |
| Bob | Math |
| Alice | Math |
| Carol | English |
| Bob | Science |
| Alice | Math |
使用 DISTINCT 去重:
SELECT DISTINCT Student, CourseFROM Enrollments;→ 结果:
| Student | Course |
|---|---|
| Alice | Math |
| Bob | Math |
| Carol | English |
| Bob | Science |
→ 重复的 (Alice, Math) 被去除了
6.10.4 高级用法
- COUNT(DISTINCT)
统计某列不重复值的数量:
SELECT COUNT(DISTINCT Student) AS StudentCountFROM Enrollments;→ 返回选课的学生总数(去重后)
- DISTINCT ON(PostgreSQL 特有)
PostgreSQL 支持 DISTINCT ON,可实现“每组取一条”:
SELECT DISTINCT ON (Student) Student, CourseFROM EnrollmentsORDER BY Student, Course;→ 每个学生只返回一条记录(按 Course 排序后的第一条)
❗ SQL Server / MySQL 不支持 DISTINCT ON,需用 ROW_NUMBER()
6.11 GROUP BY 子句
GROUP BY 是 SQL 中最核心、最强大的子句之一,用于对数据进行分组聚合,常与聚合函数(如 COUNT, SUM, AVG, MAX, MIN)配合使用,实现数据的分类统计。
6.11.1 基本语法
SELECT 列1, 聚合函数(列2)FROM 表名[WHERE 条件]GROUP BY 列1[HAVING 分组后过滤条件][ORDER BY ...];⚠️ 注意:
GROUP BY后的列必须出现在SELECT中(除非是聚合函数参数)- 聚合函数不能出现在
WHERE中,只能在SELECT或HAVING中HAVING用于过滤分组后的结果,WHERE用于过滤分组前的行
6.11.2 核心作用
将数据按指定列分组,然后对每组应用聚合函数,返回每组的汇总结果
6.11.3 举例说明
假设有一个销售表 Sales:
| OrderID | Product | Amount | Region |
|---|---|---|---|
| 1 | Apple | 100 | North |
| 2 | Banana | 150 | South |
| 3 | Apple | 200 | North |
| 4 | Orange | 80 | East |
| 5 | Banana | 120 | South |
按产品分组,统计总销售额
SELECT Product, SUM(Amount) AS TotalSalesFROM SalesGROUP BY Product;→ 结果:
| Product | TotalSales |
|---|---|
| Apple | 300 |
| Banana | 270 |
| Orange | 80 |
按地区分组,统计订单数
SELECT Region, COUNT(*) AS OrderCountFROM SalesGROUP BY Region;→ 结果:
| Region | OrderCount |
|---|---|
| North | 2 |
| South | 2 |
| East | 1 |
多列分组:按产品和地区分组
SELECT Product, Region, SUM(Amount) AS TotalSalesFROM SalesGROUP BY Product, Region;→ 结果:
| Product | Region | TotalSales |
|---|---|---|
| Apple | North | 300 |
| Banana | South | 270 |
| Orange | East | 80 |
6.12 OVER 窗口函数
OVER 子句是 SQL 中用于定义窗口函数(Window Function) 的核心语法。它允许你在不改变行数的前提下,对一组相关的行(即“窗口”)进行计算,常用于排名、累计、移动平均等分析场景。
6.12.1 基本语法
函数名() OVER ( [PARTITION BY 列名] -- 分区(可选) [ORDER BY 列名 [ASC|DESC]] -- 排序(可选) [ROWS/RANGE 子句] -- 窗口范围(可选))⚠️ 注意:
OVER()是窗口函数的标志,没有OVER就不是窗口函数- 窗口函数不聚合行,每行都保留,但可基于窗口计算值
6.12.2核心概念
- 窗口(Window)
- 一组相关的行,用于计算当前行的函数值
- 默认是“整个结果集”,可通过
PARTITION BY、ORDER BY、ROWS/RANGE缩小范围
- 分区(PARTITION BY)
- 类似
GROUP BY,但不分组,只是划分计算范围 - 每个分区内独立计算
- 类似
- 排序(ORDER BY)
- 定义窗口内行的顺序
- 影响累计、排名等计算
- 窗口范围(ROWS/RANGE)
- 定义当前行的“计算窗口”大小
- 如:前3行到当前行、当前行到后2行等
6.12.3 常用窗口函数分类
| 类型 | 函数 | 说明 |
|---|---|---|
| 排名函数 | ROW_NUMBER(), RANK(), DENSE_RANK(), NTILE() | 计算排名 |
| 聚合函数 | SUM(), AVG(), COUNT(), MAX(), MIN() | 累计、移动平均等 |
| 偏移函数 | LAG(), LEAD(), FIRST_VALUE(), LAST_VALUE() | 访问前后行数据 |
6.12.4 举例说明
假设有一个销售表 Sales:
| OrderID | Product | Amount | OrderDate |
|---|---|---|---|
| 1 | Apple | 100 | 2023-01-01 |
| 2 | Banana | 150 | 2023-01-02 |
| 3 | Apple | 200 | 2023-01-03 |
| 4 | Orange | 80 | 2023-01-04 |
| 5 | Banana | 120 | 2023-01-05 |
- ROW_NUMBER() - 行号
SELECT *, ROW_NUMBER() OVER (ORDER BY Amount DESC) AS RowNumFROM Sales;→ 按金额降序分配行号(1,2,3…)
- RANK() / DENSE_RANK() - 排名
SELECT *, RANK() OVER (ORDER BY Amount DESC) AS Rank, DENSE_RANK() OVER (ORDER BY Amount DESC) AS DenseRankFROM Sales;→ 金额相同则排名相同,RANK() 会跳号,DENSE_RANK() 不跳号
- SUM() OVER() - 累计求和
SELECT *, SUM(Amount) OVER (ORDER BY OrderDate) AS CumulativeSumFROM Sales;→ 按日期顺序累计销售额
- PARTITION BY - 分区内计算
SELECT *, SUM(Amount) OVER (PARTITION BY Product ORDER BY OrderDate) AS ProductCumulativeFROM Sales;→ 按产品分组,计算每个产品的累计销售额
- LAG() / LEAD() - 前后行数据
SELECT *, LAG(Amount, 1) OVER (ORDER BY OrderDate) AS PrevAmount, LEAD(Amount, 1) OVER (ORDER BY OrderDate) AS NextAmountFROM Sales;- ROWS - 窗口范围
SELECT *, AVG(Amount) OVER ( ORDER BY OrderDate ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING ) AS MovingAvgFROM Sales;→ 计算当前行及前后各一行的移动平均值
6.13 TOP 关键字
TOP 是 SQL Server 和 MS Access 中特有的关键字,用于限制查询返回的行数。它常用于分页、取前 N 名、抽样等场景。
⚠️ 注意:
TOP不是 ANSI SQL 标准,在 MySQL、PostgreSQL、Oracle 中不支持- 其他数据库有类似功能,如:
- MySQL / PostgreSQL:
LIMIT- Oracle:
ROWNUM或FETCH FIRST
6.13.1 基本语法(SQL Server)
SELECT [TOP (n) | TOP n [PERCENT]] [WITH TIES] 列名FROM 表名[ORDER BY ...]⚠️ 注意:
TOP必须紧跟在SELECT之后- 必须配合
ORDER BY使用WITH TIESTOP n和TOP (n)都合法(括号可选,但推荐使用括号)
6.13.2 核心用法
- 取前 N 行
SELECT TOP 5 EmployeeID, Name, SalaryFROM EmployeesORDER BY Salary DESC;→ 返回工资最高的前 5 名员工
- 取前 N% 行
SELECT TOP 10 PERCENT EmployeeID, Name, SalaryFROM EmployeesORDER BY Salary DESC;- WITH TIES(包含并列)
SELECT TOP 3 WITH TIES EmployeeID, Name, SalaryFROM EmployeesORDER BY Salary DESC;→ 如果第 3 名有并列(如多人同工资),则全部返回(结果可能 > 3 行)
✅ 举例:
- 第1名:10000
- 第2名:9000
- 第3名:8000(有3人并列)
- 结果:1 + 1 + 3 = 5 行
6.14 SELECT 逻辑执行顺序
SELECT 查询内容FROM 表名WHERE 条件表达式GROUP BY 待分组的列名HAVING 列名ORDER BY 待排序的列名6.15 LIKE 操作符
LIKE 是 SQL 中用于模糊匹配字符串的重要操作符,常用于 WHERE 子句中,配合通配符实现灵活的文本搜索。
6.15.1 基本语法
SELECT 列名FROM 表名WHERE 列名 LIKE '模式';⚠️ 注意:
LIKE用于字符型数据(CHAR、VARCHAR、TEXT 等)- 区分大小写取决于数据库和排序规则(Collation)
- 通常与通配符
%和_配合使用
6.15.2 通配符说明
| 通配符 | 含义 | 示例 |
|---|---|---|
| % | 匹配任意长度的任意字符(包括零字符) | 'A%' → 以 A 开头 |
| _ | 匹配单个任意字符 | 'A__' → A 开头,后跟两个字符 |
| [ ] | 匹配括号内的任意一个字符(SQL Server / Access) | '[ABC]%' → A、B 或 C 开头 |
| [^ ] | 匹配不在括号内的任意一个字符(SQL Server / Access) | '[^A]%' → 不以 A 开头 |
⚠️ [ ] 和 [^ ] 是 SQL Server / MS Access 特有,MySQL/PostgreSQL 不支持
6.15.3 举例说明
假设有一个员工表 Employees:
| EmployeeID | Name | |
|---|---|---|
| 1 | Alice | alice@company.com |
| 2 | Bob | bob@company.com |
| 3 | Carol | carol@company.com |
| 4 | David | david@other.com |
| 5 | Eve | eve@company.com |
- % - 任意长度匹配
-- 以 'A' 开头的名字SELECT * FROM Employees WHERE Name LIKE 'A%';→ Alice
-- 包含 'a' 的邮箱SELECT * FROM Employees WHERE Email LIKE '%a%';→ alice@company.com, carol@company.com, david@other.com
-- 以 '.com' 结尾的邮箱SELECT * FROM Employees WHERE Email LIKE '%.com';→ 所有公司邮箱- _ - 单字符匹配
-- 名字是 3 个字符SELECT * FROM Employees WHERE Name LIKE '___';→ Bob, Eve
-- 名字以 'A' 开头,后跟两个字符SELECT * FROM Employees WHERE Name LIKE 'A__';→ Alice(A-l-i)- 字符集匹配(SQL Server)
-- 名字以 A、B 或 C 开头SELECT * FROM Employees WHERE Name LIKE '[ABC]%';→ Alice, Bob, Carol
-- 名字第二个字符是 a、e 或 iSELECT * FROM Employees WHERE Name LIKE '_[aei]%';→ Alice (l-i-c-e), Carol (a-r-o-l)- [^ ] - 排除字符集(SQL Server)
-- 名字不以 A 开头SELECT * FROM Employees WHERE Name LIKE '[^A]%';→ Bob, Carol, David, Eve
-- 邮箱域名不是 company.comSELECT * FROM Employees WHERE Email LIKE '%@[^c]%.%';→ david@other.com``6.16 EXCEPT 关键字
EXCEPT 是 SQL 中用于集合操作的关键字,用于返回第一个查询结果中存在、但第二个查询结果中不存在的行。它类似于数学中的“差集”(A - B)。
6.16.1基本语法
SELECT 列1, 列2, ...FROM 表1[WHERE 条件]EXCEPTSELECT 列1, 列2, ...FROM 表2[WHERE 条件];⚠️ 注意:
- 两个
SELECT的列数和数据类型必须兼容- 列名以第一个 SELECT 的列名为准
EXCEPT会自动去重(如需保留重复,用EXCEPT ALL,但支持有限)
6.16.2 核心作用
返回在第一个查询结果中,但不在第二个查询结果中的唯一行
6.16.3 举例说明
假设有两个表:
表:Employees_2023(2023年员工)
| EmployeeID | Name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
表:Employees_2024(2024年员工)
| EmployeeID | Name |
|---|---|
| 2 | Bob |
| 3 | Carol |
| 4 | David |
查询 2023 年有、但 2024 年没有的员工:
SELECT EmployeeID, Name FROM Employees_2023EXCEPTSELECT EmployeeID, Name FROM Employees_2024;→ 结果:
| EmployeeID | Name |
|---|---|
| 1 | Alice |
→ Alice 在 2023 年存在,但在 2024 年不存在
6.17 CASE 条件表达式
CASE 是 SQL 中最强大的条件表达式,用于实现“如果…那么…否则…”的逻辑,类似于编程语言中的 if-else 或 switch-case。它可以在 SELECT、WHERE、ORDER BY、GROUP BY 等子句中使用,实现动态计算、分类、排序等高级功能。
6.17.1 基本语法
SQL 中的 CASE 有两种形式:
-
简单 CASE 表达式(类似 switch-case)
CASE 列名WHEN 值1 THEN 结果1WHEN 值2 THEN 结果2...ELSE 默认结果END适用于列值等于某个常量的场景
-
搜索 CASE 表达式(类似 if-else)
CASEWHEN 条件1 THEN 结果1WHEN 条件2 THEN 结果2...ELSE 默认结果END适用于复杂条件判断(如范围、逻辑表达式)
6.17.2 核心作用
根据条件动态返回不同的值
6.17.3 举例说明
假设有一个员工表 Employees:
| EmployeeID | Name | Salary | Department |
|---|---|---|---|
| 1 | Alice | 8000 | Sales |
| 2 | Bob | 6000 | HR |
| 3 | Carol | 12000 | IT |
| 4 | David | 4000 | Sales |
- 简单 CASE:按部门分类
SELECT Name, Department, CASE Department WHEN 'Sales' THEN '销售部' WHEN 'HR' THEN '人力资源部' WHEN 'IT' THEN '技术部' ELSE '其他部门' END AS DeptChineseFROM Employees;→ 结果:
| Name | Department | DeptChinese |
|---|---|---|
| Alice | Sales | 销售部 |
| Bob | HR | 人力资源部 |
| Carol | IT | 技术部 |
| David | Sales | 销售部 |
- 搜索 CASE:按工资分级
SELECT Name, Salary, CASE WHEN Salary >= 10000 THEN '高薪' WHEN Salary >= 5000 THEN '中薪' ELSE '低薪' END AS SalaryLevelFROM Employees;→ 结果:
| Name | Salary | SalaryLevel |
|---|---|---|
| Alice | 8000 | 中薪 |
| Bob | 6000 | 中薪 |
| Carol | 12000 | 高薪 |
| David | 4000 | 低薪 |
- 在 WHERE 中使用 CASE(慎用)
-- 查询:如果部门是 Sales,则查工资 > 5000;否则查工资 > 3000SELECT *FROM EmployeesWHERE Salary > CASE WHEN Department = 'Sales' THEN 5000 ELSE 3000 END;→ 结果:Alice, Bob, Carol
⚠️ 注意:在 WHERE 中使用 CASE 可能影响性能,建议用逻辑表达式替代:
WHERE (Department = 'Sales' AND Salary > 5000)OR (Department != 'Sales' AND Salary > 3000)
- 在 ORDER BY 中使用 CASE
-- 按部门排序:Sales 在前,然后是 IT,最后是 HRSELECT *FROM EmployeesORDER BY CASE Department WHEN 'Sales' THEN 1 WHEN 'IT' THEN 2 WHEN 'HR' THEN 3 ELSE 4 END;→ 结果按自定义顺序排列
- 在 GROUP BY 中使用 CASE
-- 按工资级别分组统计人数SELECT CASE WHEN Salary >= 10000 THEN '高薪' WHEN Salary >= 5000 THEN '中薪' ELSE '低薪' END AS SalaryLevel, COUNT(*) AS EmployeeCountFROM EmployeesGROUP BY CASE WHEN Salary >= 10000 THEN '高薪' WHEN Salary >= 5000 THEN '中薪' ELSE '低薪' END;→ 结果:
| SalaryLevel | EmployeeCount |
|---|---|
| 中薪 | 2 |
| 高薪 | 1 |
| 低薪 | 1 |
6.18 WITH AS
WITH AS 是 SQL 中用于定义公用表表达式(Common Table Expression, CTE) 的语法结构。它允许你临时定义一个结果集,并在后续查询中像表一样引用它。CTE 使复杂查询更清晰、更易维护,支持递归查询,是现代 SQL 开发的重要工具。
6.18.1 基本语法
WITH 别名 AS ( SELECT 语句)SELECT * FROM 别名;⚠️ 注意:
WITH必须是查询语句的第一个关键字- CTE 只在当前语句中有效(非持久化)
- 可定义多个 CTE,用逗号分隔
- 支持递归(需
UNION ALL和锚点/递归成员)
6.18.2 核心作用
将复杂查询分解为多个逻辑步骤,提高可读性和可维护性
6.18.3 举例说明
假设有一个员工表 Employees:
| EmployeeID | Name | ManagerID |
|---|---|---|
| 1 | Alice | NULL |
| 2 | Bob | 1 |
| 3 | Carol | 1 |
| 4 | David | 2 |
- 基本 CTE:简化复杂查询
WITH HighSalary AS ( SELECT EmployeeID, Name, Salary FROM Employees WHERE Salary > 5000)SELECT Name, SalaryFROM HighSalaryORDER BY Salary DESC;- 多 CTE:链式处理
WITHHighSalary AS ( SELECT EmployeeID, Name, Salary FROM Employees WHERE Salary > 5000),DeptAvg AS ( SELECT Department, AVG(Salary) AS AvgSalary FROM Employees GROUP BY Department)SELECT h.Name, h.Salary, d.AvgSalaryFROM HighSalary hJOIN DeptAvg d ON h.Department = d.Department;→ 先计算高薪员工,再计算部门平均工资,最后关联查询
- 递归 CTE:查询层级结构
WITH EmployeeHierarchy AS ( -- 锚点成员:顶级经理 SELECT EmployeeID, Name, ManagerID, 0 AS Level FROM Employees WHERE ManagerID IS NULL
UNION ALL
-- 递归成员:下属员工 SELECT e.EmployeeID, e.Name, e.ManagerID, eh.Level + 1 FROM Employees e JOIN EmployeeHierarchy eh ON e.ManagerID = eh.EmployeeID)SELECT Name, LevelFROM EmployeeHierarchyORDER BY Level, Name;→ 结果:
| Name | Level |
|---|---|
| Alice | 0 |
| Bob | 1 |
| Carol | 1 |
| David | 2 |
→ 递归查询员工层级关系
6.19 排名函数
在 SQL 中,排名函数(Ranking Functions) 是窗口函数的一种,用于为查询结果集中的每一行分配一个“排名”值。它们常用于实现排行榜、分页、分组排名等场景。
SQL 标准中主要的排名函数有四个:
✅ ROW_NUMBER()
✅ RANK()
✅ DENSE_RANK()
✅ NTILE(n)
6.19.1 基本语法(通用)
所有排名函数都需配合 OVER() 子句使用:
函数名() OVER ( [PARTITION BY 列名] -- 可选:分组 ORDER BY 列名 [ASC|DESC] -- 必须:排序)⚠️ 注意:
ORDER BY是必须的,否则排名无意义PARTITION BY用于分组内排名(类似 GROUP BY,但保留所有行)
6.19.2 四大排名函数详解 + 示例
我们用一个销售表 Sales 来演示:
| SalesID | Salesperson | Amount |
|---|---|---|
| 1 | Alice | 5000 |
| 2 | Bob | 5000 |
| 3 | Carol | 4000 |
| 4 | David | 3000 |
| 5 | Eve | 5000 |
| 6 | Frank | 2000 |
-
✅ ROW_NUMBER() —— 行号(无并列)
为每一行分配唯一的连续序号,即使值相同,序号也不同。
SELECT *,ROW_NUMBER() OVER (ORDER BY Amount DESC) AS RowNumFROM Sales;→ 结果:
SalesID Salesperson Amount RowNum 1 Alice 5000 1 2 Bob 5000 2 5 Eve 5000 3 3 Carol 4000 4 4 David 3000 5 6 Frank 2000 6 🎯 用途:分页、取 Top N、去重(取每组 RowNum=1)
-
RANK() —— 跳跃排名(有并列,会跳号)
相同值获得相同排名,但下一个排名会跳过并列的行数。
SELECT *,RANK() OVER (ORDER BY Amount DESC) AS RankNumFROM Sales;→ 结果:
SalesID Salesperson Amount RankNum 1 Alice 5000 1 2 Bob 5000 1 5 Eve 5000 1 3 Carol 4000 4 ← 跳过了 2,3 4 David 3000 5 6 Frank 2000 6 🎯 用途:排行榜(允许并列,如奥运奖牌榜)
-
✅ DENSE_RANK() —— 密集排名(有并列,不跳号)
相同值获得相同排名,下一个排名紧接上一个排名(不跳号)。
SELECT *,DENSE_RANK() OVER (ORDER BY Amount DESC) AS DenseRankFROM Sales;→ 结果:
SalesID Salesperson Amount DenseRank 1 Alice 5000 1 2 Bob 5000 1 5 Eve 5000 1 3 Carol 4000 2 ← 不跳号 4 David 3000 3 6 Frank 2000 4 🎯 用途:分组排名、等级划分(如 A/B/C 级)
-
NTILE(n) —— 分桶(分组排名)
将结果集平均分为 n 个桶(组),并为每行分配桶号(1 到 n)。
SELECT *,NTILE(3) OVER (ORDER BY Amount DESC) AS NTileFROM Sales;→ 结果(6 行 ÷ 3 = 每桶 2 行):
SalesID Salesperson Amount NTile 1 Alice 5000 1 2 Bob 5000 1 5 Eve 5000 2 3 Carol 4000 2 4 David 3000 3 6 Frank 2000 3 🎯 用途:数据分组、百分位数、A/B/C 测试分组
配合 PARTITION BY 实现分组内排名
-- 按部门分组,部门内按工资排名SELECT *,ROW_NUMBER() OVER (PARTITION BY Department ORDER BY Salary DESC) AS DeptRankFROM Employees;→ 每个部门内部从 1 开始排名
6.19.3 注意事项
- ORDER BY 是必须的
-- ❌ 错误ROW_NUMBER() OVER ()
-- ✅ 正确ROW_NUMBER() OVER (ORDER BY Amount DESC)- NULL 值处理
- NULL 值默认排在最后(ASC)或最前(DESC)
- 可用
ORDER BY 列名 NULLS FIRST/LAST控制(PostgreSQL/Oracle)
- 性能优化
- 在
ORDER BY和PARTITION BY的列上建索引可提升性能 - 大数据量时避免在 WHERE 中直接使用排名函数(用 CTE 或子查询)
6.20 EXISTS
当然可以!EXISTS 是 SQL 中一个非常重要的谓词(Predicate),用于检测子查询是否返回至少一行数据。它常用于“存在性检查”,是实现“查找存在/不存在匹配项”的高效方式。
6.20.1 基本语法
SELECT 列名FROM 表1WHERE [NOT] EXISTS (子查询);⚠️ 注意:
EXISTS只关心子查询是否有结果,不关心具体返回什么(通常用SELECT *或SELECT 1)- 子查询通常与外层查询相关联(Correlated Subquery)
NOT EXISTS用于“不存在”场景
6.20.2 核心作用
判断子查询是否返回至少一行,返回 TRUE 或 FALSE
6.20.3 举例说明
假设我们有两个表:
表:Students(学生表)
| StudentID | Name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
| 4 | David |
表:Enrollments(选课表)
| StudentID | CourseID |
|---|---|
| 1 | C01 |
| 1 | C02 |
| 2 | C01 |
| 3 | C03 |
-
EXISTS:查找选过 C01 课程的学生NameAliceBob
SELECT NameFROM Students sWHERE EXISTS (SELECT 1FROM Enrollments eWHERE e.StudentID = s.StudentIDAND e.CourseID = 'C01');→ 结果:
Name Alice Bob → 因为 Alice 和 Bob 选过 C01
-
NOT EXISTS:查找没选过 C01 课程的学生NameCarolDavid
SELECT NameFROM Students sWHERE NOT EXISTS (SELECT 1FROM Enrollments eWHERE e.StudentID = s.StudentIDAND e.CourseID = 'C01');→ 结果:
Name Carol David → Carol 选了 C03,David 没选课,都没选 C01
6.21 IN
IN 是 SQL 中一个非常常用的谓词(Predicate),用于判断某个值是否在指定的值列表或子查询结果中。它常用于 WHERE 子句中,简化多个 OR 条件的书写。
6.21.1 基本语法
-- 1. 值列表SELECT 列名FROM 表名WHERE 列名 IN (值1, 值2, ..., 值n);
-- 2. 子查询SELECT 列名FROM 表名WHERE 列名 IN (子查询);⚠️ 注意:
IN等价于多个OR条件:col = val1 OR col = val2 OR ...NOT IN表示“不在列表中”- 子查询必须返回单列
6.21.2 核心作用
判断某个值是否属于一个集合(值列表或子查询结果)
6.21.3 举例说明
假设有一个学生表 Students:
| StudentID | Name | City |
|---|---|---|
| 1 | Alice | Beijing |
| 2 | Bob | Shanghai |
| 3 | Carol | Guangzhou |
| 4 | David | Shenzhen |
- IN + 值列表:查找特定城市的学生
SELECT Name, CityFROM StudentsWHERE City IN ('Beijing', 'Shanghai');→ 结果:
| Name | City |
|---|---|
| Alice | Beijing |
| Bob | Shanghai |
等价于:
WHERE City = 'Beijing' OR City = 'Shanghai'- IN + 子查询:查找选过 C01 课程的学生
SELECT NameFROM StudentsWHERE StudentID IN ( SELECT StudentID FROM Enrollments WHERE CourseID = 'C01');→ 假设 Enrollments 表中有 StudentID 为 1 和 2 的记录 → 返回 Alice 和 Bob
- NOT IN:查找没选 C01 课程的学生
SELECT NameFROM StudentsWHERE StudentID NOT IN ( SELECT StudentID FROM Enrollments WHERE CourseID = 'C01');→ 返回 Carol 和 David(假设他们没选 C01)
6.21.4 注意事项
- NULL 值的致命问题(NOT IN)
❗ 如果子查询返回 NULL,NOT IN 会返回空结果集!
-- 如果 Enrollments 中有 StudentID 为 NULL 的记录SELECT NameFROM StudentsWHERE StudentID NOT IN ( SELECT StudentID FROM Enrollments WHERE CourseID = 'C01');→ 结果:空集(即使有学生没选 C01)
原因:
NOT IN (1, 2, NULL)等价于!= 1 AND != 2 AND != NULL!= NULL的结果是UNKNOWN(不是 TRUE/FALSE)AND运算中,TRUE AND UNKNOWN = UNKNOWN→ 被过滤掉
✅ 解决方案:
- 用
NOT EXISTS(推荐) - 或在子查询中过滤 NULL:
WHERE StudentID NOT IN ( SELECT StudentID FROM Enrollments WHERE CourseID = 'C01' AND StudentID IS NOT NULL)- 性能考虑
IN+ 值列表:性能好(值少时)IN+ 子查询:性能取决于子查询复杂度和索引- 大数据量时,
EXISTS通常比IN更高效(短路求值)
- 数据类型必须兼容
-- ❌ 错误:类型不匹配SELECT * FROM Students WHERE StudentID IN ('1', '2'); -- StudentID 是 INT
-- ✅ 正确SELECT * FROM Students WHERE StudentID IN (1, 2);6.22 ALTER 关键字
ALTER 是 SQL 中用于修改数据库对象结构的关键字,属于数据定义语言(DDL, Data Definition Language)。它允许你在不删除对象的前提下,动态调整表、列、约束、索引等的定义。
6.22.1 基本语法
ALTER OBJECT_TYPE 对象名 ACTION;最常见的是 ALTER TABLE,用于修改表结构:
ALTER TABLE 表名[ADD 列名 数据类型 [约束]][DROP COLUMN 列名][MODIFY COLUMN 列名 新数据类型] -- MySQL[ALTER COLUMN 列名 新数据类型] -- SQL Server[ADD CONSTRAINT ...][DROP CONSTRAINT ...]...⚠️ 注意:
- 不同数据库语法略有差异(如 MODIFY vs ALTER COLUMN)
ALTER是 DDL 语句,自动提交事务(不可回滚)- 修改表结构可能锁表,影响并发
6.22.2 核心用途
修改现有数据库对象的结构,如添加/删除列、修改数据类型、增删约束等
6.22.3 ALTER TABLE 常见操作 + 示例
假设有一个员工表 Employees:
CREATE TABLE Employees ( EmployeeID INT PRIMARY KEY, Name VARCHAR(50), Salary DECIMAL(10,2));-
添加列(ADD COLUMN)
ALTER TABLE EmployeesADD Department VARCHAR(50) DEFAULT '未分配';→ 添加“部门”列,默认值“未分配”
-
删除列(DROP COLUMN)
ALTER TABLE EmployeesDROP COLUMN Department;→ 删除“部门”列
⚠️ 注意:删除列会丢失数据!
-
修改列数据类型(MODIFY / ALTER COLUMN)
SQL Server:
ALTER TABLE EmployeesALTER COLUMN Salary DECIMAL(12,2);MySQL:
ALTER TABLE EmployeesMODIFY COLUMN Salary DECIMAL(12,2);→ 扩展工资精度
-
添加约束(ADD CONSTRAINT)
ALTER TABLE EmployeesADD CONSTRAINT CK_Salary CHECK (Salary > 0);ALTER TABLE EmployeesADD CONSTRAINT UQ_Name UNIQUE (Name);→ 添加检查约束和唯一约束
-
删除约束(DROP CONSTRAINT)
ALTER TABLE EmployeesDROP CONSTRAINT CK_Salary;→ 删除检查约束
-
重命名列/表(数据库相关)
SQL Server:
EXEC sp_rename 'Employees.Name', 'FullName', 'COLUMN';EXEC sp_rename 'Employees', 'Staff';MySQL:
ALTER TABLE EmployeesCHANGE COLUMN Name FullName VARCHAR(50);RENAME TABLE Employees TO Staff;
6.22.4 添加数据库文件
在 SQL Server 中,要向现有数据库添加数据文件(如增加新的 .mdf 或 .ndf 文件),必须使用:
✅ ALTER DATABASE 语句
语法格式:
ALTER DATABASE 数据库名ADD FILE ( NAME = '逻辑文件名', FILENAME = '物理路径\\文件名.ndf', SIZE = 初始大小, MAXSIZE = 最大大小, FILEGROWTH = 增长量);示例:
ALTER DATABASE MyDBADD FILE ( NAME = 'MyDB_Data2', FILENAME = 'D:\\Data\\MyDB_Data2.ndf', SIZE = 100MB, MAXSIZE = 500MB, FILEGROWTH = 50MB);→ 向 MyDB 数据库添加一个名为 MyDB_Data2 的辅助数据文件
⚠️ 注意:
CREATE DATABASE用于创建新数据库ALTER DATABASE用于修改现有数据库,包括:- 添加/删除数据文件或日志文件
- 修改文件属性
- 更改数据库选项(如 READ_ONLY、COLLATE 等)
6.23 SQL 文件组
在 SQL Server 中,文件组(Filegroup) 是用于组织和管理数据库文件的重要逻辑容器。它帮助 DBA 更高效地管理数据存储、优化 I/O 性能、实现分区表、简化备份恢复等。
6.23.1 什么是文件组(Filegroup)?
文件组是数据库中数据文件(.mdf / .ndf)的逻辑分组,用于组织表、索引等对象的物理存储位置。
- ✅ 日志文件(.ldf)不属于任何文件组!
- 文件组只管理数据文件
6.23.2 文件组的类型
SQL Server 支持两种文件组:
- 主文件组(PRIMARY Filegroup)
- 每个数据库必须有且只有一个
- 包含主数据文件(.mdf)
- 默认包含系统表(如 sys.objects, sys.columns)
- 默认是默认文件组(除非显式修改)
- 由系统创建,用户不能创建另一个“文件组”
- 用户定义文件组(User-defined Filegroup)
- 用户根据需要创建
- 用于:
- 存放用户表、索引
- 实现分区表
- 性能优化(如将热数据放在高速磁盘)
- 简化备份恢复(文件组级备份)
6.23.3 文件组的核心作用
| 作用 | 说明 |
|---|---|
| 数据组织 | 将表/索引分配到不同文件组,便于管理 |
| 性能优化 | 将不同文件组放在不同物理磁盘,分散 I/O |
| 分区支持 | 分区表的每个分区可放在不同文件组 |
| 备份恢复 | 支持文件组级备份和还原(部分还原) |
| 高可用 | 只读文件组可放在只读介质(如 WORM) |
6.23.4 常用操作语法
- 创建数据库时指定文件组
CREATE DATABASE MyDBON PRIMARY( NAME = 'MyDB_Primary', FILENAME = 'D:\\Data\\MyDB.mdf', SIZE = 100MB, FILEGROWTH = 50MB),FILEGROUP FG_UserData( NAME = 'MyDB_UserData1', FILENAME = 'E:\\Data\\MyDB_User1.ndf', SIZE = 200MB),( NAME = 'MyDB_UserData2', FILENAME = 'F:\\Data\\MyDB_User2.ndf', SIZE = 200MB)LOG ON( NAME = 'MyDB_Log', FILENAME = 'G:\\Log\\MyDB.ldf', SIZE = 100MB);→ 创建主文件组 + 用户文件组 FG_UserData
-
为现有数据库添加文件组
ALTER DATABASE MyDBADD FILEGROUP FG_Indexes; -
向文件组添加数据文件
ALTER DATABASE MyDBADD FILE(NAME = 'MyDB_Indexes1',FILENAME = 'H:\\Indexes\\MyDB_Idx1.ndf',SIZE = 100MB)TO FILEGROUP FG_Indexes; -
设置默认文件组
ALTER DATABASE MyDBMODIFY FILEGROUP FG_UserData DEFAULT;
→ 新建表/索引默认存放在 FG_UserData 中
-
将表/索引创建在指定文件组
-- 表CREATE TABLE Employees(ID INT PRIMARY KEY,Name VARCHAR(50)) ON FG_UserData;-- 索引CREATE INDEX IX_Employees_NameON Employees(Name)ON FG_Indexes; -
查看文件组信息
-- 查看文件组列表SELECT * FROM sys.filegroups;-- 查看文件组中的文件SELECTfg.name AS FilegroupName,f.name AS LogicalFileName,f.physical_name AS PhysicalPathFROM sys.filegroups fgJOIN sys.master_files f ON fg.data_space_id = f.data_space_idWHERE f.database_id = DB_ID('MyDB');
6.23.5 重要注意事项
-
日志文件不属于文件组
- 日志文件独立管理,与文件组无关
- 不能将日志文件“放入”文件组
-
主文件组不可删除
- 每个数据库必须有一个 PRIMARY 文件组
- 不能删除或重命名
-
文件组级备份恢复
- 可备份/还原单个文件组
- 适用于超大数据库(VLDB)
- 需在完整恢复模式下使用
-- 备份文件组BACKUP DATABASE MyDBFILEGROUP = 'FG_UserData'TO DISK = 'D:\\Backup\\FG_UserData.bak';-- 还原文件组RESTORE DATABASE MyDBFILEGROUP = 'FG_UserData'FROM DISK = 'D:\\Backup\\FG_UserData.bak'; -
文件组与分区表
- 分区表的每个分区可放在不同文件组
- 实现“热数据在SSD,冷数据在HDD”
CREATE PARTITION SCHEME PS_OrderDateAS PARTITION PF_OrderDateTO (FG_Hot, FG_Warm, FG_Cold);
第七章 数据库及数据库对象
7.1 CREATE INDEX
CREATE INDEX 是 SQL 中用于在表的一列或多列上创建索引的关键语句。索引是数据库性能优化的核心工具,它能显著加快数据检索速度,但也会增加数据修改(INSERT/UPDATE/DELETE)的开销。
7.1.1 基本语法
CREATE [UNIQUE] [CLUSTERED | NONCLUSTERED] INDEX 索引名ON 表名 (列1 [ASC|DESC], 列2 [ASC|DESC], ...) -- 索引键列(用于搜索、排序)INCLUDE (非键列1, 非键列2, ...) -- 包含列(仅存储在叶节点,不参与索引树结构)[WITH (选项)][ON 文件组];⚠️ 注意:
- 不同数据库语法略有差异(如 MySQL 不支持 CLUSTERED)
UNIQUE:确保索引列值唯一CLUSTERED:聚集索引(决定数据物理存储顺序,一个表只能有一个)NONCLUSTERED:非聚集索引(默认)
7.1.2 核心作用
加速数据检索,支持快速查找、排序、连接操作
7.1.3 举例说明
假设有一个员工表 Employees:
CREATE TABLE Employees ( EmployeeID INT PRIMARY KEY, Name VARCHAR(50), Department VARCHAR(50), Salary DECIMAL(10,2), HireDate DATE);-
创建单列索引
-- 在 Name 列上创建索引CREATE INDEX IX_Employees_Name ON Employees(Name);→ 加速
WHERE Name = 'Alice'或ORDER BY Name -
创建复合索引(多列)
-- 在 Department 和 Salary 列上创建复合索引CREATE INDEX IX_Employees_Dept_SalaryON Employees(Department ASC, Salary DESC);→ 加速
WHERE Department = 'Sales' ORDER BY Salary DESC📌 最左前缀原则:查询必须从索引第一列开始才能用索引
-
创建唯一索引
-- 确保 Email 唯一CREATE UNIQUE INDEX UQ_Employees_Email ON Employees(Email);→ 插入重复 Email 会报错
-
创建聚集索引(SQL Server)
-- 按 HireDate 创建聚集索引(数据按 HireDate 物理排序)CREATE CLUSTERED INDEX IX_Employees_HireDateON Employees(HireDate);→ 一个表只能有一个聚集索引(主键默认是聚集索引)
-
创建降序索引
-- 按 Salary 降序索引CREATE INDEX IX_Employees_Salary_DescON Employees(Salary DESC);→ 加速
ORDER BY Salary DESC
7.1.4 INCLUDE
INCLUDE 是 CREATE INDEX 语句中的一个重要子句,用于在非聚集索引(Nonclustered Index) 的叶级别(Leaf Level) 包含额外的非键列,从而创建覆盖索引(Covering Index),避免“回表查询”,显著提升查询性能。
基本语法
CREATE [UNIQUE] NONCLUSTERED INDEX 索引名ON 表名 (键列1, 键列2, ...) -- 索引键列(用于搜索、排序)INCLUDE (非键列1, 非键列2, ...) -- 包含列(仅存储在叶节点,不参与索引树结构)⚠️ 注意:
INCLUDE只能用于非聚集索引- 包含列不参与索引排序和查找,只用于覆盖查询
- 包含列可以是大对象类型(如
VARCHAR(MAX),NVARCHAR(MAX),VARBINARY(MAX)),而键列不能
核心作用
避免回表查询(Bookmark Lookup / Key Lookup),实现覆盖索引
示例
-- 索引:IX_Category_Item ON (类别, 商品号) INCLUDE (商品名, 单价)SELECT 商品号, 商品名, 单价FROM 商品表WHERE 类别 = '食品'ORDER BY 商品号;→ 执行计划:
- 索引 Seek(按
类别查找) - 索引已按
商品号排序 → 无需 Sort 商品名,单价直接从索引叶节点读取 → 无回表
→ 性能极佳(一次索引扫描)
优势
| 优势 | 说明 |
|---|---|
| 避免回表 | 减少 I/O,提升查询速度 |
| 支持大对象列 | 可 INCLUDE VARCHAR(MAX), TEXT 等(键列不支持) |
| 不增加索引树深度 | 包含列不参与索引树结构,不影响查找效率 |
| 灵活性高 | 可自由组合键列和包含列 |
注意事项
- 只适用于非聚集索引
-- ❌ 错误:聚集索引不支持 INCLUDECREATE CLUSTERED INDEX IX_Clustered ON 表(列) INCLUDE (其他列);- 包含列不参与排序和过滤
-- 索引:IX_Test ON (A) INCLUDE (B)SELECT * FROM 表 WHERE B = '值'; -- ❌ 无法使用索引(B 不是键列)SELECT * FROM 表 ORDER BY B; -- ❌ 无法利用索引排序- 更新包含列也会维护索引
- 虽然包含列不参与索引树,但更新这些列时,索引叶节点仍需更新
- 会增加
UPDATE/DELETE开销
- 索引大小增加
- 包含列会增加索引的存储空间
- 需权衡查询性能 vs 存储/维护成本
典型应用场景
| 场景 | 示例 |
|---|---|
| 覆盖查询 | SELECT A, B, C FROM 表 WHERE X = ? → 索引 (X) INCLUDE (A, B, C) |
| 避免回表 | 查询列不在 WHERE/ORDER BY 中,但需返回 → 用 INCLUDE |
| 大对象列查询 | SELECT Title, Content FROM Articles WHERE Category = 'Tech' → Content 用 INCLUDE |
| 报表查询 | 返回多列,但过滤条件简单 → INCLUDE 非过滤列 |
7.2 SQL 架构
在 SQL Server 中,架构(Schema) 是一个非常重要的逻辑容器,用于组织和管理数据库对象(如表、视图、存储过程等)。它帮助实现对象命名空间隔离、权限管理、架构分离等关键功能。
7.2.1 什么是架构(Schema)?
架构(Schema)是数据库中数据库对象的逻辑分组或命名空间。
- ✅ 一个架构包含多个数据库对象(表、视图、函数、存储过程等)
- ✅ 一个对象必须属于且仅属于一个架构
- ✅ 架构名 + 对象名 = 完全限定名(如
dbo.Employees)
7.2.2 架构的核心作用
| 作用 | 说明 |
|---|---|
| 命名空间隔离 | 避免对象名冲突(如 hr.Employees 和 finance.Employees) |
| 权限管理 | 可对架构授予权限,简化对象级权限管理 |
| 逻辑分组 | 按业务模块、部门、功能分组对象(如 sales, inventory) |
| 所有权分离 | 架构所有者 ≠ 对象所有者,实现灵活权限控制 |
7.2.3 默认架构
dbo(Database Owner):默认架构- 如果用户未指定架构,对象默认创建在
dbo下 - 示例:
CREATE TABLE Employees (...)→ 实际是dbo.Employees
7.2.4 常用操作语法
-
创建架构
CREATE SCHEMA hr;CREATE SCHEMA sales AUTHORIZATION User1; -- 指定所有者 -
创建对象时指定架构
CREATE TABLE hr.Employees (ID INT PRIMARY KEY,Name VARCHAR(50));CREATE VIEW sales.OrderSummary ASSELECT * FROM Orders; -
移动对象到其他架构
ALTER SCHEMA sales TRANSFER hr.Employees;→ 将
hr.Employees移动到sales架构 -
删除架构
DROP SCHEMA hr; -
查看架构信息
-- 查看所有架构SELECT * FROM sys.schemas;-- 查看某架构下的对象SELECTs.name AS SchemaName,o.name AS ObjectName,o.type_desc AS ObjectTypeFROM sys.schemas sJOIN sys.objects o ON s.schema_id = o.schema_idWHERE s.name = 'hr';
7.2.5 五、架构与权限管理
-
授予架构权限
-- 授予用户对 hr 架构的 SELECT 权限GRANT SELECT ON SCHEMA::hr TO User1;-- 授予用户对 sales 架构的所有权限GRANT ALL ON SCHEMA::sales TO User2;→ 用户对架构内所有对象自动拥有相应权限
-
撤销架构权限
REVOKE SELECT ON SCHEMA::hr FROM User1;
7.2.6 架构 vs 用户 vs 数据库
| 概念 | 说明 | 关系 |
|---|---|---|
| 数据库(Database) | 最高层级,包含多个架构 | 1 个数据库 → 多个架构 |
| 架构(Schema) | 逻辑容器,包含多个对象 | 1 个架构 → 多个对象 |
| 用户(User) | 数据库访问主体,可拥有架构 | 1 个用户 → 可拥有多个架构 |
✅ 示例:
CREATE USER Alice FOR LOGIN AliceLogin;CREATE SCHEMA hr AUTHORIZATION Alice; -- Alice 拥有 hr 架构CREATE TABLE hr.Employees (...); -- 表属于 hr 架构7.3 视图
视图(View) 是 SQL 中一种非常重要的数据库对象,它是一个虚拟表,其内容由查询定义。视图不存储实际数据(除非是索引视图),而是基于一个或多个表(或其他视图)的 SELECT 语句动态生成结果。
7.3.1 什么是视图?
视图 = 存储的 SELECT 查询
- ✅ 视图是虚拟表,不占用额外存储空间(标准视图)
- ✅ 数据来源于基础表,查询视图时动态执行 SELECT
- ✅ 可以像表一样被查询(SELECT)、更新(部分情况)
- ✅ 用于简化复杂查询、权限控制、数据抽象
7.3.2 视图的核心作用
| 作用 | 说明 |
|---|---|
| 简化复杂查询 | 将多表 JOIN、聚合等封装为简单视图 |
| 权限控制 | 只暴露部分列/行给用户,隐藏敏感数据 |
| 数据抽象 | 屏蔽底层表结构变化,提供稳定接口 |
| 逻辑数据整合 | 合并多个表的数据,提供统一视图 |
| 向后兼容 | 表结构变更后,用视图保持旧接口 |
7.3.3 基本语法
-
创建视图
CREATE VIEW 视图名 [(列名列表)]ASSELECT 语句[WITH CHECK OPTION];⚠️ 注意:
WITH CHECK OPTION:确保通过视图的更新/插入符合视图 WHERE 条件- 列名列表可选(默认用 SELECT 列名)
-
查询视图
SELECT * FROM 视图名;
→ 像查询表一样使用
-
修改视图
ALTER VIEW 视图名ASSELECT 语句; -
删除视图
DROP VIEW 视图名;
7.3.4 举例说明
假设有一个员工表 Employees 和部门表 Departments:
CREATE TABLE Employees ( EmployeeID INT PRIMARY KEY, Name VARCHAR(50), Salary DECIMAL(10,2), DeptID INT);
CREATE TABLE Departments ( DeptID INT PRIMARY KEY, DeptName VARCHAR(50));-
创建简化查询的视图
CREATE VIEW EmployeeView ASSELECTe.EmployeeID,e.Name,e.Salary,d.DeptNameFROM Employees eJOIN Departments d ON e.DeptID = d.DeptID;→ 查询时只需:
SELECT * FROM EmployeeView WHERE DeptName = 'Sales'; -
创建权限控制视图
-- 只暴露非敏感列CREATE VIEW PublicEmployeeView ASSELECT EmployeeID, Name, DeptNameFROM EmployeeView;→ 授权给普通用户,隐藏 Salary
-
带 CHECK OPTION 的视图
CREATE VIEW HighSalaryView ASSELECT * FROM Employees WHERE Salary > 5000WITH CHECK OPTION;→ 通过此视图插入/更新数据时,必须满足
Salary > 5000INSERT INTO HighSalaryView (EmployeeID, Name, Salary, DeptID)VALUES (101, 'Alice', 4000, 1); -- ❌ 失败!违反 CHECK OPTION
7.3.5 注意事项
-
视图不存储数据(标准视图)
- 查询视图时动态执行 SELECT
- 性能取决于底层查询复杂度
-
可更新视图的限制
不能包含:
DISTINCT,GROUP BY,HAVING, 聚合函数UNION, 子查询- 多表 JOIN(部分数据库支持简单 JOIN)
- 必须直接映射到单表的行
-
索引视图(物化视图)
- SQL Server 支持在视图上建索引(唯一聚集索引) → 数据物理存储
- 要求严格(SCHEMABINDING、确定性查询等)
- 性能高,但维护成本高
CREATE VIEW IndexedView WITH SCHEMABINDING ASSELECT DeptID, COUNT_BIG(*) AS EmployeeCountFROM dbo.EmployeesGROUP BY DeptID;CREATE UNIQUE CLUSTERED INDEX IX_IndexedView ON IndexedView(DeptID);
7.3.6 索引视图(物化视图)
索引视图(Indexed View),也称物化视图(Materialized View),是 SQL Server 中一种特殊的视图 —— 它将视图的查询结果物理存储在数据库中,而不是每次查询时动态计算。
- 普通视图 = 虚拟表(只存定义)
- 索引视图 = 真实存储数据的表(通过创建索引实现)
核心目的:
提高复杂查询(尤其是多表 JOIN、GROUP BY 聚合)的性能
通过预计算并物理存储结果,避免重复执行昂贵的 JOIN 和聚合操作。
创建要求(SQL Server):
- 视图必须使用
WITH SCHEMABINDING - 第一个索引必须是
UNIQUE CLUSTERED INDEX - SELECT 语句有限制(不能有 OUTER JOIN、DISTINCT、子查询等)
- 聚合函数必须配合
COUNT_BIG(*)
-
- 适用场景:**
- 数据仓库、报表系统(读多写少)
- 频繁执行的复杂 JOIN 或聚合查询
- 低基数度(Low Cardinality)列的 GROUP BY(如按月份、地区汇总)
不适用场景:
- 基础数据频繁更新(维护成本高)
- 高基数度 GROUP BY(如按用户ID聚合,存储膨胀)
- 简单查询(无性能提升必要)
优势:
- 查询性能大幅提升(直接读取预计算结果)
- 减少 CPU 和 I/O 开销
- 对应用程序透明(仍用原视图名查询)
代价:
- 占用额外存储空间
- 增加 INSERT/UPDATE/DELETE 的维护开销
- 创建和维护有严格语法限制
示例:
CREATE VIEW SalesSummary WITH SCHEMABINDING ASSELECT ProductID, SUM(SalesAmount) AS TotalSales, COUNT_BIG(*) AS CountRowsFROM dbo.SalesGROUP BY ProductID;
CREATE UNIQUE CLUSTERED INDEX IX_SalesSummary ON SalesSummary(ProductID);→ 此后查询 SalesSummary 将直接读取物化数据,无需重新聚合!
7.4 创建分区表
在 SQL Server 中,创建分区表是一个系统性的过程,目的是将大表的数据按某个列的值范围(或列表)水平分割,并可以将不同分区存储在不同文件组上,从而提升查询性能、简化数据管理(如按时间归档)。
7.4.1 创建分区表的标准步骤(3步)
🎯 核心顺序:先函数 → 再方案 → 最后建表
7.4.2 第一步:创建分区函数(Partition Function)
作用:定义如何分区 —— 按什么列、什么范围、边界值如何划分
语法:
CREATE PARTITION FUNCTION 分区函数名 (数据类型)AS RANGE [LEFT | RIGHT]FOR VALUES (边界值1, 边界值2, ...);RANGE LEFT:边界值属于左侧分区RANGE RIGHT:边界值属于右侧分区(更常用)
示例:
-- 按订单日期分区:2020年前、2020-2021、2021年后CREATE PARTITION FUNCTION PF_OrderDate (DATE)AS RANGE RIGHTFOR VALUES ('2020-01-01', '2021-01-01');→ 会创建 3 个分区:
- < ‘2020-01-01’
- = ‘2020-01-01’ AND < ‘2021-01-01’
- = ‘2021-01-01’
7.4.3 第二步:创建分区方案(Partition Scheme)
作用:定义分区数据存储在哪个文件组 —— 将分区函数生成的分区映射到物理存储
语法:
CREATE PARTITION SCHEME 分区方案名AS PARTITION 分区函数名TO (文件组1, 文件组2, ..., 文件组N);⚠️ 文件组数量 = 分区数量 + 1
(因为 N 个边界值 → N+1 个分区)
示例:
-- 假设已创建文件组 FG2019, FG2020, FG2021CREATE PARTITION SCHEME PS_OrderDateAS PARTITION PF_OrderDateTO (FG2019, FG2020, FG2021);→ 映射关系:
- 分区1(<2020) → FG2019
- 分区2(2020~2021) → FG2020
- 分区3(>=2021) → FG2021
✅ 也可以多个分区映射到同一文件组:
TO (FG_Hot, FG_Hot, FG_Cold); -- 前两个分区放热数据文件组
7.4.4 第三步:创建分区表(使用分区方案)
作用:创建实际的表,并指定按哪个列、使用哪个分区方案进行分区
语法:
CREATE TABLE 表名 ( 列1 数据类型, 列2 数据类型, ...) ON 分区方案名(分区列);示例:
CREATE TABLE Orders ( OrderID INT IDENTITY(1,1) PRIMARY KEY, OrderDate DATE NOT NULL, CustomerID INT, Amount DECIMAL(10,2)) ON PS_OrderDate(OrderDate); -- 按 OrderDate 分区→ 数据会根据 OrderDate 自动分配到对应分区
7.4.5 完整示例(从零开始)
-- 1. 创建文件组(可选,如果已有可跳过)ALTER DATABASE MyDB ADD FILEGROUP FG2019;ALTER DATABASE MyDB ADD FILEGROUP FG2020;ALTER DATABASE MyDB ADD FILEGROUP FG2021;
-- 2. 向文件组添加文件ALTER DATABASE MyDBADD FILE ( NAME = 'MyDB_FG2019', FILENAME = 'D:\\Data\\MyDB_FG2019.ndf', SIZE = 100MB) TO FILEGROUP FG2019;
-- (同样为 FG2020, FG2021 添加文件...)
-- 3. 创建分区函数CREATE PARTITION FUNCTION PF_OrderDate (DATE)AS RANGE RIGHT FOR VALUES ('2020-01-01', '2021-01-01');
-- 4. 创建分区方案CREATE PARTITION SCHEME PS_OrderDateAS PARTITION PF_OrderDateTO (FG2019, FG2020, FG2021);
-- 5. 创建分区表CREATE TABLE Orders ( OrderID INT IDENTITY(1,1) PRIMARY KEY, OrderDate DATE NOT NULL, CustomerID INT, Amount DECIMAL(10,2)) ON PS_OrderDate(OrderDate);
-- 6. 创建对齐索引(推荐)CREATE CLUSTERED INDEX IX_Orders_DateON Orders(OrderDate)ON PS_OrderDate(OrderDate);7.6 分区表
分区表(Partitioned Table) 是数据库中用于管理超大表的一种重要技术。它将一个大表水平分割成多个更小、更易管理的物理部分(称为“分区”),但逻辑上仍是一个表。分区表在查询性能、数据维护、存储管理等方面具有显著优势。
7.6.1 什么是分区表?
分区表 = 一个逻辑表 + 多个物理分区
- ✅ 逻辑上:仍是一个表,应用程序无需修改
- ✅ 物理上:数据按分区键(如日期、地区)分布到不同分区
- ✅ 透明性:查询时无需指定分区,优化器自动裁剪
7.6.2 为什么使用分区表?
| 问题 | 分区表解决方案 |
|---|---|
| 大表查询慢 | 分区裁剪 → 只扫描相关分区 |
| 数据维护难 | 可单独备份/删除/归档分区 |
| 存储成本高 | 热数据放SSD,冷数据放HDD |
| 索引重建慢 | 可按分区重建索引 |
7.6.3 分区表的核心概念
- 分区键(Partition Key)
- 用于分区的列(如
OrderDate,Region) - 通常是WHERE 条件中高频使用的列
- 用于分区的列(如
- 分区函数(Partition Function)
- 定义如何分区(边界值、范围)
- 示例:
RANGE RIGHT FOR VALUES ('2020-01-01', '2021-01-01')
- 分区方案(Partition Scheme)
- 定义分区存储位置(映射到文件组)
- 示例:
TO (FG2019, FG2020, FG2021)
- 分区裁剪(Partition Pruning)
- 查询时,优化器自动跳过无关分区
- 性能提升的关键
7.6.4 分区类型(SQL Server)
-
范围分区(Range Partitioning)
- 按值范围分区(最常用)
- 示例:按年份、月份分区
CREATE PARTITION FUNCTION PF_Date (DATE)AS RANGE RIGHT FOR VALUES ('2020-01-01', '2021-01-01');→ 分区:
<2020,2020-2021,>=2021 -
列表分区(List Partitioning)
- 按离散值分区(如地区、状态)
- SQL Server 通过
RANGE+ 单值模拟
CREATE PARTITION FUNCTION PF_Region (VARCHAR(10))AS RANGE RIGHT FOR VALUES ('East', 'West');→ 分区:
<East,East-West,>=West -
哈希分区(Hash Partitioning)
- 按哈希值分区(均匀分布)
- SQL Server 不直接支持,需用计算列 + 范围分区模拟
7.6.5 创建分区表步骤(SQL Server)
步骤 1:创建文件组(可选)
ALTER DATABASE MyDB ADD FILEGROUP FG2019;ALTER DATABASE MyDB ADD FILEGROUP FG2020;步骤 2:创建分区函数
CREATE PARTITION FUNCTION PF_OrderDate (DATE)AS RANGE RIGHT FOR VALUES ('2020-01-01', '2021-01-01');步骤 3:创建分区方案
CREATE PARTITION SCHEME PS_OrderDateAS PARTITION PF_OrderDateTO (FG2019, FG2020, FG2021); -- 3个文件组对应3个分区步骤 4:创建分区表
CREATE TABLE Orders ( OrderID INT IDENTITY(1,1), OrderDate DATE NOT NULL, Amount DECIMAL(10,2)) ON PS_OrderDate(OrderDate); -- 按 OrderDate 分区7.7 IDENTITY 属性
在 SQL Server 中,IDENTITY 是一个非常重要的属性,用于自动为表的列生成唯一的、递增的数值。它常用于主键列,确保每行数据都有唯一标识符,无需手动赋值。
7.7.1 什么是 IDENTITY?
IDENTITY 属性:
为表的整数类型列(如 INT, BIGINT)自动分配唯一、递增的值。
- ✅ 自动递增(Auto-increment)
- ✅ 保证唯一性(在单列上)
- ✅ 通常用于主键
- ✅ 无需手动插入值(可显式插入,但需特殊设置)
7.7.2 基本语法
-
创建表时定义 IDENTITY 列
CREATE TABLE TableName (ID INT IDENTITY(seed, increment) PRIMARY KEY,Column1 VARCHAR(50),...);seed:起始值(默认 1)increment:步长(默认 1)
示例:
CREATE TABLE Employees (EmployeeID INT IDENTITY(1,1) PRIMARY KEY, -- 从1开始,步长1Name VARCHAR(50));→ 插入数据时无需指定
EmployeeID:INSERT INTO Employees (Name) VALUES ('Alice'); -- EmployeeID 自动为 1INSERT INTO Employees (Name) VALUES ('Bob'); -- EmployeeID 自动为 2 -
查看当前 IDENTITY 值
-- 查看最后插入的 IDENTITY 值SELECT @@IDENTITY; -- 不推荐(可能返回触发器中的值)SELECT SCOPE_IDENTITY(); -- ✅ 推荐(当前作用域)SELECT IDENT_CURRENT('TableName'); -- 指定表的当前值✅ 推荐使用 SCOPE_IDENTITY(),避免触发器干扰
-
重置 IDENTITY 值
DBCC CHECKIDENT ('TableName', RESEED, new_value);示例:
-- 重置 EmployeeID 从 1000 开始DBCC CHECKIDENT ('Employees', RESEED, 1000);→ 下次插入时,ID 从 1001 开始
7.7.3 注意事项
-
数据类型限制
- 只能用于整数类型:
TINYINT,SMALLINT,INT,BIGINT,NUMERIC/DECIMAL(0 位小数)
- 只能用于整数类型:
-
每表只能有一个 IDENTITY 列
-- ❌ 错误:一个表不能有多个 IDENTITY 列CREATE TABLE Test (ID1 INT IDENTITY(1,1),ID2 INT IDENTITY(1,1) -- 报错!); -
显式插入 IDENTITY 值
默认不允许显式插入,需先启用
IDENTITY_INSERT:SET IDENTITY_INSERT Employees ON;INSERT INTO Employees (EmployeeID, Name) VALUES (100, 'Charlie');SET IDENTITY_INSERT Employees OFF;⚠️ 注意:
- 同一会话中,一次只能对一个表启用
IDENTITY_INSERT - 插入值不能与现有值冲突(违反唯一性)
- 同一会话中,一次只能对一个表启用
-
删除数据后 IDENTITY 不回滚
DELETE FROM Employees; -- 删除所有数据INSERT INTO Employees (Name) VALUES ('David'); -- ID 继续递增(不会从1开始)→
DELETE不重置 IDENTITY,需用TRUNCATE或DBCC CHECKIDENT
7.8 数据页
在 SQL Server 中,数据页(Data Page) 是数据库存储的最小 I/O 单位,也是数据在磁盘和内存中管理的基本单元。理解数据页的结构和工作机制,对数据库性能调优、存储设计、索引优化至关重要。
7.8.1 什么是数据页?
数据页(Data Page) 是 SQL Server 中用于存储表数据、索引数据的8KB(8192字节) 固定大小的物理存储单元。
- ✅ 每页 = 8192 字节 = 8KB
- ✅ 页是读写磁盘的最小单位(即使只改1字节,也要读/写整个页)
- ✅ 页属于数据文件(.mdf/.ndf),由 SQL Server 自动管理
7.8.2 数据页的结构(8192 字节)
| 部分 | 大小 | 说明 |
|---|---|---|
| 页头(Page Header) | 96 字节 | 存储页的元数据(如页类型、页ID、LSN等) |
| 数据行(Data Rows) | ≤ 8060 字节 | 存储实际的表数据或索引记录 |
| 行偏移数组(Row Offset Array) | 可变(每行2字节) | 记录每行数据在页中的起始位置(从页尾向前增长) |
| 空闲空间(Free Space) | 剩余字节 | 用于插入新行或更新现有行 |
📌 关键限制:
- 单行最大 8060 字节(因为数据行区域最大 8060B)
- 如果行 > 8060B → 使用 行溢出(Row Overflow) 或 LOB 存储
7.8.3 数据页存储示例
假设有一个表:
CREATE TABLE Employees ( ID INT, Name CHAR(1000), -- 1000字节 Bio VARCHAR(8000) -- 可能溢出);场景1:行大小 ≤ 8060B
INSERT INTO Employees (ID, Name) VALUES (1, REPLICATE('A', 1000));→ 行大小 = 4 (INT) + 1000 (CHAR) = 1004 字节
→ 可存入单页,每页可存多行
场景2:行大小 > 8060B
INSERT INTO Employees (ID, Bio) VALUES (2, REPLICATE('B', 8000));→ 行大小 = 4 + 8000 = 8004 字节 < 8060B → 仍可存入单页
INSERT INTO Employees (ID, Bio) VALUES (3, REPLICATE('C', 8100));→ 行大小 = 4 + 8100 = 8104 字节 > 8060B → 触发行溢出(Row Overflow)
→ SQL Server 将部分数据移到溢出页(Row Overflow Page),原页存指针
7.8.4 数据页的类型
SQL Server 有多种页类型,数据页只是其中之一:
| 页类型 | 说明 |
|---|---|
| 数据页(Data Page) | 存储堆表或聚集索引的数据行 |
| 索引页(Index Page) | 存储非聚集索引的索引记录 |
| 文本页(Text Page) | 存储 LOB 数据(如 TEXT, IMAGE, VARCHAR(MAX)) |
| GAM/SGAM 页 | 管理区(Extent)分配的元数据页 |
| PFS 页 | 管理页的空闲空间信息 |
7.8.5 数据页与性能
1. ✅ I/O 性能
- 页是最小 I/O 单位 → 读写效率高
- 减少随机 I/O,提高吞吐量
2. ✅ 缓存机制
- SQL Server 将页缓存在缓冲池(Buffer Pool) 中
- 热点页常驻内存 → 减少磁盘 I/O
3. ✅ 填充因子(Fill Factor)
- 控制页的填充比例(如 80%),预留空间减少页分裂
- 适用于频繁更新的表
CREATE INDEX IX_Name ON Employees(Name) WITH (FILLFACTOR = 80);7.8.6 重要限制
- 单行最大 8060 字节
- 超过则使用行溢出或 LOB 存储
- 影响查询性能(需额外 I/O)
- 页不能跨行存储
- 一行数据必须完整存于单页(除非溢出)
- 所以“每页行数 = FLOOR(8060 / 行大小)”
- 页大小固定
- 无法修改(SQL Server 不支持 4KB/16KB 页)
第八章 数据库后台编程技术
8.1 DBMS 核心模块
数据库管理系统(DBMS, Database Management System)是一个复杂的软件系统,由多个核心模块协同工作,共同完成数据的存储、查询、事务、安全、恢复等关键功能。
8.1.1 DBMS 核心模块概览
一个典型的 DBMS 通常包含以下核心模块:
| 模块 | 主要职责 |
|---|---|
| 1. 查询处理器(Query Processor) | 解析、优化、执行 SQL 查询 |
| 2. 存储管理器(Storage Manager) | 管理数据在磁盘和内存中的存储与访问 |
| 3. 事务管理器(Transaction Manager) | 保证事务的 ACID 特性 |
| 4. 恢复管理器(Recovery Manager) | 系统崩溃后恢复数据一致性 |
| 5. 安全管理器(Security Manager) | 控制用户访问权限和数据安全 |
| 6. 元数据管理器 / 数据字典(Data Dictionary) | 存储数据库结构、约束、权限等元数据 |
8.1.2 各模块详细介绍
-
查询处理器(Query Processor)
职责:接收 SQL 语句 → 解析 → 优化 → 执行 → 返回结果
主要组件:
- DDL 编译器:处理
CREATE,ALTER,DROP等语句,更新数据字典 - DML 编译器:处理
SELECT,INSERT,UPDATE,DELETE- 包含:
- 解析器(Parser):语法/语义检查
- 查询优化器(Query Optimizer):生成最优执行计划
- 查询执行引擎(Execution Engine):执行计划,调用存储管理器
- 包含:
✅ 示例:
SELECT * FROM Employees WHERE Salary > 5000
→ 解析 → 优化(选索引)→ 执行 → 返回结果 - DDL 编译器:处理
-
存储管理器(Storage Manager)
职责:管理数据在磁盘和内存中的物理存储与访问
主要组件:
- 缓冲区管理器(Buffer Manager):
- 管理内存缓冲池(Buffer Pool)
- 将磁盘页读入内存,写回脏页
- 使用 LRU 等策略管理缓存
- 文件管理器(File Manager):
- 管理数据文件、日志文件的读写
- 分配/回收磁盘空间
- 索引管理器(Index Manager):
- 维护 B+树、哈希等索引结构
- 支持快速数据定位
- 访问方法(Access Methods):
- 提供堆文件、聚集索引等访问接口
✅ 示例:
查询执行引擎请求“读取 EmployeeID=1001 的记录” → 存储管理器从磁盘或缓存中返回数据页 - 缓冲区管理器(Buffer Manager):
-
事务管理器(Transaction Manager)
职责:保证事务的 ACID 特性(原子性、一致性、隔离性、持久性)
主要组件:
- 并发控制管理器(Concurrency Control Manager):
- 实现锁机制(Locking)、多版本并发控制(MVCC)
- 防止脏读、不可重复读、幻读
- 日志管理器(Log Manager):
- 记录事务日志(Write-Ahead Logging, WAL)
- 支持事务回滚和崩溃恢复
✅ 示例:
事务 T1 更新数据 → 加锁 → 写日志 → 提交 → 释放锁 - 并发控制管理器(Concurrency Control Manager):
-
恢复管理器(Recovery Manager)
职责:系统崩溃后,利用日志恢复数据一致性
主要机制:
- 检查点(Checkpoint):定期记录数据库状态,减少恢复时间
- REDO:重做已提交但未写入磁盘的事务
- UNDO:回滚未提交的事务
✅ 示例:
系统崩溃 → 重启时读取日志 → REDO 已提交事务 → UNDO 未提交事务 → 数据库恢复一致状态 -
安全管理器(Security Manager)
职责:控制用户访问权限,保障数据安全
主要功能:
- 身份认证(Authentication):验证用户身份
- 授权管理(Authorization):
GRANT/REVOKE权限 - 审计(Auditing):记录用户操作日志
- 数据加密:透明数据加密(TDE)、列级加密
✅ 示例:
用户 Alice 登录 → 验证密码 → 检查是否有SELECT权限 → 记录操作日志 -
元数据管理器 / 数据字典(Data Dictionary)
职责:存储数据库的元数据(Metadata),即“关于数据的数据”
存储内容:
- 表结构(列名、数据类型、约束)
- 索引定义
- 用户权限
- 统计信息(用于查询优化)
- 存储位置(文件组、分区)
✅ 示例:
CREATE TABLE Employees (...)→ DDL 编译器将表结构存入数据字典 → 查询优化器使用统计信息生成执行计划
8.1.3 模块协作流程(以 SELECT 查询为例)
- 用户提交 SQL → 查询处理器接收
- 解析 & 优化 → 生成执行计划
- 执行引擎 → 调用存储管理器读取数据
- 缓冲区管理器 → 从磁盘或缓存返回数据页
- 并发控制 → 检查锁或 MVCC 版本
- 返回结果 → 用户
8.1.4 总结对比表
| 模块 | 核心组件 | 关键技术 |
|---|---|---|
| 查询处理器 | DDL/DML 编译器、优化器、执行引擎 | SQL 解析、查询优化、执行计划 |
| 存储管理器 | 缓冲区、文件、索引管理器 | 缓存、B+树、堆文件 |
| 事务管理器 | 并发控制、日志管理器 | 锁、MVCC、WAL |
| 恢复管理器 | 检查点、REDO/UNDO | 崩溃恢复、日志回放 |
| 安全管理器 | 认证、授权、审计 | GRANT/REVOKE、加密 |
| 数据字典 | 系统表、统计信息 | 元数据存储、查询优化依据 |
8.2 存储过程
在 SQL 中,存储过程(Stored Procedure) 是一组预编译的 SQL 语句集合,存储在数据库中,可通过名称调用执行。它用于封装业务逻辑、提高性能、增强安全性。
不同数据库的语法略有不同,下面将以 SQL Server 为主
8.2.1 SQL Server 创建存储过程
基本语法:
CREATE PROCEDURE 存储过程名 [@参数1 数据类型 [= 默认值] [OUTPUT]], [@参数2 数据类型 [= 默认值] [OUTPUT]], ...ASBEGIN -- SQL 语句END⚠️ 注意:
- 参数以
@开头OUTPUT表示输出参数BEGIN...END可选(单语句时可省略)
示例 1:无参数存储过程
CREATE PROCEDURE GetEmployeeCountASBEGIN SELECT COUNT(*) AS TotalEmployees FROM Employees;END→ 调用:
EXEC GetEmployeeCount;-- 或EXECUTE GetEmployeeCount;示例 2:带输入参数
CREATE PROCEDURE GetEmployeesByDept @DepartmentID INTASBEGIN SELECT * FROM Employees WHERE DeptID = @DepartmentID;END→ 调用:
EXEC GetEmployeesByDept @DepartmentID = 10;示例 3:带输出参数
CREATE PROCEDURE GetEmployeeCountByDept @DepartmentID INT, @EmployeeCount INT OUTPUTASBEGIN SELECT @EmployeeCount = COUNT(*) FROM Employees WHERE DeptID = @DepartmentID;END→ 调用:
DECLARE @Count INT;EXEC GetEmployeeCountByDept @DepartmentID = 10, @EmployeeCount = @Count OUTPUT;PRINT @Count;-
- 示例 4:带默认参数**
CREATE PROCEDURE GetEmployeesBySalary @MinSalary DECIMAL(10,2) = 5000.00ASBEGIN SELECT * FROM Employees WHERE Salary >= @MinSalary;END→ 调用(使用默认值):
EXEC GetEmployeesBySalary; -- 默认 5000→ 调用(指定值):
EXEC GetEmployeesBySalary @MinSalary = 8000;8.2.2 修改存储过程
ALTER PROCEDURE 存储过程名ASBEGIN -- 新的 SQL 语句END✅ 用 ALTER 修改,保留权限和依赖关系
8.2.3 删除存储过程
DROP PROCEDURE 存储过程名;8.2.4 注意事项
-
错误处理
CREATE PROCEDURE SafeInsert@Name VARCHAR(50)ASBEGINBEGIN TRYINSERT INTO Employees (Name) VALUES (@Name);END TRYBEGIN CATCHPRINT 'Error: ' + ERROR_MESSAGE();END CATCHEND -
返回值
- SQL Server 存储过程可返回整数(RETURN),但通常用 OUTPUT 参数或结果集
- MySQL/PostgreSQL 函数可直接返回值
-
权限控制
GRANT EXECUTE ON PROCEDURE::GetEmployeeCount TO User1;
8.3 数据类型
SQL Server 提供了丰富多样的数据类型(Data Types),用于定义表中列、变量、参数等的数据格式和存储方式。合理选择数据类型对性能、存储空间、数据完整性至关重要。
8.3.1 SQL Server 数据类型分类
SQL Server 数据类型主要分为以下几大类:
| 大类 | 子类 | 说明 |
|---|---|---|
| 精确数值型 | INT, DECIMAL, NUMERIC, MONEY | 存储精确数字(整数、小数) |
| 近似数值型 | FLOAT, REAL | 存储浮点数(科学计数法) |
| 日期和时间型 | DATE, TIME, DATETIME2, DATETIMEOFFSET | 存储日期、时间、时区 |
| 字符型 | CHAR, VARCHAR, TEXT | 存储字符串(固定/可变长度) |
| Unicode 字符型 | NCHAR, NVARCHAR, NTEXT | 存储 Unicode 字符串(支持中文等) |
| 二进制型 | BINARY, VARBINARY, IMAGE | 存储二进制数据(图片、文件等) |
| 其他特殊类型 | BIT, UNIQUEIDENTIFIER, XML, JSON | 存储布尔、GUID、XML、JSON 等 |
8.3.2 常用数据类型详解
-
精确数值型
类型 范围 存储空间 说明 TINYINT0 ~ 255 1 字节 小整数 SMALLINT-32,768 ~ 32,767 2 字节 中等整数 INT-2,147,483,648 ~ 2,147,483,647 4 字节 常用整数(主键推荐) BIGINT-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 8 字节 大整数 DECIMAL(p,s)/NUMERIC(p,s)最多 38 位精度 5~17 字节 精确小数(p=总位数,s=小数位) MONEY-922,337,203,685,477.5808 ~ 922,337,203,685,477.5807 8 字节 货币类型(不推荐,用 DECIMAL) SMALLMONEY-214,748.3648 ~ 214,748.3647 4 字节 小额货币 ✅ 推荐:财务数据用 DECIMAL(18,2),避免 FLOAT 精度问题
-
近似数值型
类型 说明 存储空间 FLOAT(n)浮点数(n=精度,1~53) 4/8 字节 REAL等同于 FLOAT(24)4 字节 ⚠️ 注意:FLOAT 是近似值,不适合财务计算
-
日期和时间型
类型 格式 范围 精度 存储空间 DATEYYYY-MM-DD 0001-01-01 ~ 9999-12-31 1 天 3 字节 TIMEHH:MM .nnnnnnn 00:00:00.0000000 ~ 23:59:59.9999999 100 纳秒 3~5 字节 DATETIME2YYYY-MM-DD HH:MM .nnnnnnn 0001-01-01 ~ 9999-12-31 100 纳秒 6~8 字节 DATETIMEYYYY-MM-DD HH:MM .nnn 1753-01-01 ~ 9999-12-31 3.33 毫秒 8 字节 DATETIMEOFFSETYYYY-MM-DD HH:MM .nnnnnnn [+/-]HH同 DATETIME2 100 纳秒 8~10 字节 SMALLDATETIMEYYYY-MM-DD HH:MM 1900-01-01 ~ 2079-06-06 1 分钟 4 字节 ✅ 推荐:
- 用
DATE存日期,TIME存时间 - 用
DATETIME2替代DATETIME(精度更高,范围更大) - 用
DATETIMEOFFSET存带时区的时间
- 用
-
字符型
类型 说明 最大长度 存储空间 CHAR(n)固定长度非 Unicode 字符串 8000 n 字节 VARCHAR(n)可变长度非 Unicode 字符串 8000 实际长度 + 2 字节 VARCHAR(MAX)大文本(替代 TEXT) 2^31-1 实际长度 + 2 字节 TEXT大文本(已废弃) 2^31-1 不推荐使用 ✅ 推荐:用 VARCHAR 替代 CHAR(节省空间),用 VARCHAR(MAX) 替代 TEXT
-
Unicode 字符型
类型 说明 最大长度 存储空间 NCHAR(n)固定长度 Unicode 字符串 4000 2n 字节 NVARCHAR(n)可变长度 Unicode 字符串 4000 2×实际长度 + 2 字节 NVARCHAR(MAX)大 Unicode 文本(替代 NTEXT) 2^30-1 2×实际长度 + 2 字节 NTEXT大 Unicode 文本(已废弃) 2^30-1 不推荐使用 ✅ 推荐:中文等 Unicode 字符用 NVARCHAR,避免乱码
-
二进制型
类型 说明 最大长度 存储空间 BINARY(n)固定长度二进制数据 8000 n 字节 VARBINARY(n)可变长度二进制数据 8000 实际长度 + 2 字节 VARBINARY(MAX)大二进制数据(替代 IMAGE) 2^31-1 实际长度 + 2 字节 IMAGE大二进制数据(已废弃) 2^31-1 不推荐使用 ✅ 推荐:用 VARBINARY(MAX) 替代 IMAGE
-
其他特殊类型
类型 说明 存储空间 BIT布尔值(0, 1, NULL) 1 位(每 8 列 1 字节) UNIQUEIDENTIFIERGUID(全局唯一标识符) 16 字节 XMLXML 数据 最大 2GB JSONJSON 数据(SQL Server 2016+) 存储为 NVARCHAR(MAX) CURSOR游标引用 不用于表列 TABLE表变量 内存中临时表 ✅ 推荐:
- 主键用
INT IDENTITY或UNIQUEIDENTIFIER - 配置数据用
XML或JSON
- 主键用
8.4 触发器
在 SQL Server 中,触发器(Trigger) 是一种特殊的存储过程,它在特定数据操作(INSERT、UPDATE、DELETE)发生时自动执行。根据触发时机,触发器分为:
✅ 前触发器(AFTER Trigger) —— 数据变更后执行(默认)
✅ 后触发器(INSTEAD OF Trigger) —— 替代原操作执行(“后”是误称,实际是“替代”)
8.4.1 AFTER Trigger(后触发器)
在 INSERT、UPDATE、DELETE 操作成功执行后触发
语法:
CREATE TRIGGER trigger_nameON table_nameAFTER|FOR INSERT, UPDATE, DELETEASBEGIN -- 触发器逻辑END⚠️ 注意:
AFTER是默认关键字(可省略),也可换用FOR- 触发器执行时,数据已变更
- 可访问
inserted和deleted临时表- 不能在视图上定义 AFTER 型触发器
示例:审计日志
CREATE TRIGGER trg_Employee_AuditON EmployeesAFTER UPDATEASBEGIN INSERT INTO AuditLog (Action, EmployeeID, ChangeDate) SELECT 'UPDATE', inserted.EmployeeID, GETDATE() FROM inserted;END→ 当 Employees 表更新后,自动记录审计日志
inserted / deleted 表说明
| 操作 | inserted 表 | deleted 表 |
|---|---|---|
| INSERT | 新插入的行 | 空 |
| UPDATE | 更新后的新行 | 更新前的旧行 |
| DELETE | 空 | 被删除的行 |
8.4.2 INSTEAD OF Trigger(替代触发器,常被误称“前触发器”)
替代原 INSERT、UPDATE、DELETE 操作,执行自定义逻辑
语法:
CREATE TRIGGER trigger_nameON table_nameINSTEAD OF INSERT, UPDATE, DELETEASBEGIN -- 自定义逻辑(原操作不会执行)END⚠️ 注意:
- 原操作不会执行,除非在触发器中显式调用
- 常用于视图(使不可更新视图可更新)
- 可访问
inserted和deleted表- 一张表只能建立一个前触发器
示例:防止删除管理员
CREATE TRIGGER trg_Prevent_Delete_AdminON EmployeesINSTEAD OF DELETEASBEGIN -- 删除非管理员员工 DELETE e FROM Employees e INNER JOIN deleted d ON e.EmployeeID = d.EmployeeID WHERE e.Role != 'Admin';
-- 提示不能删除管理员 IF EXISTS (SELECT 1 FROM deleted WHERE Role = 'Admin') BEGIN PRINT 'Cannot delete admin users!'; ENDEND→ 尝试删除管理员时,触发器阻止操作并提示
示例:更新视图
CREATE VIEW EmployeeView ASSELECT e.Name, d.DeptNameFROM Employees eJOIN Departments d ON e.DeptID = d.DeptID;
CREATE TRIGGER trg_Update_EmployeeViewON EmployeeViewINSTEAD OF UPDATEASBEGIN UPDATE Employees SET Name = inserted.Name FROM inserted WHERE Employees.EmployeeID = inserted.EmployeeID;END→ 通过视图更新员工姓名,触发器将操作转为对基表的更新
8.5 游标
游标(Cursor) 是数据库编程中的一个重要概念,它允许你逐行处理查询结果集,而不是一次性操作整个结果集。游标在需要逐行处理、复杂业务逻辑、数据迁移等场景中非常有用。游标由游标结果集和游标当前行指针两部分组成
8.5.1 什么是游标?
游标 = 指向结果集中某一行的指针
- ✅ 用于逐行遍历查询结果
- ✅ 支持读取、修改、删除当前行
- ✅ 适用于过程化编程(如存储过程、触发器)
8.5.2 游标的核心操作
游标的使用通常包含以下步骤:
- 声明游标(DECLARE)
- 打开游标(OPEN)
- 获取数据(FETCH)
- 处理数据(循环)
- 关闭游标(CLOSE)
- 释放游标(DEALLOCATE)
8.5.3 SQL Server 游标语法
-
声明游标
DECLARE 游标名 CURSOR[LOCAL | GLOBAL] -- 作用域[FORWARD_ONLY | SCROLL] -- 滚动方式[STATIC | KEYSET | DYNAMIC | FAST_FORWARD] -- 类型FOR SELECT 语句[FOR UPDATE [OF 列名列表]]; -- 是否可更新⚠️ 常用选项:
LOCAL:局部游标(推荐)FORWARD_ONLY:只能向前滚动(默认,性能好)FAST_FORWARD:只读、向前滚动(性能最佳)FOR UPDATE:允许更新当前行
-
打开游标
OPEN 游标名; -
获取数据(FETCH)
FETCH [NEXT | PRIOR | FIRST | LAST | ABSOLUTE n | RELATIVE n]FROM 游标名INTO @变量1, @变量2, ...;选项 说明 NEXT 下一行(默认) PRIOR 上一行 FIRST 第一行 LAST 最后一行 ABSOLUTE n 从第一行开始的第 n 行(n 可为负) RELATIVE n 从当前行开始的第 n 行(n 可为负) ⚠️ 常用:
FETCH NEXT:获取下一行(最常用)@@FETCH_STATUS:判断是否成功(0=成功,-1=失败,-2=丢失)
-
关闭游标
CLOSE 游标名; -
释放游标
DEALLOCATE 游标名;
8.5.4 完整示例
示例1:遍历员工表,打印姓名
DECLARE @Name NVARCHAR(50);
-- 1. 声明游标DECLARE emp_cursor CURSOR LOCAL FAST_FORWARDFOR SELECT Name FROM Employees;
-- 2. 打开游标OPEN emp_cursor;
-- 3. 获取第一行FETCH NEXT FROM emp_cursor INTO @Name;
-- 4. 循环处理WHILE @@FETCH_STATUS = 0BEGIN PRINT @Name; FETCH NEXT FROM emp_cursor INTO @Name;END
-- 5. 关闭游标CLOSE emp_cursor;
-- 6. 释放游标DEALLOCATE emp_cursor;示例2:更新员工工资(可更新游标)
DECLARE @EmployeeID INT, @Salary DECIMAL(10,2);
-- 声明可更新游标DECLARE emp_cursor CURSOR LOCALFOR SELECT EmployeeID, Salary FROM EmployeesFOR UPDATE OF Salary;
OPEN emp_cursor;
FETCH NEXT FROM emp_cursor INTO @EmployeeID, @Salary;
WHILE @@FETCH_STATUS = 0BEGIN -- 工资低于5000的员工加薪10% IF @Salary < 5000 BEGIN UPDATE Employees SET Salary = Salary * 1.1 WHERE CURRENT OF emp_cursor; -- 更新当前行 END
FETCH NEXT FROM emp_cursor INTO @EmployeeID, @Salary;END
CLOSE emp_cursor;DEALLOCATE emp_cursor;8.5.5 游标类型
| 类型 | 说明 | 性能 | 适用场景 |
|---|---|---|---|
| STATIC | 静态游标,数据快照 | 中 | 数据不频繁变化 |
| KEYSET | 键集驱动,可感知更新 | 中 | 需要感知数据更新 |
| DYNAMIC | 动态游标,实时数据 | 低 | 实时性要求高 |
| FAST_FORWARD | 只读、向前滚动 | 高 | 只读遍历(推荐) |
✅ 推荐使用 FAST_FORWARD:性能最佳,适用于大多数只读场景
8.5.6 游标的缺点
| 缺点 | 说明 |
|---|---|
| 性能差 | 逐行处理,比集合操作慢 |
| 资源占用高 | 占用内存、锁资源 |
| 复杂性高 | 代码冗长,易出错 |
| 可维护性差 | 难以调试和优化 |
📌 游标 vs 集合操作:
- 游标:逐行处理(过程化)
- 集合操作:批量处理(声明式,推荐)
8.5.7 何时使用游标?
| 场景 | 说明 |
|---|---|
| 逐行处理 | 需要对每行执行复杂逻辑 |
| 数据迁移 | 从旧系统迁移数据到新系统 |
| 复杂计算 | 每行计算依赖前一行结果 |
| 调用外部API | 每行数据需调用外部服务 |
✅ 替代方案:
- 使用
WHILE循环 +TOP 1- 使用窗口函数(如
ROW_NUMBER())- 使用递归 CTE
8.6 内联表值函数
内联表值函数(Inline Table-Valued Function, iTVF) 是 SQL Server 中一种特殊的用户定义函数,它返回一个表(Table),并且函数体只包含一个 SELECT 语句。它类似于“参数化视图”,在查询优化和性能方面具有显著优势。
8.6.1 什么是内联表值函数?
内联表值函数 = 带参数的视图
- ✅ 返回一个表结果集
- ✅ 函数体只能是一个 SELECT 语句(无 BEGIN…END)
- ✅ 不存储数据,每次调用时动态执行
- ✅ 查询优化器可将其内联展开,与直接写 SELECT 性能相同
8.6.2 基本语法
CREATE FUNCTION 函数名 (@参数1 数据类型, @参数2 数据类型, ...)RETURNS TABLEASRETURN ( SELECT 列1, 列2, ... FROM 表 WHERE 条件);⚠️ 注意:
- 无
BEGIN...END- 无变量声明
- 无复杂逻辑(只能一个 SELECT)
8.6.3 举例说明
示例1:根据部门ID获取员工列表
CREATE FUNCTION GetEmployeesByDept (@DeptID INT)RETURNS TABLEASRETURN ( SELECT EmployeeID, Name, Salary FROM Employees WHERE DeptID = @DeptID);→ 调用:
SELECT * FROM dbo.GetEmployeesByDept(10);→ 等价于:
SELECT EmployeeID, Name, SalaryFROM EmployeesWHERE DeptID = 10;根据日期范围获取销售记录
CREATE FUNCTION GetSalesByDateRange (@StartDate DATE, @EndDate DATE)RETURNS TABLEASRETURN ( SELECT OrderID, ProductID, Amount FROM Sales WHERE OrderDate BETWEEN @StartDate AND @EndDate);→ 调用:
SELECT * FROM dbo.GetSalesByDateRange('2023-01-01', '2023-12-31');8.7 多语句表值函数
8.7.1 什么是多语句表值函数?
多语句表值函数(MSTVF) 是一种返回 完整表结果集 的用户自定义函数,其内部可以包含多个 T-SQL 语句,允许复杂的逻辑处理和数据操作。
与“内嵌表值函数”不同,MSTVF 可以:
- 声明变量
- 使用临时表或表变量
- 执行多步数据处理
- 包含控制流语句(如 IF、WHILE)
对比:三种函数类型
| 类型 | 英文名 | 是否支持多条语句 | 返回形式 | 性能 |
|---|---|---|---|---|
| 标量函数 | Scalar Function | ❌ 否 | 单个值 | 一般较差 |
| 内嵌表值函数 | Inline TVF | ❌ 否(只能一个 SELECT) | 表(无存储) | ⭐⭐⭐ 高(可被优化器展开) |
| ✅ 多语句表值函数 | Multi-Statement TVF | ✅ 是 | 表(有中间存储) | ⭐⭐ 较低(黑盒执行) |
8.7.2 语法结构
CREATE FUNCTION dbo.函数名()RETURNS @ResultTable TABLE ( 列1 数据类型 [约束], 列2 数据类型 [约束], ...)ASBEGIN -- 多条T-SQL语句 INSERT INTO @ResultTable SELECT ... FROM ... WHERE ...;
-- 可添加更多逻辑 IF EXISTS (...) BEGIN UPDATE @ResultTable SET ...; END
RETURN;END;8.7.3 示例:学生成绩统计函数
假设我们有一个 成绩表(学生ID, 课程名, 分数),想创建一个函数返回每个学生的统计信息:
CREATE FUNCTION dbo.GetStudentStats()RETURNS @Result TABLE ( 学生ID INT, 课程数 INT, 平均分 DECIMAL(5,2), 最高分 INT, 状态 NVARCHAR(10))ASBEGIN -- 插入聚合数据 INSERT INTO @Result (学生ID, 课程数, 平均分, 最高分, 状态) SELECT 学生ID, COUNT(*) AS 课程数, AVG(分数*1.0) AS 平均分, MAX(分数) AS 最高分, CASE WHEN AVG(分数) >= 60 THEN '及格' ELSE '不及格' END AS 状态 FROM 成绩表 GROUP BY 学生ID;
-- 可选:后续处理(如修正异常值) UPDATE @Result SET 状态 = '优秀' WHERE 平均分 > 90;
RETURN;END;调用方式:
SELECT * FROM dbo.GetStudentStats();输出示例:
| 学生ID | 课程数 | 平均分 | 最高分 | 状态 |
|---|---|---|---|---|
| 1001 | 5 | 87.6 | 98 | 优秀 |
| 1002 | 4 | 72.5 | 85 | 及格 |
8.7.4 核心特点
| 特点 | 说明 |
|---|---|
| ✅ 返回表结构 | 可作为查询的数据源(SELECT * FROM 函数()) |
| ✅ 支持复杂逻辑 | 可使用变量、循环、条件判断等编程结构 |
| ✅ 封装性强 | 将复杂业务逻辑隐藏在函数内部 |
| ✅ 可重用 | 多个查询可共享同一函数 |
| ⚠️ 性能较低 | SQL Server 将其视为“黑盒”,无法像内嵌函数那样优化 |
| ⚠️ 阻止单次查询优化 | 不能将谓词下推到 MSTVF 内部 |
第九章 安全管理
9.1 GRANT 语句
GRANT 是 SQL 中用于授予用户或角色数据库权限的关键语句,是实现数据库安全控制的核心工具。它属于 DCL(Data Control Language,数据控制语言)。
9.1.1 GRANT 语句的基本语法
GRANT 权限列表ON 对象名|表名TO 用户名或角色名[WITH GRANT OPTION];⚠️ 注意:
权限列表:可以是单个权限或多个权限(逗号分隔)对象名:可以是表、视图、存储过程等(不能是数据库名)WITH GRANT OPTION:允许被授权者再授权给他人(慎用)
9.1.2 核心作用
授予用户或角色对数据库对象的操作权限
9.1.3 常用权限类型
| 权限 | 说明 | 适用对象 |
|---|---|---|
SELECT | 查询数据 | 表、视图 |
INSERT | 插入数据 | 表、视图 |
UPDATE | 更新数据 | 表、视图 |
DELETE | 删除数据 | 表、视图 |
EXECUTE | 执行存储过程/函数 | 存储过程、函数 |
REFERENCES | 创建外键引用 | 表 |
ALL PRIVILEGES | 所有权限 | 表、视图、存储过程等 |
9.1.4 举例说明
假设有一个员工表 Employees 和一个存储过程 GetEmployee:
-
授予用户 Alice 查询权限
GRANT SELECT ON Employees TO Alice;→ Alice 可以执行
SELECT * FROM Employees -
授予用户 Bob 插入和更新权限
GRANT INSERT, UPDATE ON Employees TO Bob;→ Bob 可以执行
INSERT和UPDATE操作 -
授予用户 Carol 执行存储过程的权限
GRANT EXECUTE ON GetEmployee TO Carol;→ Carol 可以执行
EXEC GetEmployee -
授予用户 David 所有权限,并允许他转授
GRANT ALL PRIVILEGES ON Employees TO David WITH GRANT OPTION;→ David 可以执行所有操作,并可将权限授予他人
-
授予角色 SalesRole 查询权限
GRANT SELECT ON Employees TO SalesRole;→ 所有属于
SalesRole的用户自动获得查询权限
9.1.5 System Administrator(sa)
- 是 SQL Server 安装时自动创建的默认系统管理员账户
- 拥有最高权限,可管理所有数据库和服务器设置
- 使用 SQL Server 身份验证时,
sa是默认的管理员账户 - 初始密码在安装时设置,建议修改并妥善保管
9.2 权限管理
SQL 的权限管理(Privilege Management) 是数据库安全的核心机制,用于控制谁(用户/角色)可以对哪些数据库对象(表、视图、存储过程等)执行什么操作(SELECT、INSERT、UPDATE 等)。合理配置权限可防止数据泄露、误操作和恶意攻击。
9.2.1 权限管理的核心概念
- 主体(Principal)
- 用户(User):数据库访问者(如
Alice,sa) - 角色(Role):权限集合,可分配给多个用户(如
SalesRole,db_owner) - 组(Group):Windows 组(仅 Windows 身份验证)
- 用户(User):数据库访问者(如
- 客体(Securable)
- 数据库对象:表、视图、存储过程、函数、数据库、服务器等
- 权限(Permission)
- 操作权限:
SELECT,INSERT,UPDATE,DELETE,EXECUTE,REFERENCES等 - 管理权限:
CREATE TABLE,ALTER DATABASE,CONTROL SERVER等
- 操作权限:
9.2.2 核心 SQL 语句
-
GRANT —— 授予权限
GRANT 权限列表 ON 表名 TO 主体 [WITH GRANT OPTION];示例:
-- 授予用户 Alice 查询 Employees 表的权限GRANT SELECT ON Employees TO Alice;-- 授予角色 SalesRole 插入和更新权限GRANT INSERT, UPDATE ON Orders TO SalesRole;-- 允许用户 Bob 转授权限GRANT SELECT ON Products TO Bob WITH GRANT OPTION; -
REVOKE —— 回收权限
REVOKE 权限列表 ON 对象 FROM 主体 [CASCADE];示例:
-- 回收 Alice 的查询权限REVOKE SELECT ON Employees FROM Alice;-- 级联回收(如果 Alice 转授了权限)REVOKE SELECT ON Products FROM Bob CASCADE; -
DENY —— 拒绝权限(最高优先级)
DENY 权限列表 ON 对象 TO 主体;示例:
-- 拒绝 Alice 更新工资的权限DENY UPDATE(Salary) ON Employees TO Alice;⚠️ 权限优先级:DENY > GRANT > 无权限
-
EXEC sp_addsrvrolemember —— 系统存储过程赋权
EXEC sp_addsrvrolemember 'username', 'server_role'服务器角色列表
服务器角色 权限 sysadmin 系统管理员,所有权限 serveradmin 配置服务器设置,关闭服务器 setupadmin 添加/删除链接服务器 securityadmin 管理登录账户和权限 dbcreator 创建、修改、删除数据库 ← 本题需要的角色 processadmin 管理进程(KILL) diskadmin 管理磁盘文件
9.2.3 权限类型详解
-
数据操作权限(DML)
权限 说明 适用对象 SELECT查询数据 表、视图 INSERT插入数据 表、视图 UPDATE更新数据 表、视图 DELETE删除数据 表、视图 EXECUTE执行存储过程/函数 存储过程、函数 REFERENCES创建外键引用 表 -
数据定义权限(DDL)
权限 说明 适用对象 CREATE TABLE创建表 数据库 ALTER TABLE修改表结构 表 DROP TABLE删除表 表 CREATE VIEW创建视图 数据库 CREATE PROCEDURE创建存储过程 数据库 -
管理权限
权限 说明 适用对象 CONTROL完全控制(类似管理员) 数据库、表等 ALTER ANY TABLE修改任意表 数据库 VIEW DEFINITION查看对象定义 数据库对象 CONTROL SERVER控制整个服务器 服务器
9.2.4 权限继承与角色管理
-
角色(Role)
- 将权限打包,分配给多个用户
- 简化权限管理
示例:
-- 创建角色CREATE ROLE SalesRole;-- 授予角色权限GRANT SELECT, INSERT ON Orders TO SalesRole;-- 将角色授予用户GRANT SalesRole TO Alice, Bob; -
固定数据库角色(SQL Server)
角色 权限 db_owner数据库所有权限 db_datareader可读所有表 db_datawriter增/删/改所有表 db_ddladmin可修改表结构 db_accessadmin添加或删除数据库用户 示例:
-- 将用户加入 db_datareader 角色ALTER ROLE db_datareader ADD MEMBER Alice;
9.2.5 权限优先级与冲突解决
-
DENY 优先
GRANT UPDATE ON Employees TO Alice; -- 授予更新DENY UPDATE ON Employees TO Alice; -- 拒绝更新 → 最终无权限 -
角色 vs 用户权限
- 用户权限和角色权限合并计算
DENY会覆盖GRANT
9.3 存取控制
在数据库安全领域,存取控制(Access Control) 是确保数据安全的核心机制。根据控制策略的不同,主要分为两类:
✅ 自主存取控制(DAC, Discretionary Access Control)
✅ 强制存取控制(MAC, Mandatory Access Control)
两者在控制主体、灵活性、安全性等方面有本质区别。
9.3.1 自主存取控制(DAC)
定义:
数据的所有者(Owner)可以自主决定谁可以访问其数据。
- ✅ “自主” = 所有者自己决定
- ✅ 常见于商业数据库(如 SQL Server、MySQL、Oracle)
- ✅ 通过
GRANT和REVOKE实现
实现方式:
-- 授予权限GRANT SELECT, INSERT ON Employees TO Alice;
-- 回收权限REVOKE INSERT ON Employees FROM Alice;
-- 拒绝权限(SQL Server)DENY UPDATE ON Employees TO Alice;特点:
| 特性 | 说明 |
|---|---|
| 灵活性 | 高(所有者自由授权) |
| 易用性 | 高(语法简单) |
| 安全性 | 中(依赖所有者责任心) |
| 适用场景 | 企业应用、Web 系统 |
9.3.2 强制存取控制(MAC)
定义:
系统强制规定所有主体(用户)和客体(数据)的安全级别,访问必须符合安全策略。
- ✅ “强制” = 系统决定,用户无权更改
- ✅ 常见于军事、政府等高安全系统
- ✅ 基于安全标签(Security Label) 实现
安全标签:
- 主体标签(Subject Label):用户的安全级别(如 绝密、机密、秘密)
- 客体标签(Object Label):数据的安全级别(如 绝密、机密、秘密)
- 访问规则:
- 下读(Read Down):高安全级用户可读低安全级数据
- 上写(Write Up):低安全级用户可写高安全级数据(防止信息泄露)
📘 示例:
- 用户 Alice(机密)可读“秘密”数据(下读)
- 用户 Bob(秘密)可写“机密”数据(上写)
特点:
| 特性 | 说明 |
|---|---|
| 灵活性 | 低(系统强制控制) |
| 安全性 | 高(防越权访问) |
| 复杂性 | 高(需定义安全策略) |
| 适用场景 | 军事、政府、金融核心系统 |
典型应用:
- 军事系统(如美军的 MLS 系统)
- 政府机密数据库
- 金融核心交易系统
9.3.3 DAC vs MAC 对比
| 特性 | DAC | MAC |
|---|---|---|
| 控制者 | 数据所有者 | 系统管理员 |
| 灵活性 | 高 | 低 |
| 安全性 | 中 | 高 |
| 实现方式 | GRANT/REVOKE | 安全标签 + 策略 |
| 典型系统 | SQL Server, MySQL | SELinux, Trusted Oracle |
| 适用场景 | 企业应用 | 军事/政府 |
9.4 身份验证模式
在 SQL Server for Windows 中,身份验证模式决定了用户如何登录到 SQL Server 实例。SQL Server 提供了两种主要的身份验证模式,用于满足不同的安全和管理需求。
9.4.1 SQL Server 的两种身份验证模式
| 模式 | 名称 | 说明 |
|---|---|---|
| 1 | Windows 身份验证模式 | 使用 Windows 账户进行身份验证 |
| 2 | 混合身份验证模式 | 支持 Windows 账户 + SQL Server 账户 |
⚠️ 安装 SQL Server 时需选择身份验证模式,后续可修改。
-
Windows 身份验证模式(Windows Authentication Mode)
核心机制:
- 使用 Windows 操作系统账户(本地用户或域用户)登录 SQL Server
- 不需要在 SQL Server 中单独创建登录名
- 依赖 Windows 的安全子系统(如 Active Directory)
优点:
- 安全性高:使用 Kerberos 或 NTLM 认证,支持强密码策略
- 无需管理密码:由 Windows 统一管理
- 支持信任连接:无需在网络上传输密码
- 集成企业目录:支持域用户、组
缺点:
- 仅限 Windows 环境
- 不适用于跨平台或外部应用
- 无法用于非 Windows 用户
示例:
-- 允许域用户访问CREATE LOGIN [DOMAIN\Alice] FROM WINDOWS; -
混合身份验证模式(Mixed Mode Authentication)
核心机制:
- 同时支持:
- Windows 身份验证(Windows 用户)
- SQL Server 身份验证(SQL Server 自建账户。只有在 混合身份验证模式 下,
sa才能启用并登录)
优点:
- 灵活性高:支持本地账户和外部应用
- 跨平台兼容:适用于非 Windows 客户端(如 Linux、Java 应用)
- 便于远程访问:无需域环境
缺点:
- 安全性较低:密码在网络中传输(除非启用加密)
- 密码管理复杂:需在 SQL Server 内管理密码策略
- 易受暴力破解:若未启用账户锁定策略
示例:
-- 创建 SQL Server 登录账户CREATE LOGIN sa WITH PASSWORD = 'StrongPassword123!';-- 移除登录用户DROP Login sa - 同时支持:
9.4.2 两种身份验证方式对比
| 特性 | Windows 身份验证 | SQL Server 身份验证 |
|---|---|---|
| 认证方式 | Windows OS | SQL Server 内部 |
| 账户类型 | Windows 用户/组 | SQL Server 用户 |
| 密码管理 | Windows 策略 | SQL Server 内管理 |
| 安全性 | ✅ 高(集成 AD) | ⚠️ 中(需加强配置) |
| 适用环境 | 域环境、企业内网 | 跨平台、外部应用 |
| 是否支持加密连接 | ✅ 是 | ✅ 是(需配置 SSL/TLS) |
| 是否支持多因素认证 | ✅ 是(通过 Windows) | ❌ 否(原生不支持) |
9.5 Oracle 安全管理
Oracle 的安全管理 是一个非常完善和强大的体系,涵盖了身份验证、权限管理、角色控制、审计、数据加密等多个方面,适用于企业级高安全需求场景。
9.5.1 Oracle 安全管理的核心组件
| 组件 | 说明 |
|---|---|
| 身份验证(Authentication) | 验证用户身份(密码、外部认证等) |
| 授权(Authorization) | 控制用户能访问哪些对象(权限) |
| 角色(Roles) | 权限的集合,简化管理 |
| 概要文件(Profiles) | 限制用户资源使用和密码策略 |
| 审计(Auditing) | 记录用户操作,用于监控和合规 |
| 数据加密 | 透明数据加密(TDE)、列级加密 |
| 虚拟私有数据库(VPD) | 行级安全控制 |
9.5.2 身份验证(Authentication)
Oracle 支持多种身份验证方式:
-
数据库身份验证
- 用户名 + 密码存储在数据库中
- 最常见方式
CREATE USER alice IDENTIFIED BY password; -
外部身份验证(External Authentication)
- 使用操作系统或网络服务(如 Kerberos、SSL)验证
- 不需要数据库密码
CREATE USER ops$alice IDENTIFIED EXTERNALLY; -
全局身份验证(Global Authentication)
- 基于目录服务(如 LDAP)
- 适用于单点登录(SSO)
9.5.3 权限管理
Oracle 权限分为两类:系统权限 和 对象权限 。具体可细分为数据库级(CREATE SESSION),表级(SELECT, INSERT),行级(VPD),列级(视图、授权或 透明数据加密TDE)
-
系统权限
对整个数据库的操作权限
常见权限 说明 CREATE SESSION登录数据库 CREATE TABLE创建表 CREATE ANY TABLE在任意 schema 中创建表 DROP ANY TABLE删除任意表 ALTER DATABASE修改数据库结构 DBA所有系统权限(超级管理员) 示例:
GRANT CREATE SESSION TO alice;GRANT CREATE TABLE TO alice; -
对象权限(Object Privileges)
对特定对象的操作权限
权限 适用对象 SELECT表、视图 INSERT,UPDATE,DELETE表、视图 EXECUTE存储过程、函数、包 REFERENCES表(用于外键) 示例:
GRANT SELECT, INSERT ON scott.emp TO alice;GRANT EXECUTE ON pkg_salary TO bob;
9.5.4 角色(Roles)
角色是权限的集合,用于简化权限管理。
-
预定义角色(Predefined Roles)
角色 权限 CONNECTCREATE SESSION(基本连接权限)RESOURCECREATE TABLE,CREATE SEQUENCE,CREATE PROCEDURE等(开发常用)DBA所有权限(数据库管理员) EXP_FULL_DATABASE数据导出权限 IMP_FULL_DATABASE数据导入权限 ⚠️ 注意:CONNECT 和 RESOURCE 是 Oracle 早期版本的角色,新版本推荐细粒度授权
-
自定义角色
CREATE ROLE hr_clerk;GRANT SELECT, INSERT ON employees TO hr_clerk;GRANT hr_clerk TO alice, bob;
9.5.5 概要文件(Profiles)
Profile 用于限制用户资源使用和密码策略
-
资源限制
- CPU 每次调用限制
- 会话空闲时间
- 并发会话数
-
密码策略
- 密码过期时间
- 失败登录尝试次数
- 密码复杂度(通过密码验证函数)
示例:
-- 创建 profileCREATE PROFILE app_user LIMITFAILED_LOGIN_ATTEMPTS 3PASSWORD_LOCK_TIME 1PASSWORD_LIFE_TIME 90;-- 应用到用户CREATE USER alice IDENTIFIED BY password PROFILE app_user;
9.5.6 审计(Auditing)
Oracle 提供强大的审计功能,用于记录用户操作。
-
标准审计(Statement Auditing)
-- 审计所有用户的 DELETE 操作AUDIT DELETE ANY TABLE;-- 审计特定用户AUDIT SELECT TABLE BY alice; -
细粒度审计(Fine-Grained Auditing, FGA)
- 基于内容的审计(如工资 > 10000 的查询)
- 可触发自定义逻辑
BEGINDBMS_FGA.ADD_POLICY(object_schema => 'HR',object_name => 'EMPLOYEES',policy_name => 'audit_high_salary',audit_condition => 'SALARY > 10000',audit_column => 'SALARY');END;/
9.5.7 数据加密
-
透明数据加密(TDE, Transparent Data Encryption)
- 加密整个表空间或列
- 对应用透明
- 防止物理文件泄露
-- 创建加密表空间CREATE TABLESPACE secure_tsDATAFILE 'secure.dbf' SIZE 10MENCRYPTION USING 'AES256'DEFAULT STORAGE(ENCRYPT); -
列级加密
- 使用
DBMS_CRYPTO包手动加密敏感列
- 使用
9.5.8 虚拟私有数据库(VPD) / 行级安全
VPD 实现行级访问控制,基于用户身份动态添加 WHERE 条件
示例:HR 经理只能看本部门员工
-- 创建安全策略函数CREATE OR REPLACE FUNCTION emp_security_policy ( schema_var IN VARCHAR2, table_var IN VARCHAR2) RETURN VARCHAR2 ISBEGIN RETURN 'deptno = SYS_CONTEXT(''USERENV'', ''SESSION_USER_DEPT'')';END;/
-- 应用策略BEGIN DBMS_RLS.ADD_POLICY( object_schema => 'HR', object_name => 'EMPLOYEES', policy_name => 'emp_policy', function_schema => 'SEC_MGR', policy_function => 'emp_security_policy', statement_types => 'SELECT, INSERT, UPDATE, DELETE' );END;/→ 查询 SELECT * FROM employees; 时自动加上 WHERE deptno = ...
9.5.9 典型安全配置流程
-
创建用户
CREATE USER alice IDENTIFIED BY password PROFILE app_profile; -
授予角色
GRANT CONNECT, RESOURCE TO alice;GRANT hr_clerk TO alice; -
授对象权限
GRANT SELECT ON hr.employees TO alice; -
启用审计
AUDIT SELECT TABLE BY alice; -
设置 TDE 加密敏感数据
9.6 用户管理
在 SQL 数据库中,用户的管理(User Management) 是数据库安全管理的核心部分。它涉及创建、修改、删除用户,以及为用户分配权限和角色,确保只有授权用户才能访问数据库资源。
不同数据库系统(如 SQL Server、Oracle、MySQL)在用户管理上略有差异,但核心思想一致。
9.6.1 什么是数据库用户?
数据库用户(Database User) 是一个用于连接和操作数据库的账户,通常映射到服务器级别的登录账户(Login),并被授予特定权限。
- 用户 ≠ 登录账户(在某些系统中分离)
- 用户是数据库级别的概念
- 权限通过用户控制
9.6.2 用户管理的核心操作
-
创建用户
SQL Server:
-- 创建登录账户(服务器级)CREATE LOGIN U1 WITH PASSWORD = 'password';-- 在指定数据库中创建用户,并关联登录USE DB1;CREATE USER U1 FOR LOGIN U1;MySQL:
CREATE USER 'u1'@'localhost' IDENTIFIED BY 'password'; -
修改用户
修改密码:
-- SQL ServerALTER LOGIN U1 WITH PASSWORD = 'new_password';-- MySQLALTER USER 'u1'@'localhost' IDENTIFIED BY 'new_password';更改默认数据库(SQL Server):
ALTER LOGIN U1 WITH DEFAULT_DATABASE = DB1; -
删除用户
SQL Server:
-- 删除数据库用户USE DB1;DROP USER U1;-- 删除登录账户(服务器级)DROP LOGIN U1;MySQL:
DROP USER 'u1'@'localhost';
9.6.3 权限与角色管理
-
授予权限
-- 授予 SELECT 权限GRANT SELECT ON Employees TO U1;-- 授予 INSERT 权限GRANT INSERT ON Orders TO U1;-- 授予执行存储过程权限GRANT EXECUTE ON proc_GetData TO U1; -
回收权限
REVOKE INSERT ON Orders FROM U1; -
拒绝权限(SQL Server 特有)
DENY DELETE ON Employees TO U1; -- 明确禁止删除⚠️ DENY 优先级最高,会覆盖 GRANT
9.6.4 角色(Role)管理
角色是权限的集合,简化用户权限分配。
-
固定角色(预定义角色)
SQL Server 固定数据库角色(服务器级别):
角色 权限 db_owner所有权限 db_datareader可读所有表 db_datawriter可写所有表 db_ddladmin可执行 DDL 操作 db_backupoperator可备份数据库 -- 将用户加入角色EXEC sp_addrolemember 'db_datareader', 'U1';Oracle 预定义角色:
CONNECT:基本连接权限RESOURCE:创建表、序列等DBA:超级管理员
GRANT CONNECT, RESOURCE TO u1; -
自定义角色
-- SQL ServerCREATE ROLE SalesRole;GRANT SELECT, INSERT ON Orders TO SalesRole;EXEC sp_addrolemember 'SalesRole', 'U1';
9.6.5 用户状态监控
查看用户信息:
SQL Server:
SELECT * FROM sys.database_principals WHERE type = 'S'; -- S = SQL 用户MySQL:
SELECT User, Host FROM mysql.user;第十章 数据库运行维护与优化
10.1 使用索引的原则
- 在需要经常搜索的列上创建索引
- 主键上创建索引
- 经常用于连接的列上创建索引
- 经常需要根据范围进行搜索的列上创建索引
- 经常需要排序的列上创建索引
- 在where查询子句中引用效率高的列上创建索引
- 在Order By子句、Group By子句中出现的列上建立索引
10.2 数据库重组/重构
在数据库运行维护中,数据库重组 和 数据库重构 是两个重要但容易混淆的概念。它们都用于优化数据库性能、适应业务变化,但在目标、范围和影响上有本质区别。
10.2.1 核心定义对比
| 项目 | 数据库重组(Reorganization) | 数据库重构(Restructuring / Refactoring) |
|---|---|---|
| 本质 | 物理结构优化 | 逻辑结构调整 |
| 是否改变模式 | ❌ 不改变表结构 | ✅ 改变表结构或关系 |
| 目标 | 提升性能、释放空间 | 适应新需求、改善设计 |
| 对应用影响 | 小(通常透明) | 大(可能需修改代码) |
| 执行频率 | 高频(定期维护) | 低频(重大变更) |
10.2.2 数据库重组(Reorganization)
不改变数据库逻辑结构,仅优化物理存储和访问效率
主要操作:
| 操作 | 说明 |
|---|---|
| 重建索引(Rebuild Index) | 消除 B+树碎片,减少层级,提高查询速度 |
| 重组索引(Reorganize Index) | 整理叶节点顺序,回收空页(轻量级) |
| 收缩数据库文件 | 回收未使用空间,避免磁盘浪费 |
| 更新统计信息 | 让查询优化器生成更优执行计划 |
| 数据归档与清理 | 将历史数据迁出主表,提升性能 |
| 调整文件组/文件分布 | 将热点数据放高速磁盘 |
示例:
-- SQL Server:重建索引ALTER INDEX IX_Employee_Name ON Employees REBUILD;
-- Oracle:整理表空间ALTER TABLE employees MOVE;
-- MySQL:优化表(InnoDB)OPTIMIZE TABLE orders;适用场景:
- 表频繁增删改后出现性能下降
- 索引碎片率过高(如 >30%)
- 存储空间利用率低
10.2.3 数据库重构(Restructuring)
修改数据库的逻辑结构,以适应新的业务需求或改进设计
主要操作:
| 操作 | 说明 |
|---|---|
| 修改列定义 | 改变数据类型、长度、约束(如 VARCHAR(50) → 100) |
| 增加/删除列 | 新增字段或移除废弃字段 |
| 拆分表(垂直/水平分割) | 将大表拆为多个小表 |
| 合并表 | 合并冗余表 |
| 引入汇总表 | 预计算聚合结果,加速报表查询 |
| 添加/修改外键约束 | 增强数据完整性 |
| 引入分区表 | 按时间或类别分区,提升管理效率 |
| 创建视图封装复杂逻辑 | 隐藏底层结构变化 |
示例:
-- 修改列类型(重构)ALTER TABLE employeesALTER COLUMN salary DECIMAL(12,2);
-- 拆分大表(垂直分割)CREATE TABLE employee_contact ASSELECT emp_id, phone, email FROM employees;
ALTER TABLE employees DROP COLUMN phone, email;适用场景:
- 业务需求变更(如新增字段)
- 性能瓶颈无法通过重组解决
- 原有设计不合理(如缺乏规范化)
- 系统升级或集成
10.2.4 注意事项
- 重构风险高
- 可能影响现有应用程序
- 需充分测试 SQL 语句、存储过程、前端代码
- 建议配合版本控制和灰度发布
- 重组也要谨慎
- 重建大表索引会锁表,影响在线业务
- 建议在低峰期执行
- 备份先行
- 无论是重组还是重构,操作前必须完整备份数据库
- 自动化维护
- 使用维护计划(SQL Server)、调度任务(Oracle AWR)自动执行重组
10.2.5 典型工作流程
数据库重组流程:
- 监控性能指标(CPU、I/O、响应时间)
- 检测索引碎片率
- 在低峰期执行索引重建/重组
- 更新统计信息
- 验证性能提升
数据库重构流程:
- 分析新业务需求
- 设计新数据库结构
- 制定迁移脚本(ETL)
- 在测试环境验证
- 上线部署 + 应用联调测试
- 监控运行状态
10.3 事务等待图
事务等待图(Wait-for Graph) 是数据库管理系统中用于检测死锁(Deadlock) 的核心数据结构。它通过图形化方式表示事务之间的资源等待关系,帮助系统识别并解决死锁问题。
10.3.1 什么是事务等待图?
事务等待图 = 有向图(Directed Graph)
- 节点(Node):表示一个正在运行的事务(如 T1, T2, T3)
- 有向边(Edge):表示“等待”关系
T1 → T2表示:事务 T1 正在等待事务 T2 释放某个锁
10.3.2 基本原理
当一个事务 A 请求访问某数据项,但该数据项已被另一个事务 B 加锁且不兼容时:
- 事务 A 必须等待
- 系统就在等待图中添加一条边:
A → B
如果多个事务相互等待,就可能形成环路 → 即死锁
10.3.3 死锁与环路的关系
✅ 等待图中出现环路 ⇔ 发生死锁
示例 1:两事务死锁
T1 → T2↑ ↓└─── T2 ← T1- T1 持有 X 锁(排他锁)在数据项 R 上,请求 S 锁在 S 上
- T2 持有 S 锁在 S 上,请求 X 锁在 R 上
- T1 等待 T2 释放 S
- T2 等待 T1 释放 R → 形成环路 → 死锁!
三事务循环等待
T1 → T2↑ ↓└── T3 ←┘- T1 等 T2
- T2 等 T3
- T3 等 T1 → 环路 → 死锁
死锁检测流程
- 周期性检查等待图
- 数据库后台进程定期扫描所有事务和锁请求
- 构建当前的等待图
- 检测环路
- 使用图算法(如深度优先搜索 DFS)检测是否存在环路
- 处理死锁
- 选择一个“牺牲品”事务(通常回滚代价最小)
- 回滚该事务,释放其持有的所有锁
- 打破环路,解除死锁
- 被回滚的事务可稍后重试
10.3.4 实际应用(以 SQL Server 为例)
-
自动检测
- SQL Server 默认启用死锁检测
- 检测到死锁后,会自动选择牺牲品并终止连接
-
查看死锁信息
-- 启用跟踪标志查看死锁图(XML格式)DBCC TRACEON (1222, -1);→ 死锁发生时,错误日志中会记录详细的等待图(以 XML 形式)
-
应用层处理
BEGIN TRYBEGIN TRANSACTION;-- 操作...COMMIT TRANSACTION;END TRYBEGIN CATCHIF ERROR_NUMBER() = 1205 -- 死锁错误PRINT '事务被选为死锁牺牲品,建议重试';END CATCH
10.4 分割表
分割表(Table Partitioning) 是数据库性能优化和数据管理中的重要技术,它将一个大表从逻辑上视为整体,但从物理上划分为多个更小、更易管理的“分区”或“子表”。根据实现方式的不同,“分割表”可以指 水平分割 或 垂直分割。
10.4.1 什么是分割表?
分割表 = 将一个大表按某种规则拆分成多个小表或分区
目的:
- 提升查询性能
- 简化数据维护(如归档、删除)
- 改善 I/O 分布
- 实现存储分层(热数据放 SSD,冷数据放 HDD)
10.4.2 分割表的两种主要方式
-
水平分割(Horizontal Partitioning)
按行将数据分布到多个结构相同的表中
特点:
- 所有子表具有相同列结构
- 数据按某一条件划分(如时间、地区)
- 查询时可通过条件定位到特定子表
示例:按年份分割订单表
表名 存储数据 Orders_20222022 年订单 Orders_20232023 年订单 Orders_20242024 年订单 -- 查询2023年订单 → 只查 Orders_2023SELECT * FROM Orders_2023 WHERE CustomerID = 1001;优点:
- 查询快(减少扫描行数)
- 可单独备份/删除某年数据
缺点:
- 访问所有数据需要使用 Union 操作
-
垂直分割(Vertical Partitioning)
按列将表拆分为多个表
特点:
- 原表的列被分散到不同表中
- 通常主键保留,便于连接
- 适用于宽表(列很多)或访问模式差异大的场景
示例:用户信息表拆分
-- 主表:基础信息(高频访问)CREATE TABLE Users (UserID INT PRIMARY KEY,Name VARCHAR(50),Email VARCHAR(100));-- 扩展表:隐私信息(低频访问)CREATE TABLE UserProfiles (UserID INT PRIMARY KEY,Address TEXT,Phone VARCHAR(20),Birthday DATE,FOREIGN KEY (UserID) REFERENCES Users(UserID));✅ 优点:
- 减少热点表的宽度,提升高频查询性能
- 敏感字段可单独加密或授权
10.4.3 常见分割策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 范围分割 | 按值范围分割(如时间) | 日志表、订单表 |
| 列表分割 | 按离散值分割(如地区) | 多分支机构数据 |
| 哈希分割 | 按哈希值均匀分布 | 负载均衡、避免热点 |
| 轮转法(Round-Robin) | 顺序插入不同子表 | 数据均匀分布 |
10.4.4 在 SQL Server 中的实现方式
-
使用分区表(Partitioned Table) —— 推荐
这是最标准的“水平分割”实现:
-- 1. 创建分区函数(按年份)CREATE PARTITION FUNCTION PF_OrderYear (INT)AS RANGE RIGHT FOR VALUES (2023, 2024);-- 2. 创建分区方案CREATE PARTITION SCHEME PS_OrderSchemeAS PARTITION PF_OrderYearTO (FG2022, FG2023, FG2024);-- 3. 创建分区表CREATE TABLE Orders (OrderID INT,OrderYear INT,Amount DECIMAL(10,2)) ON PS_OrderScheme(OrderYear);→ 数据自动按
OrderYear分区存储 -
手动创建子表 + 视图统一访问
-- 创建子表CREATE TABLE Orders_2023 (...);CREATE TABLE Orders_2024 (...);-- 创建视图统一查询CREATE VIEW AllOrders ASSELECT *, 2023 AS Year FROM Orders_2023UNION ALLSELECT *, 2024 AS Year FROM Orders_2024;→ 应用通过视图查询,透明访问所有数据
第十一章 故障管理
11.1 检查点
检查点(Checkpoint) 是数据库管理系统中一个非常重要的机制,用于提高故障恢复效率、确保数据一致性、减少恢复时间。它在数据库崩溃或异常重启后起着关键作用。
11.1.1 什么是检查点?
检查点(Checkpoint) 是数据库在运行过程中定期记录的一个“稳定状态点”,表示:
“到这个时间为止,所有已提交事务的修改都已尽可能写入磁盘。”
- 检查点不是备份
- 它是恢复机制的一部分
- 目的是缩短崩溃恢复时间
11.1.2 为什么需要检查点?
没有检查点的问题:
- 数据库每次崩溃恢复时,都要从日志文件的最开始扫描整个日志
- 执行 REDO(重做已提交事务)
- 即使某些事务早已落盘,也要重新处理
- 恢复时间长,影响系统可用性
引入检查点的好处:
- 只需从最后一个检查点开始恢复
- 大大减少需要处理的日志量
- 提高恢复速度
11.1.3 检查点的工作原理
当数据库创建一个检查点时,主要执行以下操作:
- 记录当前活跃事务列表
- 哪些事务正在运行(未提交)
- 这些事务的最新日志记录地址(LSN)
- 刷新脏页(Flush Dirty Pages)
- 将内存缓冲区(Buffer Pool)中所有已提交事务的修改数据页写入磁盘
- 确保这些更改持久化
- 写入检查点日志记录
- 在事务日志中插入一条特殊的
CHECKPOINT日志 - 包含:
- 时间戳
- 活跃事务列表
- 最近日志序列号(LSN)
- 在事务日志中插入一条特殊的
- 更新“重新开始文件”(Restart File)
- 记录该检查点在日志文件中的位置(LSN)
- 故障恢复时从此处开始扫描日志
11.1.4 检查点与恢复流程
假设数据库异常断电后重启:
- 读取“重新开始文件” → 找到最后一个检查点位置
- 从该检查点开始扫描日志
- REDO:重做从检查点之后的所有已提交事务(即使已写盘,以防不完整)
- UNDO:回滚未提交的事务
- 数据库恢复一致状态
✅ 因为检查点前的数据基本已落盘,所以只需处理少量日志
11.1.5 检查点的触发方式
| 触发方式 | 说明 |
|---|---|
| 定时触发 | 每隔一段时间自动创建(如每分钟) |
| 日志大小触发 | 当日志增长到一定大小时触发 |
| 系统空闲时 | 利用低峰期减少性能影响 |
| 手动触发 | DBA 可强制创建(如 CHECKPOINT 命令) |
✅ 示例(SQL Server):
CHECKPOINT; -- 手动创建检查点
11.1.6 检查点 vs 备份
| 特性 | 检查点 | 备份 |
|---|---|---|
| 目的 | 加快崩溃恢复 | 防止介质损坏导致数据丢失 |
| 是否可防止数据丢失 | ❌ 否(依赖磁盘完好) | ✅ 是(可异地保存) |
| 恢复范围 | 仅支持崩溃恢复 | 支持灾难恢复 |
| 自动程度 | 完全自动 | 需配置计划 |
| 存储开销 | 不额外占用空间 | 占用大量存储 |
✅ 检查点不能替代备份!
11.1.7 注意事项
- 检查点会影响性能
- 刷脏页会增加 I/O 负载
- 应避免频繁创建
- 检查点不保证所有数据落盘
- 只保证“已提交事务”的数据尽可能落盘
- 未提交事务仍需 UNDO
- 依赖事务日志
- 如果日志损坏,检查点也无法恢复
- 应由系统自动管理
- 不建议 DBA 频繁手动干预
11.2 数据转储
在数据库备份与恢复中,数据转储(Data Dump) 是保障数据安全、实现灾难恢复的核心手段。根据备份方式和范围的不同,主要分为以下几种转储方案:
11.2.1 按运行状态分类
-
静态转储(Static Dump / Cold Backup)
在数据库完全关闭或所有事务暂停时进行的备份。
特点:
- 数据库处于“静止”状态
- 不允许任何读写操作
- 备份期间服务中断
优点:
- 数据一致性高(无需日志辅助)
- 恢复简单直接
缺点:
- 影响系统可用性
- 不适用于 24×7 系统
适用场景:
- 小型系统维护期
- 初始全量备份
- 非关键业务
-
动态转储(Dynamic Dump / Hot Backup)
在数据库正常运行、事务并发执行的情况下进行备份。
特点:
- 允许用户继续访问数据库
- 备份过程中数据可能变化
优点:
- 提高系统可用性(不停机)
- 适合高可用系统
缺点:
- 备份本身不一致 → 必须结合事务日志才能恢复
- 实现复杂
依赖机制:
- 事务日志(Write-Ahead Logging)
- 检查点(Checkpoint)
示例技术:
- SQL Server:在线
BACKUP DATABASE - Oracle:RMAN 热备
- MySQL:
xtrabackup(InnoDB)
11.2.2 按备份内容分类
-
完全转储(Full Dump / Full Backup)
每次都备份整个数据库的所有数据。
特点:
- 包含所有数据页
- 独立可恢复(无需其他备份)
优点:
- 恢复速度快(只需一次还原)
- 管理简单
缺点:
- 占用空间大
- 耗时长
- 频繁执行影响性能
使用建议:
- 定期执行(如每周一次)
- 作为增量/差异备份的基础
-
差异转储(Differential Dump / Differential Backup)
只备份自上次完全转储以来发生变化的数据。
特点:
- 基于最近一次全备
- 每次差异备份包含从全备到当前的所有变更
恢复流程:
- 恢复最近一次完全备份
- 应用最新的差异备份(只需一个)
优点:
- 比完全备份节省空间
- 恢复比增量快(只需两个步骤)
缺点:
- 差异文件随时间增长而变大
- 仍需定期全备
示例:
周一:全量备份周二:差异备份(改1)周三:差异备份(改1+2)周四:差异备份(改1+2+3) -
增量转储(Incremental Dump / Incremental Backup)
只备份自上次任意备份(全量或增量)以来的变化数据。
特点:
- 备份最小量数据
- 依赖完整的备份链
恢复流程:
- 恢复最近一次完全备份
- 按顺序应用所有后续增量备份
优点:
- 存储开销最小
- 备份速度快
- 适合频繁备份
缺点:
- 恢复过程复杂
- 中间任一备份损坏 → 整个链条失效
- 恢复时间较长(需重放多个日志)
示例:
周一:全量周二:增量1(改1)周三:增量2(改2)周四:增量3(改3)→ 恢复:全量 + 增1 + 增2 + 增3
11.2.3 混合策略(常用组合)
| 策略 | 方案 | 说明 |
|---|---|---|
| 全量 + 差异 | 每周全备 + 每日差异 | 平衡恢复速度与存储成本 |
| 全量 + 增量 | 每周全备 + 每日增量 | 节省空间,恢复稍慢 |
| 镜像级实时复制 | AlwaysOn, Data Guard | 接近零停机恢复 |
11.2.4 高级转储技术
-
逻辑转储 vs 物理转储
类型 说明 工具示例 逻辑转储 导出为 SQL 或文本格式(如 INSERT 语句) mysqldump,pg_dump物理转储 直接复制数据文件(页、块) 文件拷贝、RMAN、xtrabackup 逻辑:跨平台迁移好,但慢 物理:快,但依赖相同 DBMS 和版本
-
快照转储(Snapshot Backup)
利用存储层(SAN、NAS)或文件系统(如 LVM、ZFS)的快照功能创建瞬间一致的副本。
优点:
- 几乎瞬时完成
- 对数据库影响小
- 可用于基础备份
缺点:
- 通常仍需结合日志恢复一致性
- 依赖底层存储支持
-
日志传送(Log Shipping)
实时或准实时将事务日志传送到备用服务器并应用。
优点:
- 接近实时同步
- 支持异地容灾
- 成本较低
缺点:
- 恢复延迟(非完全实时)
- 需手动或自动故障转移
-
主从复制(Replication)
通过数据库自身的复制机制(如 MySQL binlog 复制)实现数据同步。
类型:
- 异步复制(有延迟)
- 半同步复制(更可靠)
- 同步复制(强一致性)
11.4.5 各类转储对比表
| 方案 | 备份大小 | 恢复速度 | 存储开销 | 是否影响业务 | 典型场景 |
|---|---|---|---|---|---|
| 静态全量 | 大 | 快 | 高 | 是(中断) | 维护窗口 |
| 动态全量 | 大 | 快 | 高 | 否 | 关键系统初始备份 |
| 差异备份 | 中 | 较快 | 中 | 否 | 日常增量保护 |
| 增量备份 | 小 | 慢 | 低 | 否 | 高频备份需求 |
| 快照 | 极小 | 快 | 低 | 否 | 快速备份起点 |
| 日志传送 | 极小 | 快 | 低 | 否 | 高可用/灾备 |
第十二章 备份与恢复数据库
12.1 Restore 指令
RESTORE 是 SQL Server 中用于从备份恢复数据库的核心 T-SQL 指令。它与 BACKUP 配合使用,是数据库灾难恢复、数据迁移、测试环境搭建等场景的关键工具。
12.1 什么是 RESTORE?
RESTORE 命令:将通过 BACKUP 创建的数据库备份文件(.bak)还原到数据库中,使数据恢复到某个时间点的状态。
常用于:
- 系统崩溃后恢复数据
- 将生产数据复制到测试环境
- 实现时间点恢复(Point-in-Time Recovery)
- 数据库升级前回滚准备
12.1.2 基本语法结构
RESTORE DATABASE 数据库名FROM DISK = '备份文件路径'WITH [REPLACE], -- 覆盖现有数据库 [NORECOVERY], -- 不恢复,用于后续日志还原 [RECOVERY], -- 完成恢复(默认) [MOVE '逻辑文件名' TO '新物理路径'] -- 文件重定位12.1.3 常用 RESTORE 场景与示例
-
完整数据库恢复(Full Restore)
RESTORE DATABASE MyDBFROM DISK = 'D:\Backup\MyDB_Full.bak'WITH RECOVERY;将数据库完全恢复到备份时的状态
-
还原后继续应用日志(Log Shipping / 增量恢复)
-- 先还原全备(不恢复)RESTORE DATABASE MyDBFROM DISK = 'D:\Backup\MyDB_Full.bak'WITH NORECOVERY;-- 再还原事务日志RESTORE LOG MyDBFROM DISK = 'D:\Backup\MyDB_Log.trn'WITH RECOVERY;适用于“完整 + 日志”或“完整 + 差异 + 日志”的恢复流程
-
时间点恢复(Point-in-Time Recovery)
RESTORE DATABASE MyDBFROM DISK = 'D:\Backup\MyDB_Full.bak'WITH NORECOVERY;RESTORE LOG MyDBFROM DISK = 'D:\Backup\MyDB_Log.trn'WITH RECOVERY, STOPAT = '2025-04-05 10:30:00';恢复到指定时间点,避免误删/误改操作的影响
-
恢复到不同位置(异机或重命名)
RESTORE DATABASE MyDB_TestFROM DISK = 'D:\Backup\MyDB_Full.bak'WITHMOVE 'MyDB_Data' TO 'E:\Data\MyDB_Test.mdf',MOVE 'MyDB_Log' TO 'F:\Log\MyDB_Test.ldf',REPLACE;MOVE:重新定位数据和日志文件 REPLACE:覆盖同名数据库
-
仅恢复文件组(Partial Restore)
RESTORE DATABASE MyDBFILEGROUP = 'PRIMARY'FROM DISK = 'D:\Backup\PrimaryFG.bak'WITH NORECOVERY;优先恢复关键文件组,提高可用性(SQL Server 企业版支持)
12.1.4 重要选项说明
| 选项 | 说明 |
|---|---|
WITH RECOVERY | 完成恢复,数据库可访问(默认) |
WITH NORECOVERY | 不完成恢复,等待后续日志还原 |
REPLACE | 强制覆盖现有数据库(即使名称相同) |
MOVE | 更改数据/日志文件的存储路径 |
STOPAT | 指定恢复到某一时间点 |
STATS | 显示恢复进度(如 STATS=10 表示每10%输出一次) |
12.2 核心系统数据库
在 SQL Server 中,系统自带 4 个核心系统数据库:master、model、msdb 和 tempdb。它们在数据库引擎的运行中扮演关键角色,备份策略也各不相同。
下面从备份与恢复的角度,逐一介绍这四个数据库的作用及其备份建议。
12.2.1 master 数据库
作用:
- 存储 SQL Server 实例的全部元数据和配置信息
- 包括:
- 所有用户数据库的名称和文件路径
- 登录账户(Logins)
- 端点、链接服务器
- 系统配置(如内存、最大连接数)
🔥 如果 master 损坏且无备份 → 整个实例无法启动!
备份策略:
| 建议 | 说明 |
|---|---|
| ✅ 必须定期备份 | 否则无法恢复实例 |
| ✅ 修改后立即备份 | 如新增登录、创建数据库、添加链接服务器 |
| ❌ 不能只依赖自动备份计划 | 因为变更可能是突发的 |
🛠️ 示例:
BACKUP DATABASE master TO DISK = 'D:\Backup\master.bak';
⚠️ 注意:不能对 master 执行还原操作,除非在单用户模式下。只能进行完整备份
12.2.2 model 数据库
作用:
- 是所有新创建数据库的模板
- 每当执行
CREATE DATABASE MyDB时,SQL Server 会复制model的结构和设置
可自定义内容:
- 初始数据/日志文件大小
- 自动增长设置
- 默认文件组
- 用户对象(如通用函数或表)
-
- 备份策略:**
| 建议 | 说明 |
|---|---|
| ✅ 如果修改了 model,必须备份 | 否则下次创建数据库将丢失自定义设置 |
| ✅ 未修改也可定期备份 | 作为保护机制 |
| ❌ 无需频繁备份 | 一般变化少 |
📝 提示:即使你不主动修改 model,某些功能(如启用全文索引)也可能影响它
12.2.3 msdb 数据库
作用:
- 存储所有自动化任务的信息,包括:
- SQL Server Agent 作业
- 调度计划
- 操作员通知
- 维护计划(如备份、重建索引)
- 备份历史记录(
backupset,backupfile表) - 数据库邮件配置
⚠️ 如果 msdb 丢失 → 所有作业、维护计划、备份历史都将消失!
备份策略:
| 建议 | 说明 |
|---|---|
| ✅ 必须定期备份 | 推荐每日一次 |
| ✅ 与用户数据库同步备份 | 避免恢复时信息脱节 |
| ✅ 支持事务日志备份 | 若使用完整恢复模式 |
🔄 恢复场景:
- 先恢复
master- 再恢复
msdb→ 还原作业和备份历史
12.2.4 tempdb 数据库
作用:
- 存储临时对象和中间结果,包括:
- 局部/全局临时表(#tmp, ##tmp)
- 表变量
- 排序、哈希联接的中间数据
- 版本存储(用于行版本控制)
⚡ 特性:
- 每次 SQL Server 启动时自动重建
- 所有数据在重启后清空
- 不支持备份和还原
备份策略:
| 建议 | 说明 |
|---|---|
| ❌ 不需要也不允许备份 | 备份命令会被忽略或报错 |
| ✅ 监控其大小和性能 | 防止过度增长导致磁盘满 |
| ✅ 可配置多个数据文件 | 提高并发性能(最佳实践) |
📝 虽然不备份,但应确保:
tempdb文件放在高速磁盘上- 初始大小合理,避免频繁自动增长
12.3 数据库恢复模式
在 SQL Server 中,数据库恢复模式(Recovery Model) 是决定数据库如何记录事务日志、支持何种备份与恢复策略的关键设置。它直接影响 数据保护能力、日志管理复杂度和系统性能。
12.3.1 什么是数据库恢复模式?
恢复模式 决定了:
- 事务日志的记录方式
- 是否支持时间点恢复(Point-in-Time Recovery)
- 日志何时被截断(释放空间)
它是数据库级别的属性,每个数据库可独立设置。
12.3.2 SQL Server 的三种恢复模式
| 模式 | 特点 | 适用场景 |
|---|---|---|
| 1. 简单恢复模式(Simple Recovery Model) | 自动截断日志,不支持时间点恢复 | 开发、测试、数据仓库 |
| 2. 完整恢复模式(Full Recovery Model) | 所有操作完整记录日志,支持精确恢复 | 生产 OLTP 系统 |
| 3. 大容量日志恢复模式(Bulk-Logged Recovery Model) | 大批量操作最小日志化,平衡性能与安全 | 批量导入、ETL |
12.3.3 详细解析每种恢复模式
-
简单恢复模式(Simple Recovery Model)
核心特点:
- 自动截断事务日志
- 在检查点(Checkpoint)后,不活动的日志记录被清除
- 不保留完整的日志链
- 无法进行时间点恢复
- 不能做事务日志备份
优点:
- 日志文件不会无限增长
- 无需管理事务日志备份
- 维护简单
缺点:
- 只能恢复到最近一次完整或差异备份的时间点
- 数据丢失窗口大(RPO 高)
适用场景:
- 测试/开发环境
- 数据仓库(每天从源系统重新加载)
- 只读数据库
- 对数据丢失容忍度较高的系统
设置命令:
ALTER DATABASE MyDB SET RECOVERY SIMPLE; - 自动截断事务日志
-
完整恢复模式(Full Recovery Model)
核心特点:
- 所有事务都完整记录在日志中
- 支持 时间点恢复(STOPAT)
- 必须定期执行 事务日志备份,否则日志持续增长
优点:
- 可将数据库恢复到任意时间点(如误删前一秒)
- 最小数据丢失(RPO ≈ 0)
- 支持高可用技术(如日志传送、AlwaysOn)
缺点:
- 日志文件增长快
- 必须严格管理日志备份
- 存储成本高
适用场景:
- 生产环境 OLTP 系统(银行、电商)
- 对数据一致性要求高的系统
- 需要灾难恢复的数据库
设置命令:
ALTER DATABASE MyDB SET RECOVERY FULL;典型备份策略:
- 每周一次完整备份
- 每天一次差异备份
- 每5~15分钟一次日志备份
-
大容量日志恢复模式(Bulk-Logged Recovery Model)
核心特点:
- 正常操作:像“完整模式”一样记录日志
- 大批量操作(Bulk Operations)最小日志化:
SELECT INTOBCP导入INSERT BULK- 索引重建
- 提高性能并减少日志占用
注意事项:
- 虽然支持日志备份,但在大容量操作期间会中断日志链
- 无法恢复到该操作中间的某个时间点
- 若未及时备份日志,可能导致数据丢失
优点:
- 加快大批量数据加载速度
- 减少日志空间使用
- 仍可在非大容量操作期间实现时间点恢复
适用场景:
- ETL 过程中的数据加载阶段
- 数据迁移
- 初始数据导入
最佳实践:
“临时切换”到此模式进行批量操作,完成后切回“完整模式”
-- 批量导入前ALTER DATABASE DW SET RECOVERY BULK_LOGGED;-- 导入完成ALTER DATABASE DW SET RECOVERY FULL;
12.3.4 三种模式对比表
| 特性 | 简单模式 | 完整模式 | 大容量日志模式 |
|---|---|---|---|
| 支持时间点恢复 | ❌ 否 | ✅ 是 | ⚠️ 否(大操作期间) |
| 支持事务日志备份 | ❌ 否 | ✅ 是 | ✅ 是 |
| 日志自动截断 | ✅ 是 | ❌ 否(需日志备份) | ❌ 否 |
| 日志增长控制 | 易 | 需管理 | 需管理 |
| 数据丢失风险 | 高 | 低 | 中 |
| 适合 OLTP | ❌ | ✅ | ⚠️(部分) |
| 适合数据仓库 | ✅ | ⚠️ | ✅ |
12.4 Backup
在 SQL Server 中,BACKUP 命令 是用于创建数据库或事务日志备份的核心 T-SQL 语句。它是保障数据安全、实现灾难恢复的基础工具。
12.4.1 什么是 BACKUP 命令?
BACKUP 命令将数据库的全部或部分数据复制到指定的备份设备(磁盘或磁带),以便在系统故障、数据损坏或误操作后进行恢复。
主要用途:
- 防止数据丢失
- 支持时间点恢复(Point-in-Time Recovery)
- 数据迁移与测试环境搭建
- 满足合规性要求
12.4.2 基本语法结构
BACKUP { DATABASE | LOG } database_nameTO backup_device[ WITH [ DIFFERENTIAL ], [ INIT | NOINIT ], [ NAME = '备份集名称' ], [ SKIP | NOFORMAT ], [ STATS [ = 百分比 ] ]]12.4.2 常用类型与示例
-
完整数据库备份(Full Backup)
BACKUP DATABASE MyDBTO DISK = 'D:\Backup\MyDB_Full.bak'WITHNAME = 'MyDB 完整备份',STATS = 10; -- 每完成10%输出一次进度备份整个数据库是所有其他备份策略的基础
-
差异数据库备份(Differential Backup)
BACKUP DATABASE MyDBTO DISK = 'D:\Backup\MyDB_Diff.bak'WITHDIFFERENTIAL,NAME = 'MyDB 差异备份';只备份自上次完整备份以来变化的数据页。恢复速度快于日志链,适合中大型数据库
-
事务日志备份(Transaction Log Backup)
BACKUP LOG MyDBTO DISK = 'D:\Backup\MyDB_Log.trn'WITHNAME = 'MyDB 日志备份';记录从上一次日志备份以来的所有事务支持时间点恢复必须配合“完整恢复模式”使用
-
文件/文件组备份(File/Filegroup Backup)
-- 文件组备份BACKUP DATABASE MyDBFILEGROUP = 'PRIMARY'TO DISK = 'D:\Backup\PrimaryFG.bak';-- 单个文件备份BACKUP DATABASE MyDBFILE = 'MyDB_Data'TO DISK = 'D:\Backup\DataFile.bak';适用于超大数据库(VLDB),可分步备份关键文件SQL Server 企业版支持
12.4.3 重要选项说明
| 选项 | 说明 |
|---|---|
WITH DIFFERENTIAL | 表示是差异备份 |
INIT | 覆盖现有备份文件中的备份集 |
NOINIT | 追加到现有备份文件(默认) |
NAME = 'xxx' | 给备份集命名,便于识别 |
STATS = n | 每完成 n% 输出一次状态 |
COMPRESSION | 启用压缩(节省空间,推荐) |
COPY_ONLY | 创建不影响日志链的副本备份 |
💡 推荐启用压缩(SQL Server 2008 R2+ 支持):
WITH COMPRESSION
12.4.4 典型备份策略示例
场景:生产 OLTP 系统(完整恢复模式)
| 时间 | 操作 |
|---|---|
| 周日 2:00 | BACKUP DATABASE ... FULL |
| 周一~周六 2:00 | BACKUP DATABASE ... DIFFERENTIAL |
| 每小时 | BACKUP LOG ... |
✅ 优势:
- 恢复快(只需全备 + 最新差异 + 少量日志)
- RPO(数据丢失窗口)小
12.5 事务日志备份
事务日志备份(Transaction Log Backup) 是 SQL Server 中实现高可用性、精确恢复和最小数据丢失的核心机制之一。它记录了数据库中所有事务的详细操作,是构建完整备份策略的关键组成部分。
12.5.1 什么是事务日志备份?
事务日志备份 是对数据库的 事务日志文件(.ldf) 进行备份的操作,它捕获从上一次日志备份以来所有已提交的事务。
- 文件扩展名通常为
.trn或.bak - 必须在 完整恢复模式(Full) 或 大容量日志恢复模式(Bulk-Logged) 下才能执行
- 不适用于“简单恢复模式”
-- 示例:事务日志备份命令BACKUP LOG MyDB TO DISK = 'D:\Backup\MyDB_Log.trn';12.5.2 事务日志的作用
SQL Server 使用 预写日志(Write-Ahead Logging, WAL) 机制:
- 所有修改先写入事务日志
- 再异步写入数据文件
- 保证“日志先行”,确保可恢复性
日志记录的内容:
- 每个事务的开始与结束
- 数据修改细节(如
UPDATE,INSERT,DELETE) - 页分配/释放信息
- 大容量操作(在大容量日志模式下部分记录)
12.5.3 事务日志备份的核心作用
| 作用 | 说明 |
|---|---|
| 🔹 支持时间点恢复(Point-in-Time Recovery) | 可将数据库恢复到某个精确时间点(如误删前一秒) |
| 🔹 控制日志文件增长 | 备份后不活动的日志可被截断(释放空间) |
| 🔹 实现低 RPO(恢复点目标) | 最多只丢失最后一次日志备份之后的数据 |
| 🔹 支持高可用技术 | 如日志传送(Log Shipping)、AlwaysOn 可用性组 |
12.5.4 工作原理:日志截断与检查点
-
日志截断(Log Truncation)
- 并非删除文件,而是标记“不活动”的日志空间为可重用
- 在以下情况下发生:
- 完成日志备份(在完整/大容量模式下)
- 检查点(Checkpoint)触发(在简单模式下自动进行)
⚠️ 如果长期不做日志备份 → 日志文件会无限增长!
-
检查点(Checkpoint)
- 将内存中的脏页(已修改但未写盘的数据页)刷入磁盘
- 标记已完成的事务,为日志截断提供依据
12.5.5 适用场景
| 场景 | 是否适合使用事务日志备份 |
|---|---|
| 生产 OLTP 系统(银行、电商) | ✅ 强烈推荐 |
| 要求零或接近零数据丢失 | ✅ 必需 |
| 高频交易系统 | ✅ 支持每5~15分钟一次备份 |
| 数据仓库(每天批量加载) | ⚠️ 一般不需要,可用简单模式 |
| 开发测试环境 | ❌ 通常不需要 |
12.5.6 典型备份策略(结合日志备份)
示例:完整恢复模式下的日常备份计划
| 时间 | 操作 | 命令示例 |
|---|---|---|
| 周日 02:00 | 完整备份 | BACKUP DATABASE MyDB TO DISK='full.bak' |
| 每天 02:00 | 差异备份 | BACKUP DATABASE MyDB DIFFERENTIAL ... |
| 每小时 | 事务日志备份 | BACKUP LOG MyDB TO DISK='log.trn' |
✅ 恢复时:
- 还原完整备份(NORECOVERY)
- 还原最新差异备份(NORECOVERY)
- 按顺序还原日志备份 → 可恢复到任意时间点
12.5.7 注意事项
| 注意事项 | 说明 |
|---|---|
| 必须定期备份日志 | 否则日志文件持续增长,可能导致磁盘满 |
| 不能单独用于恢复 | 必须基于一个完整或差异备份 |
| 按顺序应用 | 日志备份必须按 LSN(日志序列号)顺序还原 |
| 避免中断备份链 | 删除中间某个日志文件会导致后续无法恢复 |
| 启用压缩 | 减少日志传输时间和存储空间(SQL Server 2008+ 支持) |
-- 推荐启用压缩BACKUP LOG MyDB TO DISK = 'D:\Backup\Log.trn' WITH COMPRESSION;12.5.8 尾日志备份(Tail-Log Backup)
当数据库损坏但日志文件仍可用时,可执行:
BACKUP LOG MyDB TO DISK = 'D:\Backup\TailLog.trn' WITH NO_TRUNCATE;- 捕获故障前最后的操作
- 实现最大可能的数据保护
12.5.9 如何查看日志备份信息?
-- 查看备份文件中的日志范围RESTORE HEADERONLY FROM DISK = 'D:\Backup\Log.trn';
-- 查看是否包含特定时间点SELECT backup_start_date, backup_finish_date, first_lsn, last_lsnFROM msdb.dbo.backupsetWHERE database_name = 'MyDB' AND type = 'L'; -- L = Log第十三章 大规模数据库架构
13.1 分布式数据库
13.1.1 什么是分布式数据库?
分布式数据库 是指一个在多个物理位置上分布的数据库系统,这些位置通过网络连接,并由一个统一的数据库管理系统(DDBMS)进行管理。从用户角度看,它像一个逻辑上集中的数据库;但实际上数据分布在不同站点(节点)上。
分布式数据库系统由局部数据库管理系统、全局数据库管理系统GDBMS、全局数据字典、通信管理四部分组成我的答案:
典型架构:
+----------------+ | 用户/应用程序 | +-------+--------+ | +-----------v-----------+ | 分布式数据库管理系统 | +-----------+-----------+ | +-----------v-----------+ 网络 | 多个站点(Site) <-------------> 其他站点 | 数据存储与处理单元 | +-----------------------+13.1.2 分布式数据库的设计目标
| 目标 | 说明 |
|---|---|
| 数据共享 | 支持跨组织、跨地域的数据访问和共享 |
| 本地自治(Local Autonomy) | 每个站点可独立管理和控制本地数据 |
| 高可用性 | 某个站点故障不影响整体系统运行 |
| 可扩展性 | 易于添加新站点或扩容 |
| 透明性 | 对用户隐藏数据分布细节 |
| 减少通信开销 | 优化查询执行计划,降低网络传输量 |
13.1.3 核心特征(基本特性)
-
分布性(Distribution)
- 数据物理上分散在多个地理位置不同的站点
- 每个站点拥有自己的计算机系统(CPU、内存、磁盘)
- 所有站点通过网络互联
💡 示例:银行全国分行的数据分别存于各地服务器,但总行可统一查询
-
逻辑整体性(Logical Integration)
- 尽管数据物理分散,但逻辑上是一个统一的数据库
- 用户可以通过单一接口访问全局数据
- DDBMS 负责将用户请求分解并路由到相应站点
即:“看起来像一个库,实际分布在多地”
-
透明性(Transparency)
这是分布式数据库的关键优势之一,包括以下几种类型:
类型 含义 分片透明性 用户不知道数据被分片 位置透明性 用户不知道数据存放在哪个站点 局部映射透明性 用户不关心本地数据库的实现细节 复制透明性 数据可能有多份副本,用户无感知 📌 例如:
SELECT * FROM Employees WHERE salary > 5000;-- 用户无需知道 Employees 表是否分片、存在哪台服务器 -
数据分片(Data Fragmentation)
为了提高效率和管理灵活性,大表会被分割成多个片段(Fragment),存储在不同站点。
常见分片方式:
方式 说明 水平分片 按行划分(如按地区:北京员工 vs 上海员工) 垂直分片 按列划分(如基础信息放A站,薪资信息放B站) 导出分片 又称为导出水平分片,即水平分片的条件不是本关系属性的条件,而是其他关系属性的条件 混合分片 水平+垂直+导出结合 -
数据复制(Replication)
同一数据在多个站点保留副本,以提高:
- 查询性能(就近访问)
- 可用性(某站点宕机仍可读取其他副本)
- 容错能力
⚠️ 缺点:更新时需同步多个副本,一致性维护复杂
-
分布式事务管理
由于事务可能涉及多个站点,必须保证其 ACID 特性。
核心机制:两阶段提交协议(2PC)
协调者(Coordinator)↓参与者A: 准备 → 回答“就绪”参与者B: 准备 → 回答“就绪”↓协调者决定:提交↓参与者A: 提交 → 完成参与者B: 提交 → 完成- 所有节点要么全部提交,要么全部回滚
- 保证分布式事务的一致性
-
并发控制与恢复
- 并发控制:使用分布式锁或时间戳机制防止冲突
- 恢复控制:基于日志和检查点,支持故障后恢复
- 支持站点故障、通信中断等情况下的自动恢复
13.1.4 分布式数据库设计目标
最基本特征
本地自治、非集中式管理以及高可用性
分布透明性
位置独立性、数据分片独立性、数据复制独立性
其他性质
分布式查询处理、分布式事务管理、硬件独立性、操作系统独立性、网络独立性和数据库管理系统独立性
13.1.5 三层模式结构
| 层级 | 名称 | 说明 |
|---|---|---|
| 1 | 全局外模式 | 用户视图(可选) |
| 2 | 全局概念模式 | 全局逻辑结构,描述所有数据的统一视图 |
| 3 | 分片模式 | 将全局表划分为若干片段(水平/垂直分片) |
| 4 | 分配模式 | 每个分片存放在哪个站点(分配位置) |
| 5 | 局部概念模式 | 各站点本地数据库的逻辑结构 |
13.2 并行数据库
下面为您详细介绍 并行数据库(Parallel Database) 的概念、架构、特点及其在现代数据处理中的应用。
13.2.1 什么是并行数据库?
并行数据库(Parallel Database) 是指利用多个处理器、内存和磁盘资源,并行执行数据库操作(如查询、连接、排序等),以提高系统性能和吞吐量的数据库系统。
- 核心目标:提升大规模数据处理速度
- 适用于 OLAP(联机分析处理)、数据仓库、商业智能等场景
- 与分布式数据库不同,并行数据库更注重性能优化而非地理分布
13.2.2 并行数据库的设计目标
| 目标 | 说明 |
|---|---|
| 高性能 | 利用多处理器并行处理任务 |
| 高吞吐量 | 同时处理大量查询 |
| 线性加速(Linear Speedup) | N 倍资源 → 接近 N 倍速度 |
| 可扩展性 | 支持横向或纵向扩展 |
| 透明性 | 用户无需感知并行机制 |
理想情况下为线性加速(Speedup = 节点数)
13.2.3 并行数据库的主要体系结构
根据硬件资源共享方式的不同,并行数据库分为四种典型结构:
-
共享内存结构(Shared-Memory Architecture)
- 所有处理器共享同一主存和磁盘(主存储器)
- 类似于单机多核系统,以多处理器代替单处理器
- 处理器通过总线访问共享内存
特点:
- 结构简单,通信开销低
- 存在“总线瓶颈”,扩展性差(一般不超过几十个CPU)
- 适合小型并行系统
✅ 示例:SMP(对称多处理)服务器上的数据库
-
共享磁盘结构(Shared-Disk Architecture)
- 每个处理器有自己的内存
- 所有处理器通过高速网络访问公共磁盘阵列
特点:
- 单节点故障不影响整体
- 可实现高可用(如 Oracle RAC)
- 需要复杂的缓存一致性协议(Cache Coherency)
✅ 优点:灵活、容错性强
❌ 缺点:锁竞争严重,I/O 瓶颈可能出现在磁盘层✅ 示例:Oracle Real Application Clusters (RAC)
-
无共享结构(Shared-Nothing Architecture)
- 每个节点拥有独立的 CPU、内存、磁盘
- 节点之间通过高速网络通信
- 数据被分片(Partitioned)存储在不同节点上
特点:
- 扩展性最好(可扩展至数百甚至数千节点)
- 最小化资源争用
- 查询由协调节点分解后并行执行
- 容错性好(一个节点宕机只影响局部数据)
✅ 代表系统:
- Teradata
- Amazon Redshift
- Google BigQuery
- Apache Spark SQL
- ClickHouse 集群模式
📌 这是目前大规模数据仓库和云数据库的主流架构
-
层次结构(Hierarchical Architecture)
- 混合架构:结合上述多种结构
- 例如:
- 底层使用共享内存或多核节点
- 上层使用无共享集群
- 提供灵活性和性能平衡
✅ 适用于复杂企业级系统
13.2.4 并行操作的基本技术
-
数据划分(Data Partitioning)
将大表按某种策略拆分到多个节点:
方式 说明 轮转法(Round-Robin) 均匀分布,负载均衡 哈希分区(Hash Partitioning) 按主键哈希分布 范围分区(Range Partitioning) 按值范围划分(如时间) 👉 分区是实现并行扫描、连接、聚合的基础
-
并行查询处理
常见操作的并行化方式:
操作 并行方式 并行扫描 每个节点扫描本地数据段 并行选择/投影 在各节点本地完成 并行连接 使用 Hash Join 或 Replication Join 并行聚合 先局部聚合,再全局合并 并行排序 局部排序 + 归并排序 -
查询优化与调度
- 查询解析器将 SQL 分解为子任务
- 调度器分配到各节点执行
- 中间结果通过网络传输(需优化通信代价)
⚠️ 关键挑战:最小化网络传输量
13.2.5 并行数据库 vs 分布式数据库
| 对比项 | 并行数据库 | 分布式数据库 |
|---|---|---|
| 设计目标 | 性能、加速比 | 可用性、本地自治、地理分布 |
| 数据分布 | 为性能而分片 | 为管理或位置而分布 |
| 透明性 | 查询透明 | 位置、分片、复制透明 |
| 事务管理 | 通常集中或轻量级 | 强调两阶段提交(2PC) |
| 典型架构 | Shared-Nothing | 多种混合 |
| 应用场景 | 数据仓库、OLAP | 跨区域企业系统、云服务 |
💡 现代趋势:两者界限模糊(如云原生数据库兼具并行与分布特性)
13.3 BigTable
下面为您全面、系统地介绍 BigTable —— Google 开发的分布式、高性能、可扩展的 NoSQL 数据库系统,也是现代云数据库(如 HBase、Cloud Bigtable)的原型。
13.3.1 什么是 BigTable?
BigTable 是由 Google 在 2006 年提出的分布式、结构化数据存储系统,用于管理海量结构化数据(TB 到 PB 级),支撑 Google 核心服务(如网页索引、Google Maps、Gmail、Analytics 等)。
它不是关系型数据库,也不是传统意义上的“表”,而是一个:
稀疏、分布式、持久化的多维排序映射表(Sparse, Distributed, Persistent Multi-Dimensional Sorted Map)
13.3.2 核心设计目标
| 目标 | 说明 |
|---|---|
| 高可扩展性 | 支持数十亿行、数百万列、PB 级数据 |
| 高可用性 | 自动分片、容错、故障恢复 |
| 高性能读写 | 毫秒级响应,支持高并发 |
| 低成本 | 运行在廉价商用硬件上 |
| 简单模型 | 易于编程和集成 |
13.3.3 数据模型:三维键值映射
BigTable 的数据模型是一个四维结构:
( RowKey, ColumnFamily:ColumnQualifier, Timestamp ) → Value组成部分详解:
| 组件 | 说明 |
|---|---|
| Row Key(行键) | 字符串(byte array),是主键,按字典序排序。决定数据物理分布(分区依据)。 |
| Column Family(列族) | 必须在建表时预定义,多个列属于一个列族。同一列族的数据物理存储在一起(利于压缩与缓存)。 |
| Column Qualifier(列限定符) | 列族内的列名,例如 cf1:age、cf1:email。无需预定义,可动态添加。 |
| Timestamp(时间戳) | 可选,默认为写入时间。支持多版本(默认保留最近几个版本),可用于历史查询或冲突解决。 |
| Value(值) | 任意字节数组(byte[]),不解释内容,可存字符串、JSON、序列化对象等 |
示例:
| RowKey | ColumnFamily | Timestamp | Value |
|---|---|---|---|
user:alice | profile:name | 2024-01-01T10:00:00 | "Alice" |
user:alice | profile:email | 2024-01-01T10:00:00 | "alice@x.com" |
user:alice | clicks:page1 | 2024-01-02T09:30:00 | "home" |
user:alice | clicks:page1 | 2024-01-03T14:20:00 | "product" ← 多版本 |
✅ 注意:
- 列族
profile和clicks预先定义clicks:page1、clicks:page2是动态添加的列- 同一单元格有多个版本(按时间戳区分)
13.3.4 架构组成(三大组件)
BigTable 依赖 Google 的底层基础设施,主要由三个组件构成:
| 组件 | 作用 |
|---|---|
| Chubby | 分布式锁服务,用于协调 Master 和 Tablet Server,保证一致性(类似 ZooKeeper) |
| Master Server | 负责元数据管理、负载均衡、故障恢复、分配 Tablet 到 Tablet Server |
| ⚠️ 不处理读写请求,仅做调度 | |
| Tablet Server | 实际处理客户端的读写请求 |
| 每个 Tablet Server 管理多个 Tablet(即数据分片) |
💡 每个 Table 被切分为多个 Tablet(约 100–200MB),每个 Tablet 是一个连续的行范围,由一个 Tablet Server 管理
13.3.5 关键特性详解
-
自动分片(Tablet)
- 表被水平划分为多个 Tablet(按 RowKey 范围)
- 每个 Tablet 存储在某个 Tablet Server 上
- 当 Tablet 过大时自动分裂;过小时合并
- 实现无缝水平扩展
-
动态列(Schema-less)
- 列族需预先定义,但列限定符(列名)无需预定义
- 支持每行不同列,非常适合半结构化数据(如用户行为日志、传感器数据)
✅ 示例:
用户 A:有 clicks, clicks
用户 B:只有 clicks, purchases -
多版本支持
- 每个单元格可保存多个版本(按时间戳)
- 默认保留最近 3~10 个版本(可配置)
- 支持“快照查询”:查询某时间点的数据
-
高效存储与压缩
- 使用 SSTable(Sorted String Table) 存储数据
- 数据按 RowKey 排序,支持快速查找
- 使用 GFS(Google File System) 或 Colossus 存储底层文件
- 支持多种压缩算法(如 Snappy、Brotli)
-
高可用与容错
- 数据复制到多个 Tablet Server(通常 3 份副本)
- Master 故障不影响读写(只影响调度)
- Tablet Server 故障 → Master 重新分配其 Tablet 到其他节点
第十四章 数据仓库与数据挖掘
14.1 ODS 体系
14.1.1 什么是 ODS?
ODS(Operational Data Store,操作型数据存储) 是一种面向主题的、集成的、接近实时的、用于支持日常运营和决策的中间层数据存储系统。
它位于源业务系统(OLTP) 和 数据仓库(DW) 之间,是企业数据架构中的“缓冲区”或“过渡层”。
核心定位:
ODS = 源系统 + 数据仓库之间的桥梁
| 层级 | 功能 | 响应速度 | 数据粒度 | 更新频率 |
|---|---|---|---|---|
| OLTP 系统 | 支持日常交易(如订单、支付) | 秒级响应 | 细粒度 | 实时/分钟级 |
| ODS | 支持近实时报表、跨系统查询、操作分析 | 分钟~小时级 | 细粒度 | 几分钟到1小时更新 |
| 数据仓库(DW) | 支持战略分析、历史趋势、BI 报表 | 小时~天级 | 聚合/宽表 | 日级或周级 |
14.1.2 ODS 的核心特征
| 特征 | 说明 |
|---|---|
| ✅ 面向主题(Subject-Oriented) | 围绕特定业务主题组织数据(如客户、订单、库存),而非按应用系统 |
| ✅ 集成性(Integrated) | 整合多个异构源系统(ERP、CRM、POS、财务系统等)的数据,统一格式、编码、语义 |
| ✅ 可更新性(Updateable) | 与数据仓库不同,ODS 支持频繁更新、插入、删除,反映最新业务状态 |
| ✅ 近实时性(Near Real-Time) | 数据从源系统抽取后,在几分钟到几小时内加载,支持“今天的数据今天看” |
| ✅ 细节级数据(Detail-Level) | 存储的是原始明细数据,不是聚合后的统计结果 |
| ✅ 短期保留 | 一般只保留最近 3~24 个月数据,长期历史归档到数据仓库 |
14.1.3 ODS 与相关概念对比
| 对比项 | ODS | 数据仓库(DW) | 数据集市(DM) | OLTP 系统 |
|---|---|---|---|---|
| 目的 | 支持操作型分析、跨系统查询 | 支持战略决策、历史分析 | 支持部门级分析 | 支持日常事务处理 |
| 数据更新 | 频繁更新(增删改) | 只追加(Append-Only) | 只追加或周期更新 | 实时增删改 |
| 数据粒度 | 细粒度(明细) | 聚合或宽表 | 聚合或主题窄表 | 细粒度 |
| 时间范围 | 最近数月 | 数年 | 数月~数年 | 当前实时 |
| 响应速度 | 分钟~小时级 | 小时~天级 | 小时级 | 秒级 |
| 用户 | 业务分析师、运营人员 | 高管、BI 分析师 | 部门业务人员 | 一线员工 |
💡 简单记忆:
- OLTP:干实事(下单、付款)
- ODS:查今天的事(“昨天有多少退货?”)
- DW:看过去的事(“过去三年销售趋势?”)
14.1.4 ODS 典型应用场景
| 场景 | 说明 |
|---|---|
| ✅ 跨系统数据整合查询 | 比如:客服要查一个客户的订单、会员积分、售后记录 —— 这些信息分散在 CRM、ERP、客服系统中,通过 ODS 统一查询 |
| ✅ 近实时监控报表 | 如:当日销售额、物流状态追踪、库存预警(每小时刷新) |
| ✅ 数据仓库的前置准备层 | ODS 作为 DW 的“清洗中转站”,完成去重、标准化、主数据匹配后再导入 DW |
| ✅ 临时应急分析 | 当 DW 未更新或延迟时,用 ODS 快速提供最新数据 |
| ✅ 支持操作型 BI | 不是“分析趋势”,而是“现在发生了什么”——如“当前排队等待处理的订单数” |
14.1.5 ODS 的典型架构(分层结构)
[源系统] ↓ (ETL/ELT)[ODS 层] ←→ 实时/准实时同步(CDC、消息队列、定时调度) ↓ (ETL)[数据仓库 DW] ↓[数据集市 / BI 报表]常用技术实现:
- 数据抽取:CDC(Change Data Capture)、Kafka、Debezium、SQL Server CDC
- 数据加载:批量+增量混合(如每天全量 + 每小时增量)
- 存储引擎:关系型数据库(Oracle、SQL Server、PostgreSQL)、MPP 数据库(Greenplum)、甚至 NoSQL(MongoDB)
- 同步机制:
- 消息驱动(如 Kafka + Flink)
- 定时调度(如 Airflow 每15分钟跑一次任务)
14.1.6 四类 ODS
| 类型 | 更新频率 | 数据来源 | 特点 | 应用场景 |
|---|---|---|---|---|
| ODS I | 天级(隔夜) | 操作系统 | 批量加载,类似轻量 DW | 次日统计报表 |
| ODS II | 小时级(如4h) | 操作系统 | 定时增量 | 当日趋势监控 |
| ODS III | 秒级/近实时 | 操作系统 | CDC 实时同步 | 实时风控、物流跟踪 |
| ODS IV | 多向流动 | 操作系统 + DW/DM 反馈 | 双向数据流,含分析反馈 | 智能运营、标签反写 |
14.2 操作型/DSS型数据
| 特征 | 操作型数据(OLTP) | DSS型数据(OLAP / 数据仓库) |
|---|---|---|
| 用途 | 支持日常业务操作(如下单、支付) | 支持管理决策、分析、报表 |
| 更新频率 | 高频(秒/分钟级增删改) | 低频(日/周批量加载,基本只读) |
| 数据粒度 | 细粒度(明细记录) | 粗粒度(聚合、汇总) |
| 数据冗余 | 通常规范化(减少冗余) | 通常反规范化(增加冗余,提升查询效率) |
| 性能要求 | 高并发、低延迟响应(毫秒级) | 复杂查询、大数据量扫描,允许较长时间响应 |
| 数据需求 | 固定、预知(如“登录”、“结账”) | 不确定、探索性(“为什么销量下降?”) |
14.3 OLTP/OLAP
14.3.1 基本定义
| 缩写 | 全称 | 中文名称 | 核心目标 |
|---|---|---|---|
| OLTP | Online Transaction Processing | 联机事务处理 | 支持企业日常业务操作,确保高并发、低延迟的事务处理 |
| OLAP | Online Analytical Processing | 联机分析处理 | 支持复杂查询与多维数据分析,辅助管理决策 |
💡 简单记忆:
- OLTP = 做事(下单、支付、登录)
- OLAP = 看数据(报表、趋势、预测)
14.3.2 核心特征对比表
| 维度 | OLTP(事务型) | OLAP(分析型) |
|---|---|---|
| 主要用途 | 支撑日常业务操作 | 支持决策分析、商业智能 |
| 用户群体 | 一线员工、客服、收银员、系统自动调用 | 管理层、分析师、BI 团队 |
| 数据量 | 单次操作数据量小(一条记录) | 查询涉及海量历史数据(百万~亿级) |
| 查询类型 | 简单、短事务:INSERT, UPDATE, DELETE, SELECT BY PK | 复杂聚合查询:GROUP BY, JOIN, SUM, AVG, ROLLUP |
| 访问模式 | 单条记录为主(点查) 如:查询订单ID=1001 | 集合访问为主(范围扫描) 如:查询“2024年华东区销售额” |
| 响应时间 | 毫秒级(<1秒) 用户体验敏感 | 秒级~分钟级 可接受较长等待 |
| 并发数 | 高并发(数千~数万 TPS) | 并发较低(几十到几百) |
| 数据更新频率 | 高频更新(每秒数百笔) | 几乎只读,定期批量加载(T+1) |
| 数据模型 | 规范化设计(3NF) 减少冗余,保证一致性 | 反规范化设计(星型/雪花模型) 预聚合、冗余维度,提升查询速度 |
| 索引策略 | 主键索引、唯一索引、外键索引 | 复合索引、位图索引、物化视图 |
| 存储引擎 | InnoDB(MySQL)、SQL Server、Oracle RAC | 列式存储(ClickHouse、Snowflake)、MPP 数据库 |
| 数据来源 | 直接来自业务系统 | 来自多个 OLTP 系统(ETL 后导入) |
| 典型应用 | 电商下单、银行转账、机票预订、医院挂号 | 销售趋势分析、客户画像、库存预警、财务报表 |
14.3.3 典型场景举例
OLTP 场景(做事)
| 应用 | 操作示例 |
|---|---|
| 电商平台 | 用户下单 → 扣库存 → 生成订单 → 支付回调 |
| 银行系统 | ATM 取款 → 更新账户余额 → 记录流水 |
| 医院系统 | 挂号 → 分诊 → 开药 → 收费 |
| 工厂 ERP | 扫码入库 → 更新库存 → 生成工单 |
→ 这些操作要求:快、准、稳
OLAP 场景(看数据)
| 应用 | 操作示例 |
|---|---|
| 商业智能报表 | “上季度各区域销售额排名” |
| 客户流失分析 | “近半年未登录且消费 <100 的用户占比” |
| 促销效果评估 | “双十一期间,使用优惠券的用户复购率提升了多少?” |
| 库存优化 | “哪些商品连续3周销量低于平均值?是否该下架?” |
| 财务预测 | “基于过去5年数据,预测明年Q1营收增长15%” |
→ 这些操作要求:深、广、准 —— 不求快,但求洞察
14.3.4 架构关系:OLTP → ETL → OLAP
现代企业数据架构通常是:
[OLTP 系统] ↓ (ETL: Extract, Transform, Load)[数据仓库 / 数据湖] ← 存储清洗后的结构化/半结构化数据 ↓[OLAP 引擎 / BI 工具] ← 如 Tableau、Power BI、Superset、ClickHouse ↓[可视化报表、大屏、预测模型]📌 关键点:
- OLTP 是“源头”,负责产生原始数据
- OLAP 是“终点”,负责挖掘价值
- 两者互补而非替代
14.3.5 技术选型对比
| 类别 | OLTP 推荐技术 | OLAP 推荐技术 |
|---|---|---|
| 数据库 | MySQL、PostgreSQL、Oracle、SQL Server | ClickHouse、Apache Druid、Snowflake、Amazon Redshift、Google BigQuery |
| 存储格式 | 行存(Row Store) | 列存(Column Store) |
| 计算引擎 | 本地事务引擎 | MPP(大规模并行处理) |
| 缓存机制 | Redis(缓存热点数据) | Cube 预聚合、物化视图 |
| 数据同步 | 实时复制(CDC) | 批量抽取(每日/每小时) |
14.3.6 为什么不能混用?
| 问题 | 如果用 OLTP 做分析 | 如果用 OLAP 做事务 |
|---|---|---|
| 性能 | 分析查询拖慢交易系统 → 支付变卡 | 分析型系统不支持高并发写入 → 下单失败 |
| 一致性 | OLTP 事务强一致,但 OLAP 可能延迟 | OLAP 不支持 ACID,不适合金融操作 |
| 资源占用 | 复杂查询占满 CPU/内存 → 事务超时 | 低效的索引导致写入缓慢 |
| 可用性 | 一个慢查询导致整个系统雪崩 | 系统不可用影响运营 |
⚠️ 曾有公司尝试直接在 Oracle OLTP 上跑 BI 报表 → 每天下午 3 点系统崩溃,因为分析任务抢光了连接池!
14.3.7 OLAP 的三种主要实现方式
根据数据存储结构和计算模型的不同,OLAP 主要有三种实现方式:
| 类型 | 英文全称 | 中文名称 | 存储方式 | 特点 |
|---|---|---|---|---|
| MOLAP | Multidimensional OLAP | 多维 OLAP | 预计算的多维立方体(Cube) | 快速响应,但预计算耗资源 |
| ROLAP | Relational OLAP | 关系型 OLAP | 存在关系数据库中(星型/雪花模型) | 灵活,可扩展性强 |
| HOLAP | Hybrid OLAP | 混合 OLAP | 结合 MOLAP 和 ROLAP | 平衡速度与灵活性 |
-
MOLAP(Multidimensional OLAP)
原理:
- 将数据预先聚合并存储在一个多维立方体(Cube) 中
- 使用专门的多维数据库引擎(如 Microsoft Analysis Services)
- 查询时直接读取 Cube 中的预计算结果
优点:
- 查询速度极快(毫秒级)
- 支持复杂的多维运算(如时间序列、占比计算)
缺点:
- 预计算耗时长、占用空间大
- 数据更新慢(需重新构建 Cube)
- 不适合超大规模数据
技术代表:
- Microsoft SQL Server Analysis Services (SSAS)
- Essbase(Oracle)
- SAP BW
适用场景:
- 固定报表需求
- 分析维度少、数据量适中(<1亿行)
- 对性能要求极高
-
ROLAP(Relational OLAP)
原理:
- 数据仍存储在关系型数据库中
- 使用星型或雪花模型组织事实表和维度表
- 查询通过 SQL 实现,由数据库引擎执行聚合
优点:
- 数据实时性好(直接查最新数据)
- 可扩展性强(支持 PB 级数据)
- 灵活,无需预计算所有组合
缺点:
- 复杂查询可能较慢(依赖优化器和索引)
- 需要强大的数据库性能支持
技术代表:
- 数据库:Snowflake、Redshift、ClickHouse、Greenplum、PostgreSQL
- BI 工具:Tableau、Power BI、Superset、FineBI
- 中间层:物化视图、列式存储、分区表
适用场景:
- 动态分析需求
- 数据量巨大
- 需要与现有数据仓库集成
-
HOLAP(Hybrid OLAP)
原理:
- 结合 MOLAP 和 ROLAP 的优势
- 常用聚合结果 → 存为 Cube(MOLAP)
- 明细数据或不常用维度 → 保留在关系库中(ROLAP)
优点:
- 在速度与灵活性之间取得平衡
- 减少 Cube 存储压力
- 支持部分预计算 + 按需计算
缺点:
- 架构复杂
- 查询路由逻辑复杂(何时用 Cube?何时查库?)
技术代表:
- Microsoft SSAS(支持 HOLAP 模式)
- 一些云数仓平台(如阿里云 AnalyticDB)
适用场景:
- 既有固定报表,又有探索性分析
- 中大型企业混合使用需求
14.3.8 OLAP 中常见的多维分析操作
下面为您详细介绍 OLAP(联机分析处理) 中常见的多维分析操作。这些操作是数据分析师、BI 工程师进行决策支持的核心工具,帮助用户从多个角度快速探索和理解数据。
什么是多维分析?
多维分析(Multidimensional Analysis) 是指将数据组织成“维度 + 度量”的模型(如星型模型),并允许用户从不同维度组合对数据进行观察与分析。
核心概念:
| 概念 | 说明 | 示例 |
|---|---|---|
| 维度(Dimension) | 分析的视角或分类标准 | 时间、地区、产品、客户等级 |
| 层级(Hierarchy) | 维度内部的上下级关系 | 时间:年 → 季度 → 月 → 日 |
| 地区:国家 → 省 → 市 | ||
| 度量(Measure) | 可聚合的数值指标 | 销售额、订单数、利润、平均单价 |
📊 数据模型示例:
事实表:销售记录┌────────────┬───────────┬───────────┬──────────┐│ 时间 │ 地区 │ 产品类别 │ 销售额 │├────────────┼───────────┼───────────┼──────────┤│ 2024-01-01 │ 北京 │ 手机 │ 500,000 ││ 2024-01-01 │ 上海 │ 笔记本 │ 300,000 │└────────────┴───────────┴───────────┴──────────┘常见的 OLAP 多维分析操作
以下是五种最核心的 OLAP 操作,通常被称为 “FASMI”原则中的分析能力体现。
-
上卷
将数据从细粒度汇总到更粗的层级,也叫“向上钻取”或“聚合”。
操作方式:
- 在某个维度上向上移动一个层级
- 使用
SUM()、AVG()等聚合函数
示例:
-
当前视图:按“城市”查看销售额
-
上卷后:按“省份”查看总销售额
-
SQL 类似:
SELECT province, SUM(sales) FROM fact_sales GROUP BY province;
目的:
- 查看整体趋势
- 减少数据量,提升可视化清晰度
-
下钻/钻取
将数据从粗粒度展开为更细粒度,深入查看细节。
操作方式:
- 在某个维度上向下移动一个层级
示例:
- 当前视图:2024年全国总销售额
- 下钻:→ 2024年各季度 → 各月 → 各日
- 或:全国 → 华东区 → 江苏省 → 南京市
目的:
- 定位问题根源(如某个月销量下降)
- 探索异常数据背后的细节
-
切片(Slice)
固定一个维度的值,只查看该值对应的数据子集。
操作方式:
- 选择一个维度的一个成员,形成二维视图
示例:
- 原始数据:多维(时间 × 地区 × 产品)
- 切片操作:固定“时间 = 2024年Q1”
- 结果:仅显示 Q1 的数据,形成“地区 × 产品”二维表
目的:
- 聚焦特定条件下的数据表现
-
切块(Dice)
同时固定多个维度的值,查看一个多维子集。
操作方式:
- 多个维度同时筛选
示例:
- 条件:
- 时间:2024年Q1
- 地区:华东区
- 产品类别:手机
- 结果:仅查看“华东区在Q1销售的手机”相关数据
目的:
- 进行精准分析,缩小范围
🔍 注:切片是“一刀”,切块是“多刀”
-
旋转(Pivot / Rotate)
改变维度在报表中的位置(行 ↔ 列互换),以不同视角展示数据。
操作方式:
- 行列转换
示例:
-
原始表格:
| Q1 | Q2 | Q3 | Q4----------|-------|-------|-------|-------手机 | 50万 | 60万 | 70万 | 80万笔记本 | 40万 | 45万 | 50万 | 55万 -
旋转后:
| 手机 | 笔记本----------|-------|--------Q1 | 50万 | 40万Q2 | 60万 | 45万Q3 | 70万 | 50万Q4 | 80万 | 55万
目的:
- 更直观地比较不同维度的表现
- 适配不同报表布局需求
扩展操作(高级功能)
除了五大基本操作,现代 BI 工具还支持以下增强分析功能:
| 操作 | 说明 |
|---|---|
| 钻过(Drill-across) | 在多个事实表之间切换分析(如从“销售”切换到“库存”) |
| 钻透(Drill-through) | 从聚合数据点击查看底层明细记录(如点击“总销售额”查看每笔订单) |
| 排名(Ranking) | 对结果排序并显示 Top N(如“销量前十的产品”) |
| 占比分析(% of Total) | 计算每个项占总体的比例 |
| 同比/环比 | 与去年同期或上一期比较增长率 |
图形化演示(以销售分析为例)
假设我们有维度:时间、地区、产品,度量:销售额
| 操作 | 视图变化 |
|---|---|
| 原始视图 | 三维立方体:时间 × 地区 × 产品 → 销售额 |
| 切片(Slice) | 固定“时间=2024年” → 得到“地区 × 产品”二维表 |
| 切块(Dice) | 固定“时间∈{Q1,Q2}”、“地区=华东” → 得到子立方体 |
| 上卷(Roll-up) | “城市” → “省份” → 总计 |
| 下钻(Drill-down) | “2024年” → “Q1” → “1月” → “1月1日” |
| 旋转(Pivot) | 行:产品,列:季度 → 改为 行:季度,列:产品 |
14.4 关联规则计算过程
支持度:事务中同时包含 X、Y 的百分比
置信度:在事务已经包含X的情况下包含 Y 的百分比
14.5 数据仓库
14.5.1 什么是数据仓库?
数据仓库(Data Warehouse, DW) 是一个面向主题的、集成的、时变的、非易失的数据集合,用于支持管理决策过程。
这是由数据仓库之父 Bill Inmon 在1990年代提出的经典定义,至今仍是行业标准。
在企业数据仓库中,操作型数据存储层(ODS),一般用以支撑即时 OLAP 和全局型 OLTP 应用。
四大核心特征(4个“V”)
| 特征 | 说明 |
|---|---|
| 面向主题(Subject-Oriented) | 数据按业务主题组织(如“销售”、“客户”、“产品”),而非按应用系统(如ERP、CRM) |
| 集成(Integrated) | 整合来自多个异构源系统(OLTP、外部系统)的数据,统一命名、编码、格式、度量单位 |
| 时变(Time-Variant) | 包含历史数据,支持时间维度分析(如过去5年趋势) |
| 非易失(Non-Volatile) | 数据一旦进入仓库,一般不修改或删除,只追加(只读为主) |
💡 记忆口诀:“主题集成时变化,非易失存历史”
14.5.2 数据仓库 vs OLTP 系统
| 对比维度 | 数据仓库(DW) | 操作型系统(OLTP) |
|---|---|---|
| 目的 | 支持决策分析、报表、BI | 支撑日常事务处理(下单、支付) |
| 数据来源 | 多个OLTP系统 + 外部数据 | 单一业务系统 |
| 数据内容 | 历史、聚合、宽表 | 实时、明细、规范化的事务记录 |
| 用户群体 | 管理层、分析师、BI团队 | 一线员工、客服、系统自动调用 |
| 查询类型 | 复杂查询(GROUP BY, JOIN, 聚合) | 简单事务(INSERT/UPDATE/SELECT by PK) |
| 响应速度 | 秒级~分钟级(允许慢查) | 毫秒级(必须快) |
| 数据更新 | 定期批量加载(T+1) | 高频实时写入 |
| 数据模型 | 星型模型 / 雪花模型(反规范化) | 第三范式(3NF,高度规范化) |
| 存储规模 | TB ~ PB 级 | GB ~ TB 级 |
✅ 数据仓库是“分析引擎”,OLTP是“执行引擎”。
14.5.3 数据仓库典型架构(三层结构)
[数据源] → [ETL 层] → [数据仓库层] → [应用层]-
数据源层(Source Systems)
- 来源包括:
- OLTP系统(ERP、CRM、POS、财务系统)
- 日志文件、Excel、API接口
- 第三方数据(如天气、市场行情)
- 来源包括:
-
ETL 层(Extract, Transform, Load)
- Extract(抽取):从源系统抽取数据
- Transform(转换):清洗、标准化、去重、映射、派生字段
- Load(加载):将处理后的数据加载到数据仓库
✅ ETL 是数据仓库的“心脏”,决定数据质量
-
数据仓库层(Data Warehouse Layer)
- 存储集成后的历史数据
- 通常分为:
- ODS(操作型数据存储):近实时数据缓冲区(可选)
- DW 主体层:事实表 + 维度表(星型模型)
- 数据集市(Data Mart):按部门/主题划分的子集(如销售集市、财务集市)
-
应用层(Application Layer)
- BI 工具:Tableau、Power BI、FineBI
- 报表系统、KPI看板、预测模型
- 决策支持系统(DSS)
14.5.4 数据仓库的核心模型:星型模型(Star Schema)
星型模型组成:
| 组件 | 说明 | 示例 |
|---|---|---|
| 事实表(Fact Table) | 存放数值型度量值(可加性) | 销售事实表:订单ID、商品ID、销售时间、金额、数量 |
| 维度表(Dimension Table) | 存放描述性属性(用于分析) | 商品维度表:商品ID、名称、类别、品牌 |
时间维度表:日期、星期、季度、年份 |
✅ 特点:
- 结构简单,查询效率高
- 支持多维分析(OLAP)
- 支持钻取(Drill-down)、上卷(Roll-up)、切片(Slice)、切块(Dice)
示例:
┌─────────────┐ │ 时间维度 │ │ (DateDim) │ └──────┬──────┘ │┌────────────────────────────┐│ 销售事实表 ││ (SalesFact) ││ - 订单ID ││ - 商品ID → ────────────────┤│ - 客户ID → ────────────────┤│ - 销售金额 ││ - 销售数量 │└──────┬────────────┬───────┘ │ │┌──────▼──────┐ ┌────▼─────┐│ 商品维度表 │ │ 客户维度表 ││ (ProductDim)│ │ (CustomerDim)│└─────────────┘ └──────────┘14.5.5 数据仓库的主要作用
| 作用 | 说明 |
|---|---|
| ✅ 支持商业智能(BI) | 生成销售报表、利润分析、客户画像 |
| ✅ 支持趋势预测 | 基于历史数据预测未来销量、库存需求 |
| ✅ 提升决策效率 | 管理层通过可视化仪表盘快速获取洞察 |
| ✅ 整合分散数据 | 打破“信息孤岛”,实现企业级数据统一视图 |
| ✅ 保障数据一致性 | 统一口径、统一指标(如“销售额”定义一致) |
| ✅ 历史追溯能力 | 可查询任意历史时间点的数据状态 |
14.5.6 数据仓库构建流程(6步法)
| 步骤 | 内容 |
|---|---|
| 1️⃣ 需求分析 | 明确目标:谁用?用来看什么?(如“区域销售排名”) |
| 2️⃣ 模型设计 | 设计星型模型:确定事实表、维度表、粒度 |
| 3️⃣ 数据抽取 | 从ERP、CRM等系统抽取原始数据 |
| 4️⃣ 数据转换 | 清洗、映射、去重、计算衍生字段(ETL) |
| 5️⃣ 数据加载 | 将数据加载进数据仓库 |
| 6️⃣ 应用开发 | 开发报表、仪表盘、自助分析平台 |
⚠️ 注意:数据质量 > 数据量。脏数据会导致错误决策!