MySQL 事务并发学习笔记

一、事务

概念

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

  • 事务最经典也经常被拿出来说例子就是转账了
  • 假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是:将小明的余额减少 1000 元,将小红的余额增加 1000元, 万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了
  • 事务就是保证这两个关键操作要么都成功,要么都要失败

https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207222237925.png

ACID

1. 原子性(Atomicity)

事务被视为不可分割的最小单元,事务的所有操作要么全部完成,要么完全不起作用

回滚可以用回滚日志(Undo Log)来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可

2. 一致性(Consistency)

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

  • 例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的

3. 隔离性(Isolation)

并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间是独立的

  • 事务执行时就好像它是数据库上唯一正在执行的事务 (意即不受其他事务影响)

4. 持久性(Durability)

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

系统发生崩溃可以用重做日志(Redo Log)进行恢复,从而实现持久性。与回滚日志记录数据的逻辑修改不同,重做日志记录的是数据页的物理修改


事务的 ACID 特性概念简单,但不是很好理解,主要是因为这几个特性不是一种平级关系:

  • 只有满足一致性,事务的执行结果才是正确的
  • 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性
  • 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性
  • 事务满足持久化是为了能应对系统崩溃的情况

https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207210437023.png

AUTO COMMIT

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

二、并发一致性问题

在并发环境下,事务的隔离性很难保证,例如 多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作), 因此会出现很多并发一致性问题

1. 丢失修改 — Lost Update

  • 丢失修改指一个事务的更新操作被另外一个事务的更新操作替换
  • 一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据
  • 例如:T1 和 T2 两个事务都对一个数据进行修改,T1 先修改并提交生效,T2 随后修改,T2 的修改覆盖了 T1 的修改

https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207221744244.png

2. 读脏数据 — Dirty read

  • 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据
  • 因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据 “脏数据” 所做的操作可能是不正确的
  • 例如:T1 修改一个数据但未提交,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据
  • 总结: 脏读发生在 修改数据 → 提交数据 之间

https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207221920368.png

3. 不可重复读 — Non repeatable read

  • 不可重复读指在一个事务内多次读取同一数据。在这一事务还未结束前,另一事务也访问了该同一数据集合并做了修改
  • 由于第二个事务的修改,第一次事务的两次读取的数据可能不一致, 这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读
  • 例如:T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。

https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207222102010.png

4. 幻读 — Phantom read

  • 幻读本质上也属于不可重复读的情况
  • 一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读

https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207222134306.png


