事务

概念

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

img

ACID

原子性(Atomicity)

事务被视为不可分割的最小单元,要么提交成功,要么失败回滚。

1
2
回滚: 利用回滚日志实现,回滚日志记录事物所执行的修改操作,回滚时反向执行。
回滚日志: 保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读(TODO:两个说法有矛盾!)

一致性(Consistency)

数据库在事物执行前后都保持一致性状态。

知乎

1
一致性:应用系统从一个正确的状态到另一个正确的状态。而ACID就是说事务能够通过AID来保证这个C的过程.C是目的,AID都是手段。

隔离性(Isolation)

一个事务的修改在提交前,对其他事务不可见。

持久性(Durability)

事务提交,即使系统发生崩溃,也不会丢失执行结果。通过重做日志保证持久性。

1
重做日志: 防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。

img

1
MySQL 默认采用自动提交模式。也就是说,如果不显式使用START TRANSACTION语句来开始一个事务,那么每个查询都会被当做一个事务自动提交。

并发一致性问题

丢失修改

img

读脏数据

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

img

不可重复读

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

img

幻影读

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

当事务1根据某种检索条件读取了若干条记录,未提交事务;而事务2又插入了一条记录,该记录也符合事务1的检索条件;那么当事务1在根据相同查询条件检索数据时候,出现了不一致的现象。

img

解决

通过引入并发控制实现隔离性。通过复杂的封锁配置或者配置事务隔离级别解决。

封锁

封锁粒度

MySQL 中提供了两种封锁粒度:行级锁以及表级锁。

开销与并发程度的权衡。

封锁类型

读写锁

  • 排它锁(Exclusive),X锁,又称读写锁。
  • 共享锁(Shared),S锁,又称读锁

规则:

  1. X锁后拥有读写权,其他事务不能加任何锁。
  2. S锁后拥有读取权,其他事务能加S锁。

意向锁

意向锁是一种不与行级锁冲突表级锁,分为两种:

  1. 意向共享锁IS:事务有意向对表中的某些行加共享锁。事务要获取某些行的 S 锁,必须先获得表的 IS 锁。
  2. 意向排它锁IX:事务有意向对表中的某些行加排他锁。事务要获取某些行的 X 锁,必须先获得表的 IX 锁。

意向锁由引擎维护,无法由用户手动操作。

解决的问题

1
如果另一个任务试图在该表级别上应用共享或排它锁,则受到由第一个任务控制的表级别意向锁的阻塞。第二个任务在锁定该表前不必检查各个页或行锁,而只需检查表上的意向锁。

1560178757091

注意:这里的排他 / 共享锁指的都是表锁!!!意向锁不会与行级的共享 / 排他锁互斥!!!

封锁协议

三级封锁协议

  1. 一级封锁协议

    事务修改数据得加X锁,结束才释放。解决丢失修改问题

  2. 二级封锁协议

    在一级的基础上,读取A加S锁,读取完马上释放S锁。解决脏读问题,因为正在修改的事务会加X锁,此时无法加S锁,其他事务也就无法读。

  3. 三级封锁协议

    二级的基础上,直到事务结束了才释放S锁。解决不可重复读的问题

两段锁协议

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

事务遵循两段锁协议是保证可串行化调度的充分条件。

1
可串行化调度: 通过并发控制,使得并发执行的事务结果与某个串行执行的事务结果相同。

MySQL 的 InnoDB 存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。

隔离级别

未提交读(READ UNCOMMITTED)

事务的修改,即使没有提交,对其他事务可见。

提交读(READ COMMITTED)

事务的修改在提交前对其他事务不可见。

可重复读(REPEATABLE READ)

同一个事务中多次读取同样的数据结果相同。

可串行化(SERIALIZABLE)

强制事务串行执行。需要加锁实现。所有读取的行加锁。它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

1560179672381

多版本并发控制 MVCC

Multi-Version Concurrency Control,通过保存数据在某个时间点的快照来实现。每个事务看到的数据都是一致的,但根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。

InnoDB 实现隔离级别的具体方式,用于实现提交读可重复读

可串行化读需要对所有读取的行加锁,无法单纯用MVCC实现。

优缺点

  • 优点:是的大多数读操作不用加锁。性能好。
  • 缺点:需要额外的存储空间和检查工作。

版本号

  • 系统版本号:递增的数字,每开启一个数字递增。
  • 事务版本号:事务开始时的系统版本号。

隐藏的列

MVVC在每行记录后面保存两个隐藏的列,存储两个版本号:

  • 创建版本号:创建一个数据行的快照时的系统版本号。
  • 过期(删除)版本号:如果该快照的删除版本号大于当前事务版本号表示该快照有效,否则表示该快照已经被删除了。

Undo 日志

MVCC 使用到的快照存储在Undo日志里,该日志通过回滚指针把一个数据行(Record)的所有快照连接起来。

img

实现过程

以下基于可重复读。

开始一个事务时,该事务的系统版本号比之前所有数据行快照的创建版本号要大。

select

返回符合下列条件的记录:

  1. 行的系统版本号≤当前事务版本,以确保读取的行要么是事务开始前已经存在,要么是自身插入或者修改过的。
  2. 删除版本号要么未定义,要么大于当前事务版本号。

insert

插入的每一行保存当前系统版本号作为行版本号。

delete

删除的每一行保存当前的系统版本号为行过期版本号。

update

插入新记录 -> 修改原来的行的过期版本号为系统版本号

Next-Key Locks

Next-Key Locks 是 MySQL 的 InnoDB 存储引擎的一种锁实现。解决幻影读问题。在可重复读(REPEATABLE READ)隔离级别下,使用 MVCC + Next-Key Locks 可以解决幻读问题。

Record Locks

锁定一个记录上的索引,而不是记录本身。

没有索引,InnoDB会自动在主键上创建隐藏的聚簇索引。

Gap Locks

锁定索引之间的间隙,不包含索引本身。

1
SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;

其它事务就不能在 t.c 中插入 15。

Next-Key Locks

它是 Record Locks 和 Gap Locks 的结合,不仅锁定一个记录上的索引,也锁定索引之间的间隙。

关系数据库设计理论

函数依赖

A->B A 函数决定 B,B函数依赖于A。

超码、候选码、主码。

部分函数依赖:A->B若能找到A的真子集C,使得C->B

传递函数依赖

范式

1NF

域是原子的。

2NF

每一个非主属性完全依赖于候选码。

3NF

非主属性不传递函数依赖于候选码。

ER图

实体、属性、联系

实体三种联系

img

一对多,一对一,多对多,上图表示一个课程有一个课代表,一个人可以当多门课的课代表。

出现多次的关系

img

非二元联系集

img