一、事务

ACID

1、原子性(Atomicity)
事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。

2、一致性(Consistency)
数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对同一个数据的读取结果都是相同的。

3、隔离性(Isolation)
一个事务所做的修改在最终提交以前,对其它事务是不可见的。

4、持久性(Durability)
一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。

关于事务首先要明确一点,要保证事务的执行结果正确,就要保证一致性。
在无并发条件下,隔离性满足,这时只要满足原子性,就能满足一致性。
在并发条件下,要同时满足隔离性和原子性,才可以满足一致性。

并发一致性问题

在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。
关于并发一致性问题,常有的并发异常有五种:第一类丢失更新、第二类丢失更新、脏读、不可重复读、幻读

1、第一类丢失更新
某一个事务的回滚,导致另外一个事务已更新的数据丢失了
图片说明
如图所示,事务1进行了回滚,事务2中修改过的数据9被事务1回滚给覆盖掉了

2、第二类丢失更新
某一个事务的提交,导致另外一个事务已更新的数据丢失了
图片说明
如图所示,事务1进行了提交,事务2中修改过的数据9被事务1提交给覆盖掉了

3、脏读
某一个事务,读取了另外一个事务未提交的数据
图片说明

4、不可重复读
某一个事务,对同一个数据前后读取的结果不一致
图片说明

5、幻读
某一个事务,对同一个表前后查询到的行数不一致
图片说明

二、隔离级别

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

该隔离级别需要加锁实现,因为要使用加锁机制保证同一时间只有一个事务执行,也就是保证事务串行执行。

图片说明

并发控制还可以通过加锁来实现,相对于隔离级别略显麻烦

三、加锁

锁粒度

MySQL中提供了两种锁粒度:行锁和表锁
表级锁:开销小、加锁快、发生锁冲突的概率高、并发度低,不会出现死锁
行级锁:开销大、加锁慢、发生锁冲突的概率低、并发度高,会出现死锁
加锁需要消耗资源,锁粒度越小,获取锁、释放锁等动作就越频繁,给系统带来的开销也就越大。

锁类型

共享锁 s :行级,读取一行,又称之为读锁
排他锁 x :行级,更新一行,又称之写锁
意向共享锁 is :表级,准备加共享锁
意向排他锁 ix :表级,准备加排他锁
图片说明
加锁原则:

  • 一个事务对数据对象 A 加了 X 锁(写锁),就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁。

  • 一个事务对数据对象 A 加了 S 锁(读锁),可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁。

  • 一个事务在获得某个数据行对象的 S 锁(读锁)之前,必须先获得表的 IS 锁(意向读锁)或者更强的锁;

  • 一个事务在获得某个数据行对象的 X 锁(写锁)之前,必须先获得表的 IX 锁(意向写锁)。

通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。

在运用读写锁对数据对象进行加锁时,需要约定一些规则,这些规则就叫做封锁协议

封锁协议

封锁协议约定了什么

  • 申请锁的时间
  • 持锁时间
  • 释放锁的时间

三级封锁协议

1级封锁协议
事务 T 要修改数据 A 时必须加 X 锁,直到 T 结束才释放锁。

可以解决第一二类丢失更新问题,因为不能同时有两个事务对同一个数据进行修改,那么事务的修改就不会被覆盖。
图片说明
因为并没有规定事务T在修改数据时候要加读锁,所以“读”引发的并发不一致--脏读、不可重复读,是解决不了的

2级封锁协议
在一级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。

可以解决读脏数据问题,因为如果一个事务在对数据 A 进行修改,根据 1 级封锁协议,会加 X 锁,那么就不能再加 S 锁了,也就是不会读入数据。
图片说明

3级封锁协议
在一级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束才释放。
3级封锁协议可以防止丢失更新、脏读、不可重复读
图片说明

两段锁协议

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

可串行化调度是指,通过并发控制,使得并发执行的事务结果与某个串行执行的事务结果相同。串行执行的事务互不干扰,不会出现并发一致性问题。

事务遵循两段锁协议是保证可串行化调度的充分条件。例如以下操作满足两段锁协议,它是可串行化调度。

lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B)

但不是必要条件,例如以下操作不满足两段锁协议,但它还是可串行化调度。

lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C)