完整性与安全

从并发正确性到约束、触发器和安全控制

Posted by CloudingYu on May 26, 2026

一、并发控制

1.1 并发控制的正确性基础

数据库并发控制的核心目标是:让多个事务并发执行,但结果等价于某种串行执行的结果

  • 串行调度(Serial Schedule):事务一个接一个顺序执行,是正确性的基础。
  • 可串行化(Serializable):一个并发调度如果与某个串行调度等价,就称为可串行化调度。

这就是为什么与资金有关的系统都用关系数据库——它有一个很严格的正确性保证,即使在并发执行的时候,结果也一定与某个正确的串行结果对应。

1.2 两阶段封锁协议(Two-Phase Locking Protocol, 2PL)

两阶段封锁是大部分商业数据库采用的封锁协议。

核心思想

  • 在对任何数据做操作之前,必须先获得该数据的锁(注意:不是事务开始时就获取所有锁,而是操作前获取)
  • 一旦释放了任何一把锁,就不再获取新的锁

两个阶段

  1. 增长阶段(Growing Phase):不断获取锁,不释放锁
  2. 收缩阶段(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)

  1. 若在调度 $S$ 中 $T_i$ 读了 Q 的初始值,则在 $S'$ 中 $T_i$ 也读 Q 的初始值
  2. 若在 $S$ 中 $T_i$ 读了 $T_j$ 产生的 Q 值,则在 $S'$ 中 $T_i$ 也读 $T_j$ 产生的 Q 值
  3. 若在 $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 为什么将约束放在数据库端而非应用端?

数据库支撑很多应用,如果把约束放在应用程序端,会有以下问题:

  1. 应用程序开发人员频繁变动,团队可能早就解散
  2. 一旦约束发生变化(如政策变化),所有应用程序都要改

放在数据库端的好处:只需修改数据库中的约束定义即可,不需要改动每个应用程序。

以前本科有年龄限制,后来教育部放开了——如果约束在数据库里,直接改约束条件就行,不需要逐个应用去修改。

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)模型

  1. 事件(Event):对数据库的插入、删除、修改操作
  2. 条件(Condition):事件触发后判断是否符合特定条件
  3. 动作(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) 最低

两条基本规则

  1. 读规则(No Read Up):用户只能读取安全级别低于或等于自己的数据(可以向下读)
  2. 写规则(No Write Down):用户只能写入安全级别等于自己的数据(不能向下写)

同一艘船,秘密级别显示”侦查任务”,公开级别显示”运输任务”。如果拥有秘密级别信息的人把它写入公开级别,就会导致泄密——所以必须禁止向下写。

信息流分析:系统在运行时会分析信息从高级别向低级别的流向,防止信息泄露。

4.8 统计数据库的安全与隐私

问题:统计查询(如工资总和)虽然不直接查个人数据,但可以通过巧妙的查询组合推断出个体信息。

不允许查一个人的工资,但可以查至少 10 个人的。攻击者先查”目标用户 + 另外9个人的工资总和”,再查”张同事 + 同样那9个人的工资总和”,两者相减就知道张同事的工资了。

防范措施

  • 限制每次查询的最小数据量(如至少 N 条)
  • 限制两次查询结果交集的最小值(至少 M 条)
  • 记录所有查询操作,检查合并后是否可能泄露
  • 数据污染:在数据中加入噪声
  • 差分隐私(Differential Privacy):通过严格控制信息泄露边界来保护隐私

4.9 数据加密

动机:云数据库时代,企业把数据放到云端,但云服务提供商可能偷看数据。如果把数据加密后再上传,即使被拿走也无法解读。

加密的副作用

  • 数据加密后,原来的大小关系、排序等性质都会丢失(如 20 岁加密后可能比 18 岁的加密结果还小)
  • 各种比较、排序相关的查询都无法直接执行

