一、并发控制
1.1 并发控制的正确性基础
数据库并发控制的核心目标是:让多个事务并发执行,但结果等价于某种串行执行的结果。
- 串行调度(Serial Schedule):事务一个接一个顺序执行,是正确性的基础。
- 可串行化(Serializable):一个并发调度如果与某个串行调度等价,就称为可串行化调度。
这就是为什么与资金有关的系统都用关系数据库——它有一个很严格的正确性保证,即使在并发执行的时候,结果也一定与某个正确的串行结果对应。
1.2 两阶段封锁协议(Two-Phase Locking Protocol, 2PL)
两阶段封锁是大部分商业数据库采用的封锁协议。
核心思想:
- 在对任何数据做操作之前,必须先获得该数据的锁(注意:不是事务开始时就获取所有锁,而是操作前获取)
- 一旦释放了任何一把锁,就不再获取新的锁
两个阶段:
- 增长阶段(Growing Phase):不断获取锁,不释放锁
- 收缩阶段(Shrinking Phase):不断释放锁,不获取锁
加锁的时间控制得越短越好(出于并发性考虑),但太短又会带来正确性问题,所以一般遵循两阶段封锁协议。
1.3 事务的存取模式与隔离级别
事务存取模式:
- 只读(Read-Only):事先声明事务只读,调度器可以放心地只下读锁
- 读写(Read-Write):即使申请的是读锁,系统也可能分配写锁(因为后续可能有写操作)
隔离级别(Isolation Levels)——对应不同的加锁策略:
| 隔离级别 | 含义 | 加锁策略 |
|---|---|---|
| Serializable(可串行化) | 最严格,保证并发执行结果可串行化 | 采用两阶段封锁协议 |
| Repeatable Read(可重复读) | 只允许读已提交数据,两次读之间不允许其他事务修改该数据 | 第一次读前加锁,第二次读后才解锁(可能不是严格2PL) |
| Read Committed(读已提交) | 只允许读已提交数据,但不要求可重复读 | 读前加锁,读后立即释放(不是两阶段封锁) |
| Read Uncommitted(读未提交) | 最弱,可以读取未提交的数据(脏读) | 相当于无锁(一条横线) |
隔离级别实际上对应了不同的自动加锁/解锁策略。在写程序时从来不需要手动加锁,就是因为数据库根据你设定的隔离级别自动完成了这些事情。
1.4 基于时间戳的并发控制
两种并发控制方式对比:
| 封锁(Lock) | 时间戳(Timestamp) | |
|---|---|---|
| 优点 | 严格正确性保证 | 不需要等待 |
| 缺点 | 拿不到锁就得等待,影响并行性 | 不断回滚,回滚概率非常大 |
1.5 冲突可串行化(Conflict Serializability)
冲突操作:如果两个操作中至少有一个是写操作,则它们是冲突的,次序不可交换。
- 读-读:非冲突,可以交换次序
- 读-写:冲突,不可交换(读到哪个写的结果会变)
- 写-写:冲突,不可交换(后续读操作读到的值会不同)
冲突可串行化定义:如果两个调度 $S$ 和 $S'$ 可以通过一系列非冲突操作的交换得到彼此,则它们冲突等价。如果调度 $S$ 与某个串行调度冲突等价,则 $S$ 是冲突可串行化的。
关键认识:
- 两个调度中,作用在不同数据项上的操作,不管是什么类型,交换都不影响结果
- 并发控制可以带来很大的收益:很多时候访问的都是不同数据,完全可以同时做
冲突可串行化中等价的判断标准是,在冲突操作的先后次序上必须保持一致(如 T1 先写 A、T2 后读 A)。
1.6 视图可串行化(View Serializability)
视图可串行化是对冲突可串行化的放松——因为冲突可串行化要求比较严格,实现起来比较困难。
视图等价的条件(对每个数据项 Q):
- 若在调度 $S$ 中 $T_i$ 读了 Q 的初始值,则在 $S'$ 中 $T_i$ 也读 Q 的初始值
- 若在 $S$ 中 $T_i$ 读了 $T_j$ 产生的 Q 值,则在 $S'$ 中 $T_i$ 也读 $T_j$ 产生的 Q 值
- 若在 $S$ 中最后执行写操作的是 $T_i$,则在 $S'$ 中最后执行写的也是 $T_i$
视图可串行化的”放松”体现在哪里?写操作的顺序——在冲突可串行化中,所有写操作必须严格按顺序;但在视图可串行化中,中间写操作的顺序可以有一定交换,只要最后写操作的事务一致、读操作读到的数据来源一致即可。换句话说,只要读的数据都是对的,最后数据库保持的状态是对的,就可以。
1.7 可恢复性与级联回退
问题引入:前面的调度讨论中都没有考虑事务提交(commit)和终止回滚(rollback)。
不可恢复的调度:$T_1$ 写 A,$T_2$ 读 A,然后 $T_1$ rollback。$T_1$ 回退后,$T_2$ 读到的 A 就是错的。如果 $T_2$ 已经 commit,那么这个错误就无法弥补了。
比如 $T_1$ 扣了一笔钱,$T_2$ 读的是扣钱之后的结果并 commit 了,但 $T_1$ 又 rollback 了——你读的数据读错了,而且已经提交了,没法恢复。
可恢复调度(Recoverable Schedule):如果 $T_j$ 读了 $T_i$ 写的数据,则 $T_j$ 的 commit 必须在 $T_i$ 的 commit 之后。
级联回退(Cascading Rollback):一个事务的失败引起其他事务也跟着回退。避免级联回退的方法是:$T_j$ 只读 $T_i$ 已经提交后写的数据(即读到的一定是别人已提交的数据)。
数据库在进行调度时,会根据执行顺序保证可恢复性和避免级联回退。
1.8 实际案例:12306 的并发问题
12306 刚推出第一年时非常难用,原因就是锁加得太多。
具体问题:比如上海到北京一张票,有人从南京买到济南,这张票会被拆成三张票。系统在处理时会把这三张票同时锁住,处理完再释放——一次锁多条记录,锁得太多导致并发能力极差。
后续优化增加了分布式并行处理能力。
其他案例:
- SQL Server:早期并发性能也不好。图灵奖得主 Jim Gray(先在 IBM 负责 DB2 事务,后去微软)帮助微软改进了 SQL Server 的事务处理。
- 国产数据库替代:银行系统国产化替代中,最担心的就是国产数据库的并发能力能否胜任。
二、数据库完整性(Integrity)
2.1 为什么将约束放在数据库端而非应用端?
数据库支撑很多应用,如果把约束放在应用程序端,会有以下问题:
- 应用程序开发人员频繁变动,团队可能早就解散
- 一旦约束发生变化(如政策变化),所有应用程序都要改
放在数据库端的好处:只需修改数据库中的约束定义即可,不需要改动每个应用程序。
以前本科有年龄限制,后来教育部放开了——如果约束在数据库里,直接改约束条件就行,不需要逐个应用去修改。
2.2 完整性约束的类型
- 主键(Primary Key):不允许有空值、不允许重复
- 外键(Foreign Key):参照完整性,外键值必须在被参照表的主键中存在
- UNIQUE:某列不允许有重复值
- 域约束(Domain Constraint):定义数据类型,限制取值范围(如 color 类型只能取五个值)
2.3 外键的参照动作(Referential Actions)
当主键数据被修改或删除时,外键如何跟随变化:
| 动作 | 含义 |
|---|---|
| NO ACTION | 如果子表还有引用,不允许删除/修改主键 |
| CASCADE | 级联删除/修改所有引用该主键的外键记录 |
| SET NULL | 将外键值设为 NULL |
| SET DEFAULT | 将外键值设为缺省默认值 |
院系合并时,如果直接删除旧院系记录,相关人员记录会失去参照对象。可以用 NO ACTION 阻止删除,也可以用 SET NULL/DEFAULT 临时处理。
2.4 CHECK 约束与断言(Assertion)
CHECK 约束:在定义表时可以添加 CHECK 子句,包含条件表达式。在对表进行插入、删除、修改操作时,系统自动执行 CHECK 检查。
1
CHECK (年龄 >= 15 AND ((性别 = '男' AND 年龄 < 35) OR (性别 = '女' AND 年龄 < 30)))
断言(Assertion):数据库级别的约束,定义后系统会持续检查。
- 例1:每位教师开设的课程不能超过 10 门
- 例2:不允许男生选吴教师的课
- 例3:每门课不超过 50 个男生选修
执行机制:每当对相关表执行插入/修改操作,就会触发断言的执行(本质上就是执行一段查询来验证约束条件)。如果断言被违反,操作失败,数据放入专门的表中供查看。
CHECK 和断言本质上就是一些查询——一旦对相关表有操作,就触发断言执行;一旦发现不符合条件,操作就失败。
三、触发器(Trigger)
3.1 触发器的基本概念
触发器(Trigger),也叫主动规则(Active Rule),历史上曾出现过一种叫”主动数据库”(Active Database)的概念。
触发器的 Event-Condition-Action(ECA)模型:
- 事件(Event):对数据库的插入、删除、修改操作
- 条件(Condition):事件触发后判断是否符合特定条件
- 动作(Action):若条件满足,则执行动作(可以是一条 SQL 语句、一小段程序或多条 SQL 语句)
买一瓶矿泉水(事件)→ 判断库存是否低于阈值(条件)→ 若低于阈值,向进货人员发消息”矿泉水只有49瓶了,该进货了”(动作)。
3.2 触发器的定义要素
- 触发器名称
- 触发时机:BEFORE / AFTER
- 触发事件:INSERT / UPDATE / DELETE
- 目标表
- 新旧元组引用(old row / new row)
- 触发粒度:FOR EACH ROW / FOR EACH STATEMENT
- 触发条件(WHEN 子句)
- 触发动作(SQL 语句或程序段)
3.3 触发器实例
例1:成绩不能降低 当 SC 表的成绩被修改时,如果新成绩比旧成绩低,自动恢复到旧成绩。
例2:课程人数限制 当课程号发生更新时,检查选课人数是否超过 50。如果超过,将超出部分的学生记录映射到另一张表。
3.4 触发器的注意事项
在主动数据库的研究中,最怕的问题是触发器级联触发——一个触发器的执行可能触发另一个触发器,导致系统混乱。因此在主动数据库研究中,做了大量如何限制触发器级联的工作。
四、数据库安全
4.1 安全是一个多层次的问题
数据库安全涉及多个层面:
| 层次 | 内容 |
|---|---|
| 物理/机房 | 计算机设备和机房的安全防护 |
| 操作系统 | 数据文件放在操作系统上,OS 安全不足可能导致数据库文件被拷贝 |
| 网络 | 分布式数据库中数据在网络传输,可能被窃取 |
| 数据库 | 用户身份核查、使用权限检查 |
数据库安全重点关注数据库层面的权限控制。
4.2 权限类型
数据访问权限:
- 读(SELECT)
- 插入(INSERT)
- 修改(UPDATE)
- 删除(DELETE)
模式修改权限:
- 索引权限(创建/删除索引)
- 资源权限(创建新关系)
- 修改权限(增加/删除属性)
- 撤销权限(删除关系)
数据库的每一种操作都有对应的权限,通过授权(GRANT)和回收(REVOKE)来控制。
4.3 GRANT 与 REVOKE
授权(GRANT):
1
GRANT <权限> ON <数据库元素> TO <用户> [WITH GRANT OPTION]
WITH GRANT OPTION:被授权者可以将该权限再授权给其他人。
回收(REVOKE):
1
REVOKE <权限> ON <数据库元素> FROM <用户> [RESTRICT | CASCADE]
- RESTRICT:如果被授权者已将权限转授给其他人,则拒绝回收(必须先处理级联权限)
- CASCADE:回收权限的同时,所有由此转授出去的权限也一并回收
4.4 权限转授图
权限在不同用户之间形成一张转授图。一个人是否拥有某权限,取决于从权限源头到这个人的转授路径是否存在。
DBA 授权给 U1、U2、U3;U2 又把权限给 U3,U3 又给回 U2。如果只回收 DBA 给 U2 的权限,因为 U3 还给过 U2,U2 仍然有权限——必须把 U3 的授权也去掉。
4.5 组(Group)与角色(Role)
为什么需要组和角色? 如果每个用户都要单独授权,用户多了非常麻烦。
组(Group):用户集合,授权给组即授权给组内所有成员。
角色(Role):比组更灵活的概念。
- 数据库定义角色(如”教务”角色、”财务”角色)
- 一个用户可以拥有多个角色
- 用户以特定角色登录时,获得该角色对应的权限
角色机制的好处——同一个用户,以教务角色登录看到教学数据,以财务角色登录看到财务数据,管理更加灵活。
4.6 自主访问控制与强制访问控制
数据库安全分两个级别:
自主访问控制(Discretionary Access Control, DAC):
- 商用数据库的主要安全机制
- 通过 GRANT/REVOKE 实现权限管理
- 对应安全级别 C 类及以下
强制访问控制(Mandatory Access Control, MAC):
- 安全数据库(C 类以上)采用
- 对数据安全的防护更加严格
- 不仅访问模型有特殊要求,系统构建、进程管理等方面都有严格规定
4.7 强制访问控制的基本模型(Bell-LaPadula)
每个用户和数据都被赋予一个安全级别(Security Level):
| 级别 | 含义 |
|---|---|
| 绝密(Top Secret) | 最高 |
| 机密(Secret) | |
| 秘密(Confidential) | |
| 公开(Unclassified) | 最低 |
两条基本规则:
- 读规则(No Read Up):用户只能读取安全级别低于或等于自己的数据(可以向下读)
- 写规则(No Write Down):用户只能写入安全级别等于自己的数据(不能向下写)
同一艘船,秘密级别显示”侦查任务”,公开级别显示”运输任务”。如果拥有秘密级别信息的人把它写入公开级别,就会导致泄密——所以必须禁止向下写。
信息流分析:系统在运行时会分析信息从高级别向低级别的流向,防止信息泄露。
4.8 统计数据库的安全与隐私
问题:统计查询(如工资总和)虽然不直接查个人数据,但可以通过巧妙的查询组合推断出个体信息。
不允许查一个人的工资,但可以查至少 10 个人的。攻击者先查”目标用户 + 另外9个人的工资总和”,再查”张同事 + 同样那9个人的工资总和”,两者相减就知道张同事的工资了。
防范措施:
- 限制每次查询的最小数据量(如至少 N 条)
- 限制两次查询结果交集的最小值(至少 M 条)
- 记录所有查询操作,检查合并后是否可能泄露
- 数据污染:在数据中加入噪声
- 差分隐私(Differential Privacy):通过严格控制信息泄露边界来保护隐私
4.9 数据加密
动机:云数据库时代,企业把数据放到云端,但云服务提供商可能偷看数据。如果把数据加密后再上传,即使被拿走也无法解读。
加密的副作用:
- 数据加密后,原来的大小关系、排序等性质都会丢失(如 20 岁加密后可能比 18 岁的加密结果还小)
- 各种比较、排序相关的查询都无法直接执行
解决方案:
- 同态加密(Homomorphic Encryption):加密数据上可以直接进行计算,解密后的结果与在明文上计算的结果一致
- 目前很多数据库厂商在研究将同态加密集成到数据库系统中
五、存储过程(Stored Procedure)
5.1 概念与动机
问题:应用程序在数据库外部执行,通过网络与数据库通信,效率较低。
解决方案:在数据库服务器内部也写一段应用程序——即存储过程。存储过程运行在数据库管理系统的内存空间中,与数据库共享同一地址空间。
好处:
- 高性能:查询结果不需要网络传输,直接内部传递,速度极快
- 可重用/可共享:放在存储过程中的通常是底层公用模块,多个应用共享
- 网络通信量少
- 更安全:用户只需有执行存储过程的权限,不需要对底层表的直接访问权限
5.2 存储过程的实际应用
- 大型系统通常分为两层:底层公用模块用存储过程实现,上层偏业务的模块在外部实现
- 国产数据库迁移的关键:很多老系统为了提高性能,使用了大量存储过程(特别是 Oracle),国产数据库要替代 Oracle,必须能无缝迁移这些存储过程
国产数据库(如华为高斯 DB)出来之后,一个很重要的功能就是做存储过程的迁移——因为老系统里存储过程太多了。
六、国产数据库发展
6.1 发展历程
国产数据库的发展大致经历以下阶段:
| 阶段 | 时间 | 状态 |
|---|---|---|
| 不可用 | 1990s-2000s | 数据库市场全是 Oracle、DB2,国产只有达梦、人大金仓等,基本上”不可用”——只能用于并发量极小、数据量极小的场合(如办公系统),系统完整度有很大缺陷 |
| 可用 | 2010s | 进入互联网时代后快速进步 |
| 好用 | 当前(~2024后) | 全国约 400 家国产数据库厂商(后收缩至上百家),银行核心系统已实现国产替代 |
当年有过一个项目要求基于国产数据库开发,后来发现完全没法用,只能在别的数据库上调完程序再搬过来。
6.2 国产化替代与银行系统
- 几年前国家开始要求重点行业全部转向国产(计算机 → 操作系统 → 数据库 → 中间件)
- 银行系统的国产化替代 deadline 约为明年年底,但已有大量银行完成了全面替换
- 银行核心系统对数据库来说是最高难度:用户数极多、并发量极大、涉及资金安全
如果国产数据库能替代银行核心系统的国外产品,那在其他任何信息化场景下都不会出问题。
6.3 云计算带来的”弯道超车”机会
背景:Oracle 和 DB2 从 1980 年代初就开始发展,已经打磨了 30 多年。国产数据库从零起步,性能优化经验差距巨大。
云计算/分布式思想的核心价值:
- 单机性能比不过 Oracle,但可以多放几台服务器
- 通过分布式架构,用数量堆出整体性能
- 这是国产数据库最重要的技术路线
6.4 事务型 vs 分析型数据库
国产数据库产品分为两大类型:
| 类型 | 特点 | 例子 |
|---|---|---|
| 事务型(TP) | 需要加锁、并发控制,要求更高 | 华为 GaussDB、OceanBase |
| 分析型(AP) | 主要是读操作,无事务问题,无锁问题 | 华为 GaussDB(A) |
两者的实现机制有差别,大部分厂商将两者做成两套不同的系统。
6.5 多版本并发控制(MVCC)
传统做法的问题:对数据做 UPDATE 时直接覆盖原值。这时如果有其他事务仍在读旧版本,就会出错(在时间戳方法中会导致回滚)。
MVCC 的核心思想:
- 每次更新不覆盖旧数据,而是创建一个新版本
- 旧版本仍然保留(相当于数据库保留多个”快照”)
- 读操作根据事务 ID来确定应该读哪个版本(读早于自己事务版本的版本)
- 删除操作只是给版本打上”已删除”的标记
- 修改操作:生成新版本,标记旧版本
MVCC 解决的是读写冲突——写事务创建新版本,读事务仍然可以读旧版本,不需要回滚。代价是多存了一些数据,但在云计算的 KV(Key-Value)存储架构下,数据本来就被拆得很散,多版本在数据组织上是方便的。
6.6 乐观锁与悲观锁
MVCC 解决了读的问题,但写写冲突仍然需要锁来处理:
悲观锁(Pessimistic Locking):
- 假设冲突经常发生
- 从一开始就对数据加锁,修改完再释放
- 相当于传统的两阶段封锁协议
- 适用场景:写冲突较多
乐观锁(Optimistic Locking):
- 假设冲突不太会发生
- 平时不加锁,在修改时进行判断
- 常用 CAS(Compare-And-Swap) 机制:读出一个值 A,处理后想写入 B,写入前判断当前值是否还是 A——如果是则写入 B,否则重试
- 有些实现还加上版本号来避免 ABA 问题(值从 A 变成 B 又变回 A)
- 适用场景:读多写少,写冲突较少
6.7 阿里 OceanBase 早期架构
- 一主多从架构:一个节点负责读写,其他节点只负责读
- 优点:避免分布式事务问题(只有一个写节点)
- 写操作日志通过 RDMA(Remote Direct Memory Access)在节点间快速复制
- RDMA:高速数据中心网络技术,可以实现内存到内存的直接数据交换,不需要经过 TCP/IP 七层协议栈
6.8 华为 GaussDB 架构特点
核心组件:
- CM(Cluster Manager):集群管理模块
- GTM(Global Transaction Manager):全局事务管理器,负责全局事务 ID、快照时间戳等
- CN(Coordinator Node):协调节点,查询优化和查询执行的地方,负责任务分解和调度
- DN(Data Node):数据节点,下面是存储
关键技术特点:
-
多写:华为和阿里都已实现多节点同时写操作(真正的分布式事务),在 VLDB 2023 上发表了相关论文
- 全局缓冲区 + 局部缓冲区:
- 全局缓冲区:所有节点数据都有体现
- 局部缓冲区:本节点运算的局部数据
- 通过 RDMA 实现数据的快速复制
-
故障快速恢复:一台机器宕机后,内存数据丢失,但由于全局缓冲区中有备份,机器重启后可以快速恢复,故障切换时间(RTO)极短
-
密文查询:华为 GaussDB 做了全密态数据加密,加密后的性能劣化不超过 5%(由于加密算法设计得好),支持密文上的查询操作——利用了同态加密等方法
-
AI for DB:将 AI 技术用于数据库的查询优化、索引推荐、异常监控等方面。由于查询优化的根本性困难(很难做到非常准确和高效),AI 的引入成为一种重要的增强手段
-
代价估计优化:在分布式环境下考虑了行存/列存的代价估计、网络通讯代价估计。前端数据库(如华为)可以用基于代价的优化(CBO),而早期版本或简单数据库则用基于规则的优化(RBO)
-
向量化执行引擎:利用列存 + 向量化计算,在 HTAP 混合场景下效果很好
-
解释执行 → 编译执行:从传统的解释执行(逐条执行)发展到编译执行(利用编译器优化技术提高效率)
-
向量化逻辑时钟:不用传统的全局统一时钟(基于严格时间),而是基于事件——每次数据传递计数加一,通过事件顺序判断事务先后。简化了分布式时钟同步问题
-
存储过程无缝迁移:可以从 Oracle 将存储过程无缝迁移到 GaussDB
-
TPC 测试与 TPMC:TPC(Transaction Processing Performance Council)测试是反映数据库性能的标准测试,TPMC 表示单位时间内完成的事务个数(Transactions Per Minute)。GaussDB 单机的 TPMC 性能非常好
-
LSM 冷热分离架构:LSM(Log-Structured Merge)树的冷热分离架构在国产数据库中也广泛使用
- 弹性部署与扩展比:支持单机版本和分布式部署,扩展比(性能随机器扩展的提升效率)很好
历史背景:华为从 2002 年就开始做数据库,早期做了大量内存数据库,主要用于运营商计费系统——每个月的话费是如何根据通话记录(市话、长途等)计算出来的,计费规则非常复杂,需要高性能的内存数据库来支持。当时包括华为、中兴都在做内存数据库。后来在此基础上发展出了 GaussDB。
6.9 其他国产数据库厂商
| 数据库/公司 | 特点 |
|---|---|
| OceanBase(阿里) | 分布式关系数据库,支持多写 |
| 人大金仓 | 最早期的国产数据库之一,国家长期支持 |
| 南大通用 | 国家支持的国产数据库 |
| 达梦 | 早期国产数据库 |
| 星环 | 上海企业,类似 HBase 技术 |
| 麒麟 | 上海企业,做 Hive 的商业化 |
| 华为 GaussDB | 事务型 + 分析型(GaussDB(A)),支持多写、密文查询 |