数据库系统原理

参考:

事务

为什么会出现事务(Transaction)?

  • 为了当应用程序访问数据库的时候,事务能够简化我们的编程模型。
  • 应用层不需要去考虑各种各样的潜在错误(网络错误、服务器宕机等)和并发问题;

什么是事务?

  • 满足 ACID 四个特性的一组操作;
  • 可以通过 commit 操作结束一个事务,也可以通过 RollBack 操作回滚到事务的开始;

ACID:

  • 原子性(Atomicity):事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚(可以通过 undo log 实现)。
  • 一致性(Consistency):事务执行前后的状态都是正确的,事务操作应当将数据库从一个正确状态转移到另一个正确状态;
  • 隔离性(Isolation):降低并发事务之间的影响程度;
  • 持久性(Durability):一旦事务提交,则其所做的修改将会永远保存到数据库中;若系统发生崩溃,可以通过 redo log 重做;

并发问题

并发导致的一致性问题:

  • 丢失修改:并发进行的事务对同一个数据进行修改,后者覆盖了前者的内容;

  • 脏读:事务 A 修改了一个数据,但未提交;事务 B 读到了事务 A 未提交的更新结果,如果事务 A 提交失败进行了回滚,事务 B 读到的就是脏数据。

  • 不可重复读:在同一个事务中,对于同一份数据读取到的结果不一致。比如,事务 B 在事务 A 提交前读到的结果,和提交后读到的结果可能不同。

    避免这种情况:加锁或 MVCC;

  • 幻读:在同一个事务中,同一个查询多次返回的结果不一致。

    因为对于新增的记录根本无法加锁。需要将事务串行化,才能避免幻读。

多版本并发控制(Multi Version Concurrency Control, MVCC):

  • 实现方式:通过在每行记录后面保存两个隐藏的列:一个保存了行的创建时间,一个保存行的过期时间(或删除时间)。当然存储的并不是实际的时间值,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。

在 REPEATABLE READ 隔离级别下,MVCC 的“增删改查”四种操作:

  • INSERT:InnoDB 为新插入的每一行保存当前系统版本号作为行创建标识。
  • DELETE:InnoDB 为删除的每一行保存当前系统版本号作为行删除标识。
  • UPDATE:InnoDB 会插入一行新的记录,保存当前系统版本号作为创建标识,同时保存当前系统版本号到原来的行作为行删除标识。
  • SELECT:InnoDB 会根据以下两个条件检查每行记录,InnoDB 只查找:
    1. 创建标识早于当前事务版本的数据行,这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
    2. 行的删除标志要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。

封锁

在 MySQL 中,提供了两种封锁粒度:行级锁以及表级锁。如何选用锁?

  • 应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。
  • 加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销。因此封锁粒度越小,系统开销就越大。

锁的类型(读写锁):

  1. 互斥锁(Exlusive),X 锁,或写锁。

    事务 T 对数据 D 加了 X 锁,T 就可以对 D 进行读写,其他事务不对能 D 加锁;

  2. 共享锁(Shared),S 锁,或读锁。

    事务 T 对数据 D 加了 S 锁,T 只可以对 D 进行读,加锁期间其他事务可以对 D 加 S 锁、不能加 X 锁;

意向锁(Intention Locks):

  • 背景:在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。
  • 意向锁在原来的 X/S 锁之上引入了 IX/IS 表锁,它们使用有以下规定:
    1. 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁;
    2. 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。
    3. 任意 IS/IX 锁之间都是兼容的,因为它们只表示想要对表加锁,而不是真正加锁;

封锁协议:

  1. 一级封锁协议:事务 T 要修改数据 A 时必须加 X 锁,直到 T 结束才释放锁;
  2. 二级封锁协议:在一级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁;
  3. 三级封锁协议:在一级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁;

隔离级别

数据库隔离性表现的四个等级,由低到高分别是:

  1. 未提交读(READ UNCOMMITTED):事务中的修改即使没有提交,对其他事务也是可见的;
  2. 提交读(READ COMMITTED):一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。
  3. 可重复读(REPEATABLE READ):保证在同一个事务中多次读取同一数据的结果是一样的;
  4. 可串行化(SERIALIZABLE):强制事务串行执行,这样多个事务互不干扰,不会出现并发一致性问题。