解决方案

  • 同态加密(Homomorphic Encryption):加密数据上可以直接进行计算,解密后的结果与在明文上计算的结果一致
  • 目前很多数据库厂商在研究将同态加密集成到数据库系统中

五、存储过程(Stored Procedure)

5.1 概念与动机

问题:应用程序在数据库外部执行,通过网络与数据库通信,效率较低。

解决方案:在数据库服务器内部也写一段应用程序——即存储过程。存储过程运行在数据库管理系统的内存空间中,与数据库共享同一地址空间。

好处

  1. 高性能:查询结果不需要网络传输,直接内部传递,速度极快
  2. 可重用/可共享:放在存储过程中的通常是底层公用模块,多个应用共享
  3. 网络通信量少
  4. 更安全:用户只需有执行存储过程的权限,不需要对底层表的直接访问权限

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):数据节点,下面是存储

关键技术特点

  1. 多写:华为和阿里都已实现多节点同时写操作(真正的分布式事务),在 VLDB 2023 上发表了相关论文

  2. 全局缓冲区 + 局部缓冲区
    • 全局缓冲区:所有节点数据都有体现
    • 局部缓冲区:本节点运算的局部数据
    • 通过 RDMA 实现数据的快速复制
  3. 故障快速恢复:一台机器宕机后,内存数据丢失,但由于全局缓冲区中有备份,机器重启后可以快速恢复,故障切换时间(RTO)极短

  4. 密文查询:华为 GaussDB 做了全密态数据加密,加密后的性能劣化不超过 5%(由于加密算法设计得好),支持密文上的查询操作——利用了同态加密等方法

  5. AI for DB:将 AI 技术用于数据库的查询优化、索引推荐、异常监控等方面。由于查询优化的根本性困难(很难做到非常准确和高效),AI 的引入成为一种重要的增强手段

  6. 代价估计优化:在分布式环境下考虑了行存/列存的代价估计、网络通讯代价估计。前端数据库(如华为)可以用基于代价的优化(CBO),而早期版本或简单数据库则用基于规则的优化(RBO)

  7. 向量化执行引擎:利用列存 + 向量化计算,在 HTAP 混合场景下效果很好

  8. 解释执行 → 编译执行:从传统的解释执行(逐条执行)发展到编译执行(利用编译器优化技术提高效率)

  9. 向量化逻辑时钟:不用传统的全局统一时钟(基于严格时间),而是基于事件——每次数据传递计数加一,通过事件顺序判断事务先后。简化了分布式时钟同步问题

  10. 存储过程无缝迁移:可以从 Oracle 将存储过程无缝迁移到 GaussDB

  11. TPC 测试与 TPMC:TPC(Transaction Processing Performance Council)测试是反映数据库性能的标准测试,TPMC 表示单位时间内完成的事务个数(Transactions Per Minute)。GaussDB 单机的 TPMC 性能非常好

  12. LSM 冷热分离架构:LSM(Log-Structured Merge)树的冷热分离架构在国产数据库中也广泛使用

  13. 弹性部署与扩展比:支持单机版本和分布式部署,扩展比(性能随机器扩展的提升效率)很好

历史背景:华为从 2002 年就开始做数据库,早期做了大量内存数据库,主要用于运营商计费系统——每个月的话费是如何根据通话记录(市话、长途等)计算出来的,计费规则非常复杂,需要高性能的内存数据库来支持。当时包括华为、中兴都在做内存数据库。后来在此基础上发展出了 GaussDB。

6.9 其他国产数据库厂商

数据库/公司 特点
OceanBase(阿里) 分布式关系数据库,支持多写
人大金仓 最早期的国产数据库之一,国家长期支持
南大通用 国家支持的国产数据库
达梦 早期国产数据库
星环 上海企业,类似 HBase 技术
麒麟 上海企业,做 Hive 的商业化
华为 GaussDB 事务型 + 分析型(GaussDB(A)),支持多写、密文查询