不可重复读和幻读的区别

  • 不可重复读的重点是修改,幻读的重点在于新增或者删除
  • 例 1 -(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了):事务 1 中的 A 先生读取自己的工资为 1000 的操作还没完成,事务 2 中的 B 先生就修改了 A 的工资为 2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。
  • 例 2 -(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于 3000 的有 4 人,事务1读取了所有工资大于 3000 的人,共查到 4 条记录,这时事务 2 又插入了一条工资大于 3000 的记录,事务 1 再次读取时查到的记录就变为了 5条,这样就导致了幻读。

产生并发不一致性问题的主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。

三、封锁

封锁粒度

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

  • 应该尽量只锁定需要修改的那部分数据 (选择合适的粒度),而不是所有的资源
  • 锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高

但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销

  • 因此封锁粒度越小,系统开销就越大

在选择封锁粒度时,需要在锁开销和并发程度之间做一个权衡

封锁类型

1. 读写锁

  • 互斥锁(Exclusive),简写为 X 锁,又称写锁。
  • 共享锁(Shared),简写为 S 锁,又称读锁。

有以下两个规定:

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

在 InnoDB 中

  • 对于 UPDATE、DELETE 和 INSERT 语句,InnoDB 会自动给涉及的数据集加排他锁(X)
  • 对于普通 SELECT 语句,InnoDB 会自动给涉及数据集加共享锁(S)

锁的兼容关系如下:

https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207213523777.png

2. 意向锁

使用意向锁(Intention Locks)可以更容易地支持多粒度封锁

在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。

意向锁在原来的 X/S 锁之上引入了 IX / IS,IX / IS 都是表锁,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。有以下两个规定:

  • 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁
  • 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁

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

各种锁的兼容关系如下:

https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207214442687.png

解释如下:

  • 任意 IS / IX 锁之间都是兼容的,因为它们只表示想要对表加锁,而不是真正加锁
  • 这里兼容关系针对的是表级锁,而表级的 IX 锁和行级的 X 锁兼容,两个事务可以对两个数据行加 X 锁
    • E.g. 事务 T1 想要对数据行 R1 加 X 锁,事务 T2 想要对同一个表的数据行 R2 加 X 锁,两个事务都需要对该表加 IX 锁,但是 IX 锁是兼容的,并且 表级 IX 锁与行级的 X 锁也是兼容的,因此两个事务都能加锁成功,对同一个表中的两个数据行做修改。

封锁协议

1. 三级封锁协议

一级封锁协议

  • 事务 T 要修改数据 A 之前必须对其加 X 锁,直到事务 T 结束才释放锁, 事务结束包括正常结束(COMMIT)和非正常结束 (ROLLBACK)
  • 可以解决丢失修改问题, 但不能保证可重复读和不读“脏”数据
  • 因为我们要求如果一个事务想要去修改数据,读数据之前要加上 X 锁,所以如果两个事务都要去修改的话,前一个在读的时候加上了锁,另外一个事物在读取的时候也需要加锁,但是 X 锁只能上一个,于是就就只能等了,所以就解决了更新丢失问题

https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207220440451.png

二级封锁协议

  • 一级封锁协议再加上,事务 T 在读取数据 A 之前必须对其加 S 锁,读取完马上释放 S 锁
  • 可以解决丢失修改, 读脏数据问题, 但是不能保证可重复读
  • 如果一个事务 A 正在修改数据,加上了 X 锁,然后另外一个事务 B 打算来读取数据,B 如果不需要加锁就可以去读数据, 那么 B 就有可能够读取到 A 事务没有提交的数据,导致了脏读
  • 所以, 二级协议就是在一级协议的基础上规定,一个事务要读取数据之前,必须要加 S 锁,如果另外一个事务正在写,就会导致当前读事务被阻塞了,也就解决了脏读问题
  • 由于是读完马上释放 S 锁, 所以一个事务中有可能多次获取和释放 S 锁, 这就导致了可能在一个事务中多次获取的数据不一样,也就是不可重复读

https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207220831843.png

三级封锁协议

  • 在一级封锁协议的基础上增加事务 T 在读取数据 A 之前必须加 S 锁,直到事务结束了才能释放 S 锁
  • 可以解决丢失修改, 读脏数据, 不可重复读的问题
  • 三级协议同样是补充了二级协议,二级协议在一个事务中可能会多次获取和释放 S 锁,这就导致了可能在一个事务中多次获取的数据不一样,也就是不可重复读
  • 因为在事务结束前,数据 A 上一直有 S 锁,其他事务不能再在 A 上加 X 锁来修改 A,在读取期间数据的值不会发生改变

https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207221313819.png

2. 两段锁协议

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

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

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

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

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)

MySQL 隐式与显示锁定

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

InnoDB 也可以使用特定的语句进行显示锁定:

SELECT ... LOCK In SHARE MODE;
SELECT ... FOR UPDATE;

四、隔离级别

未提交(READ UNCOMMITTED)

  • 最低的隔离级别,允许读取尚未提交的数据变更 (一个事务还未提交, 它的变更就能被别的事务看到)
  • 可能会导致脏读、幻读或不可重复读

读已提交(READ COMMITTED)

  • 允许读取并发事务已经提交的数据,换句话说,一个事务所做的修改在提交之前对其它事务是不可见的
  • 可以阻止脏读,但是幻读或不可重复读仍有可能发生

可重复读(REPEATABLE READ)

  • 一个事务过程中读取到的数据, 总是跟这个事务启动的时候读到的数据是一致的
  • 可以阻止脏读和不可重复读,但幻读仍有可能发生

可串行化(SERIALIZABLE)

  • 最高的隔离级别,完全服从 ACID 的隔离级别
  • 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰
  • 也就是说,该级别可以防止脏读、不可重复读以及幻读

https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191207223400787.png

参考:
https://www.cyc2018.xyz/
https://tech.meituan.com/2014/08/20/innodb-lock.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值