数据库系统原理

事务

事务指满足ACID特性的一组操作,可以通过Commit提交一个事务,也可以使用Rollback进行回滚 。

1583059258400

ACID

  1. 原子性(Atomicity):事务是不可分割的最小单元,要么所有操作全部Commit成功,要么全部失败Rollback
  2. 一致性(Consistency):数据库在事务执行前后保持一致性状态。在一致性状态下,所有事务对同一数据的读取结果都是相同的
  3. 隔离性(Isolation):一个事务所做修改在最终提交之前,对其他事务不可见
  4. 持久性(Durability):一旦事务提交,则所做的修改将会永远保存到数据库中
  • 只有满足一致性,事务执行结果才能正确
  • 无并发情况下,只要满足原子性,就可以满足一致性
  • 并发情况下,多个事务并发执行,需要满足原子性和隔离性,才能满足一致性
  • 事务持久性是为了应对系统崩溃的情况

1583061131652

并发一致性问题

在并发环境下,事务的隔离性难以保证,会出现很多并发一致性问题

  1. 丢失修改:T1T2都修改了一个数据AT1先修改A=10T2后修改A=20T2的修改会覆盖T1的修改。
  2. 读脏数据:T1修改了数据AT2随后读取了数据A。若T1撤销了此次修改,T2读取的数据就是脏数据。
  3. 不可重复读:T1读取了数据AT2随后修改了数据A。若T1再次读取数据A,两次读取结果不同。
  4. 幻影读:T1读取了某个范围的数据,T2随后在这个范围内插入数据,若T1再次读取这个范围内数据,两次读取结果不同。

不可重复读与幻读相似,区别在于:不可重复读的重点在于修改,而幻读的重点在于新增或删除

封锁

封锁粒度

  • 行级锁
  • 表级锁

锁定的数据量越少,发生锁争用的可能就越小,系统并发程度就越高。

加锁需要消耗资源,锁的各种操作(如获取锁、释放锁以及检查锁状态)都会增加系统开销,封锁粒度越小,系统开销越大。

封锁类型

  • 读写锁
    • 互斥锁(Exclusive),简写为X锁,又称写锁
    • 共享锁(Shared),简写为S锁,又称读锁
    • 一个事务对数据对象A加了X锁,可以对A进行读取和更新。加锁期间其他事务不能对A加任何锁
    • 一个事务对数据对象A加了S锁,可以对A进行读取操作,但是不能进行更新操作。加锁期间其他事务能对AS锁,但是不能加X
  • 意向锁
    • 在存在行级锁和表级锁的情况下,事务T想要对表AX锁,就需要先检测是否存在其他事务对表A或者表A中的任意一行加了锁,对A的每一行都检测一次,非常耗时
    • 意向锁在X/S锁之上引入IX/ISIX/IS都是表锁,用来表示一个事务想要在表中的某个数据行上加了X锁或者S锁,有如下规定:
      • 一个事务获得某个数据行对象的S锁之前,必须先获得表的IS锁或者更强的锁
      • 一个事务获得某个数据行对象的X锁之前,必须先获得表的IX
  • 任意IX/IS锁之间兼容,它们只表示想要对表加锁,而不是真正的加锁

封锁协议

  • 三级封锁协议

    • 一级封锁协议

      事务T想要修改数据A时必须加X锁,直到T结束才释放锁,可以解决丢失修改问题,因为不能同时有两个事务对同一个数据进行修改,事务的修改不会被覆盖

    • 二级封锁协议

      在一级的基础上,要求读取数据A时必须加S锁,读取完释放S锁,可以解决读脏数据问题,因为根据一级封锁协议,数据加了X锁,其他事务就不能加S锁,也就不能读入数据

    • 三级封锁协议

      在二级的基础上,要求读取数据A时必须加S锁,直到事务结束才释放S锁,可以解决不可重复读问题,因为读取A时,其他事务不能对数据AX锁,避免在读期间数据发生变化

  • 两段锁协议

    加锁和解锁分为两个阶段进行

MySQL实现事务ACID特性的方式总结:

  • 原子性:使用undo log来实现,如果事务执行过程中出错或者用户执行了rollback,系统通过undo log日志返回事务开始的状态
  • 持久性:使用redo log来实现,只要redo log日志持久化了,当系统崩溃,可以通过redo log把数据恢复
  • 隔离性:通过锁以及MVCC来实现
  • 一致性:通过回滚、恢复以及并发情况下的隔离性从而实现一致性

隔离级别

  • 未提交读(READ UNCOMMITED

    事务中的修改,即使没有提交,对其它事务也是可见的

  • 提交读(READ COMMITED

    一个事务所做修改在提交之前对其它事务不可见

  • 可重复读(REPEATABLE READ

    保证在同一个事务中多次读取同一数据的结果是一样的

  • 可串行化(SERIALIZABLE

    强制事务串行执行,不会出现并发一致性问题,需要加锁保证同一时间只有一个事务执行

1583138319664

多版本并发控制

Next-Key Locks

关系数据库设计理论

  • 函数依赖

  • 异常

    • 冗余数据
    • 修改异常
    • 删除异常
    • 插入异常
  • 范式

    范式理论是为了解决上述四种异常

    高级别范式依赖于低级别范式,1NF是最低级别的范式

    • 第一范式(1NF

      属性不可分

    • 第二范式(2NF

      每个非主属性完全函数依赖于键码

      可以通过分解来满足

      分解前

      1583139160746

      以上学生课程关系中,{Sno,Cname}为键码,有如下函数依赖:

      • Sno -> Sname,Sdept
      • Sdept -> Mname
      • Sno ,Cname -> Grade

      Grade完全函数依赖于键码,没有任何冗余数据

      Sname,Sdept,Mname部分依赖于键码,当一个学生选修多门课时,就会出现冗余

      分解后

      1583139435346

      函数依赖:

      • Sno -> Sname,Sdept
      • Sdept -> Mname

      1583139483382

      函数依赖:

      • Sno,Cname -> Grade
    • 第三范式(3NF

      非主属性不传递函数依赖于键码

      上面关系-1中存在如下传递函数依赖

      • Sno -> Sdept -> Mname

      可以进行分解:

      1583139714931

      1583139749745

ER图

Entity-Relationship,由实体、属性、联系组成

  • 实体的三种联系

    包含一对一、一对多、多对多

    • 若A到B是一对多关系,一个带箭头的线段指向B
    • 若A到B是一对一,两个带箭头的线段
    • 若A到B是多对多,两个不带箭头的线段

索引

索引使用的场景

  • 对于非常小的表,大部分情况下简单的全表扫描比建立索引更高效
  • 对于中大型的表,索引非常有效
  • 对于特大的表,建立和维护索引的代价会变大,需要用到一种技术直接区分出需要查询的一组数据而不是一条条记录的匹配,例如使用分区技术

索引的原则

  • 选择唯一性索引
  • 为经常需要排序、分组和联合操作的字段建立索引
  • 为经常作为查询条件的字段建立索引
  • 限制索引的数目
  • 尽量使用数据量少的索引
  • 尽量使用前缀来索引
  • 删除不再使用或者很少使用的索引
  • 最左前缀匹配原则
  • 尽量选择区分度高的列作为索引
  • 索引列不能参与计算,带函数的查询不参与索引
  • 尽量的扩展索引,不要新建索引

B Tree原理

  • B-Tree
  • B+Tree

MySQL事物实现原理

redo log

undo log

MVCC

参考文章:

cyc2018的数据库系统原理

https://frank-lam.github.io/fullstack-tutorial/#/MySQL

朱小厮的博客:分布式事务科普