事务
文章目录
- 事务
- 什么是事务?
- 事务的四大特性:
- 事务的实现原理:
- 为什么要有事务的隔离级别?
- 事务的状态:
- 显示事务与隐式事务:
- 数据并发问题与四种隔离级别:
- 1.脏写 (Dirty Write)
- 2.脏读(Dirty read)
- 也就是只要是读取到了另一个事物未提交的数据, 即使是插入的数据, 也是脏读, 那么有的同学会问: 读未提交的状态下不是也有==不可重复读==和==幻读==? 不可重复读和幻读如何出现?
- 其实很简单 : 读未提交的状态下我们能读取到未提交的数据, 那么对于已经提交的数据肯定也是能读取到的, 所以对于提交了的修改或者删除操作的数据被读取到就是不可重复读问题, 对于提交了的增加操作的数据被读取到就是幻读问题
- 3.不可重复读(Non-Repeatable Read)
- 4.幻读(Phantom)
- 对于删除的情况, 也就是之前已经读取到的记录, 之后又读取不到的情况, 相当于删除的这些记录发生了一个不可重复读的现象, 幻读强调的是读取到了之前没有读取到的数据
- SQL规范关于隔离级别:
- 幻读的着重解释:
文章中有很多事务写成事物, 但是就不改了, 因为输入法导致经常是事务和事物互换位置
什么是事务?
事务是数据为了保证数据操作的原子性, 隔离性, 持久性, 一致性, 数据库提供的一套机制, 在同一个事务中, 如果有多条SQL执行, 事务要确保执行的可靠性
事务的四大特性:
一般来说, 事务要满足A(原子)C(一致)I(隔离)D(持久)特性:
原子性:
一个事务中的所有操作, 要么全部完成, 要么全部不完成, 不会结束在中间某个环节. 事务在执行过程中发生错误, 会被回滚到事务开始前的状态, 就像这个事务从来没有执行过一样
- 一个事务中的SQL要么都执行要么都不执行
持久性:
事物处理结束后, 对数据的修改是永久的, 即便系统故障也不会丢失.
- 一旦事物提交数据就会被保存在硬盘上
隔离性:
数据库允许多个并发事物同时对其数据进行读写和修改的能力, 隔离性可以防止多个事务并发执行时由于交叉执行导致数据的不一致. 事物隔离分为不同级别: 读未提交(read uncommitted), 读提交(read committed), 可重复读(repeatable read)和串行化(Serializable)
- 多个事务同时对数据操作的时候需要不同的隔离机制, 来进行控制, 保证数据一致性
一致性:
在事务开始之前和事务结束之后, 数据库的完整性没有被破坏, 这表示写入的数据必须完全符合所有的预设规则. 前面提到的原子性, 持久性和隔离性, 都是为了保证数据库状态的一致性
事务的实现原理:
事务的原子性实现原理:
事务的原子性其实就是事务的提交和回滚, 事务的回滚是通过undo日志实现的
事务的持久性实现原理:
事务的持久性就是即使是出现系统宕机的时候也要确保数据持久化到硬盘上, 是通过redo日志实现的
事务的隔离性实现原理:
读未提交原理:
读已提交原理:
事务中每次执行查询语句的时候都会生成一个readView, 读取的就会是最新已经提交的数据
- 通过MVCC实现的读已提交
可重复读原理:
事务中只有第一次执行查询语句的时候会生成一个readView, 以后读取的时候都会从这个readView中读取
- 通过MVCC实现的可重复读
串行化:
通过锁机制实现
为什么要有事务的隔离级别?
MySQL是一个服务器/客户端架构的软件, 对于同一个服务器来说, 可以有若干个客户端与之连接, 每个客户端与服务器连接上之后, 就可以称之为一个会话. 我们可以同时在不同的会话里输入各种语句, 这些语句可以作为事物的一部分进行处理. 不同的会话可以同时发送请求, 也就是服务器可能同时在处理多个事务, 这样子就会导致不同的事务可能同时访问到相同的记录. 我们前边说过事务有一个特性称之为隔离性, 理论上在某个事务对某个数据进行访问时其他事务应该进行排队, 当该事务提交之后, 其他事务才可以继续访问这个数据, 但是这样子的话对性能影响太大, 所以设计数据库的人提出了各种隔离级别, 来最大限度的提升系统并发处事务的能力
- 注意: 只有InnoDB支持事务, 所以这里说的事务隔离级别是指InnoDB下的事务隔离级别
事务的状态:
我们现在知道事务是一个抽象的概念, 它其实对应着一个或者多个数据库操作, MySQL根据这些操作所执行的不同阶段把事务大致划分为以下几个状态:
- 活动的(active) :
- 事务对应的数据库操作正在执行过程中时, 我们就该说事务处在活动的状态
- 部分提交的(partially committed)
- 当事务中最后一个操作执行完成, 但是由于操作都在内存中执行, 所造成的的影响并没有刷新到磁盘中时, 我们就说该事务在部分提交的状态
- 失败的(failed):
- 当事务处在活动的或者部分提交状态时, 可能遇到了某些错误(数据库自身的错误, 操作系统错误或者直接断电等)而无法继续执行, 或者人为的停止当前事务的执行, 我们就说该事务处在失败的状态
- 中止的(aborted)
- 如果事务执行了一部分而变为失败的状态, 那么就需要把已经修改的事务中的操作还原到事物执行前的状态, 换句话说, 就是要撤销失败事务对当前数据库造成的影响. 我们把这个撤销的过程称之为回滚. 当回滚操作执行完毕时, 也就是当前数据库恢复到了执行事务之前的状态, 我们就说该事务处在了中止的状态
- 提交的(committed)
- 当一个处在部分提交的状态的事务将修改过的数据偶读同步到磁盘上之后, 我们就可以说该事务处在了提交的状态
显示事务与隐式事务:
显示事务:
使用关键字 start transaction 或者begin
- start transaction后面可以加 : read only(只读事务) / read write(读写事务) / with consistent shapshot(一致性读)
- 注意 begin后面是不能加上面选项的
- 一个事物上可以加只读和一致性读, 也可以加读写和一致性读, 但是不能加只读和读写
隐式事物:
对于DML操作和DDL语句而言都是会隐式的开启事物
- DML操作的自动提交事物是可以通过set autocommit来控制, 但是DDL语句是不行的, DDL一定会隐式提交事物
对于DML操作如果是在一个显式事物当中, 也是不会自动提交事物的
- 即使是在显式事物当中, DDL也是会自动提交事物, 也就是隐式提交事物
隐式提交事物的情况:
- 数据定义语言: DDL(create, alter, drop等)
- 隐式使用或者修改mysql数据库中的表(这里的’mysql’表示一个真正的表, 是一个系统表)
- 事物控制或者关于锁定的语句 :
- 事物控制: 当一个事物还没有提交的时候显示开启一个新的事物, 这个时候会提交前面的事物
- 使用lock tables , unlock tables等关于锁定的语句时也会隐式提交前面语句所属的事物
- 关于MySQL复制的一些语句(后面主从复制用) : start slave, stop slave, reset slave, change master to等
- 其他一些语句: check table(检查表), flush(刷新)等也是会提交前面的事物
早期学习事物的时候认为是DML和DDL的时候会默认开启一个事物, 但是其实了解了事物实现原理之后就知道, 其实事务是通过undo日志实现的, 其实就是记录了对应相反的逻辑操作, 所以其实就是DML等操作的时候会记录相反的操作到undo 日志中而已
数据并发问题与四种隔离级别:
1.脏写 (Dirty Write)
对于两个事务Session A, Session B, 如果事务Session A修改了另一个未提交事物Session B修改过的数据, 那么就意味着发生了脏写
- 脏写这个问题是非常严重的, 意味我们当前SessionA如果修改了SessionB中修改了, 但是未提交的数据, 这个时候如果SessionB回滚, 那么SessionA已经提交的数据就会被回滚, 我的天, 提交了的数据被回滚, 你就知道问题有多严重
- 但是脏写其实是不会发生的, 因为MySQL中其实是有锁机制的, 只有InnoDB支持事物, 而InnoDB中由行级锁, 只要一个事物中执行修改操作就会加行X锁, 所以另一个事物这个时候就是修改不了的
脏写是一个事物修改了另一个事物修改之后但是还未提交的数据
2.脏读(Dirty read)
对于两个事物Session A, Session B, Session A读取了已经被Session B更新但是还没有被提交的字段, 之后若Session B回滚, Session A读取的内容就是临时且无效的
- 也就时SessionA不知道自己读取的数据是已经被回滚了的错误数据, 这个时候自己还在该数据基础上做处理
脏读指的是一个事物读取到了另一个事务未提交的数据(包括增删改)
-
也就是只要是读取到了另一个事物未提交的数据, 即使是插入的数据, 也是脏读, 那么有的同学会问: 读未提交的状态下不是也有不可重复读和幻读? 不可重复读和幻读如何出现?
其实很简单 : 读未提交的状态下我们能读取到未提交的数据, 那么对于已经提交的数据肯定也是能读取到的, 所以对于提交了的修改或者删除操作的数据被读取到就是不可重复读问题, 对于提交了的增加操作的数据被读取到就是幻读问题
3.不可重复读(Non-Repeatable Read)
对于两个事物Session A, Session B, SessionA读取了一个字段, 然后SessionB更新了该字段. 之后SessionA再次读取同一个字段, 值就不同了, 那么就意味着发生了不可重复读
- 注意 : 不可重复读强调的是修改操作重复读取时数据的不同
- 对应删除操作, 删除之后不能重复读的问题其实也是可以划分到不可重复读中的(因为按照定义划分到幻读中是完全行不通的)
不可重复读指的是一个事物读取到另一个事物已经提交的修改或者删除的操作
4.幻读(Phantom)
对于两个事物Session A, SessionB, Session A从一个表中读取了一个字段, 然后Session B在该表中插入了一些新的行, 之后, 如果Session A再次读取同一个表, 就会多出几行, 那么就意味着发生了幻读
- 幻读是比较难以解释的, 因为我们知道对于可重复读隔离级别来将, 其实我们查询的时候就不会出现查询出不同数据的情况了, 那么难道是可重复读就解决了幻读问题? —> 其实不是的, 可重复读的时候如果我们使用select操作, 那么是快照读, 这个时候确实是不会有幻读问题, 但是如果我们使用的是insert操作, 这个时候就是使用的当前读, 这个时候就会有幻读问题的出现了
- 我们把新插入的记录称之为幻影记录
对于删除的情况, 也就是之前已经读取到的记录, 之后又读取不到的情况, 相当于删除的这些记录发生了一个不可重复读的现象, 幻读强调的是读取到了之前没有读取到的数据
==幻读(数据库层面)==指的是一个事物读取到另一个事物已经提交的新增操作
SQL规范关于隔离级别:
幻读的着重解释:
幻读, 并不仅限于两次读取获取的结果集不同, 幻读侧重的方面是某一次select操作得到的结果锁表征的数据状态无法支撑后序的业务操作. 更为具体一些就是 : select某记录是否存在, 不存在, 准备插入此记录, 但是执行insert时发现此记录又是存在的, 无法插入, 此时就发生了幻读
- 要灵活理解读取的意思, 第一次select是读取, 第二次的insert其实也是属于一个隐式的读取, 插入数据其实也是要先读取一下有没有主键冲突才能决定是否执行插入
其实RR也是可以避免幻读的, 通过对select操作手动加行X锁(独占锁) (select … for update也正是serializable隔离级别下会隐式为你做的事情). 同时, 即使是当前记录不再, 当前事物也是会获得一个行级锁, 因为InnoDB的行锁锁定的是索引, 故数据(记录实体)存在与否没有关系, 存在就加行X锁, 不存在就加间隙锁, 其他事物则无法插入此索引的记录(自己是可以插入的), 就会转圈圈, 故杜绝了幻读
所以说MySQL的幻读并非是读取两次返回结果集不同, 而是事物在插入实现检测不存在的记录时, 惊奇的发现这些数据已经存在了, 之前检测读取到的数据如同鬼影一般
select … for update是快照读, 可以解决上面的幻读(真正的幻读), 但是也会带来一定的幻读(虚假的幻读), 这个比较难以解释, 就是虽然加锁之后后续不会有insert时的幻读(真实的幻读), 但是如果先前执行了快照读(普通读), 然后后序有人提交事物, 那么使用快照读再次读取的时候读取的结果集是最新的, 如果之前有人提交了事物, 那么返回的结果集肯定是数据变多了, 也就发生了幻读(虚假的幻读)
- 上面说的真实的幻读其实才是真正的问题, 是会导致我们的业务无法正常走下去
- 上面说的虚假幻读其实是没有什么问题的, 虽然返回的结果集的记录多了, 但是并不会导致业务走不下去
前几天有个同学问我, 那么如果出现结果集变多了, 你说是虚假的幻读, 但是我怎么感觉这都是已经发生了不可重复读, 这里就是他搞错了定义, 不可重复读是针对的修改的操作, 而不是insert操作, 所以说, 概念有的时候也是要牢记的