数据库的控制功能主要指对数据库中数据的管理和维护,确保数据的安全性、完整性和一致性。数据库控制功能主要包括:并发控制、性能优化、完整性约束、以及备份与恢复。

一、并发控制

(一)事务

DBMS 运行的基本工作单位是事务,事务是用户定义的一个数据库读写操作序列,这些操作序列要么全做,要么全不做,是一个不可分割的工作单位。

事务具有以下四个基本特性,通常被称为ACID 特性

  • 原子性(Atomicity):事务中的所有操作是一个不可分割的整体,要么全部完成,要么全部不完成,不能部分地完成。一旦某一步执行失败,就会全部回滚到初始状态。
  • 一致性(Consistency):事务必须使数据库从一个一致性状态转换到另一个一致性状态。这意味着事务执行过程中和执行结束后,数据库中的数据必须满足所有预定义的规则和约束。
  • 隔离性(Isolation):一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的事务之间不会相互影响。多个事务并发运行,但看上去像串行调度执行一样。
  • 持久性(Durability):一旦事务被提交,它对数据库的修改就是永久性的,即使系统发生故障也不会丢失。

一致性是事务 ACID 四大特性中最重要的属性,而原子性、隔离性和持久性,都是作为保障一致性的手段。

(二)数据不一致

如果数据不一致,那么在并发“读 - 写”或并发“写 - 写”的场景,容易出现以下问题:

1. 修改丢失

T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。

T1 T2
读 A=10
读 A=10
A=A-5,写回
A=A-8,写回

T1 对 A 的修改被 T2 覆盖,T1 的修改不起作用。

2. 脏读

T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。

T1 T2
读 A=20
A=A+50,写回
读 A=70
rollback
A=20,写回

T1 对 A 进行修改,但之后回滚撤销,T2 读取的数据与数据库中不一致,是错误的数据(脏数据)。

3. 不可重复读

T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。

T1 T2
读 A=20
读 B=30
计算 A+B=50
读 A=20
A=A+50,写回
读 A=70
读 B=30
计算 A+B=100

T1 读取数据并进行计算,T2 更新了数据,T1 再次读取数据,然而计算结果不一致。

4. 幻读

T1 读取某个范围的数据,T2 在这个范围内插入或删除数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。

(三) 隔离级别

为了解决数据不一致的问题,数据库中的事务隔离分为如下四种:

  1. 读未提交:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读。
  2. 读已提交:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。这是大多数数据库默认的隔离级别。
  3. 可重复读:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以防止脏读和不可重复读,但是幻读仍有可能发生。这是 MySQL 数据库默认的隔离级别。
  4. 串行化:最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行。
隔离级别 导致脏读 导致不可重复读 导致幻读 实现机制
读未提交 Y Y Y
读已提交 N Y Y 读锁
可重复读 N N Y 读锁 +MVVC
串行化 N N N 对所有数据行加锁

隔离级别从上到下越来越严格,并发造成的数据不一致可能性越小,但需要付出的性能代价越大。

(四) 锁

数据库的隔离级别是通过锁机制来实现的。从不同的角度对锁进行分类:

  • 根据 性能
    • 乐观锁:通过版本对比来实现并发控制,假设事务之间不会发生冲突,直到提交操作时才会检查是否有冲突。
      • 适用于并发冲突较少的场景。
    • 悲观锁:假设事务之间会发生冲突,因此在访问数据之前就会加锁,保证同一时间只有一个事务能够访问数据。
      • 适用于并发冲突较多的场景。
  • 根据 对数据库的操作粒度
    • 表锁:锁定整个表,在事务操作时会锁定整张表,影响表中所有数据。
      • 每次操作锁住一张表。加锁的开销小,加锁快,不会出现死锁;锁定粒度大,容易发生锁冲突,并发度最低。适用于整张表数据迁移的场景。
    • 行锁:只锁定某行数据,使其他事务无法修改该行数据,但不影响表中其他数据的访问。
      • 每次操作只锁住表中一行数据。加锁的开销大,加锁慢,可能会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。
  • 根据 对数据库的操作类型
    • 读锁 共享锁,S 锁 Shared):在事务读取数据时加锁,其他事务可以读取同一数据,但不能进行写操作。
      • 事务 T1 对数据库添加 S 锁,事务 T1 可以进行读写,事务 T2 只能读不能写。
    • 写锁 排它锁,X 锁 eXclusive):在事务对数据进行写入或修改时加锁,阻止其他事务对该数据的读写操作。
      • 事务 T1 对数据库添加 X 锁,事务 T1 可以进行读写,事务 T2 无法读写。