数据库事务( 以MySQL8.0.11为例 )

本章示例表及数据如下:

  • 数据库:
    数据库事务( 以MySQL8.0.11为例 )_第1张图片
  • 默认数据
    数据库事务( 以MySQL8.0.11为例 )_第2张图片

理论基础

事务

事务(transaction):最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)
事务只和DML语句有关,或者说只有DML语句才有事务。业务逻辑不同,DML语句的个数不同。

在事物进行过程中,未结束之前,DML语句是不会更改底层数据,只是将历史操作记录一下,在内存中完成记录。只有在事物结束的时候,而且是成功的结束的时候,才会修改底层硬盘文件中的数据。

在MySQL中,默认情况下事务是自动提交的。

事务的基本要素(ACID)

  • 原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
  • 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
  • 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
  • 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

事务的并发问题

  • 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

  • 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。

  • 幻读:就好像发生了幻觉一样,比如:明明当前事务查询没有这条结果,理应可以在当前事务进行新增操作,但是却提示记录已存在不能插入相同主键记录,这就是幻读,或者修改明明是全表update,但是执行成功后再进行查询却发现还有一条记录没有更新成功,这也是幻读。

    其中:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。

事务相关术语:

  • 开启事务:Start Transaction,任何一条DML语句(insert、update、delete)执行,标志事务的开启
  • 结束事务:End Transaction
  • 提交事务:Commit Transaction
  • 回滚事务:Rollback Transaction

  • 悲观锁:假定所有不同事务的处理一定会出现干扰,数据库中最严格的并发控制策略,如果一个事务正在对数据处理,那么在整个事务过程中,其他事务都无法对这个数据进行更新操作,直到该事务释放了这个锁。
  • 乐观锁:假定所有不同事务的处理不一定会出现干扰,所以在大部分操作里不许加锁,但是既然是并发就有出现干扰的可能。在乐观锁中当提交更新请求之前,要先去检查读取这个数据之后该数据是否发生了变化,如果有变化,那么你此次的提交就要放弃,如果没有变化就可以提交。

示例1:演示事务提交

数据库事务( 以MySQL8.0.11为例 )_第3张图片

示例2:演示事务回滚

数据库事务( 以MySQL8.0.11为例 )_第4张图片

事务隔离级别

事务隔离级别 名称 脏读 不可重复读 幻读
read-uncommitted 读未提交,也叫脏读
read-committed 不可重复读,也叫读已提交
repeatable-read 可重复读,默认级别
serializable 串行化

说明:

  • 隔离级别为读已提交时,写数据只会锁住相应的行。
  • 读已提交解决了脏读,但是两次读取的结果可能会不一致。
  • 隔离级别可重复读解决了多次查询结果都是一样的问题,但是如果事务执行期间有其他事务插入新数据,此时会产生幻读。
  • 隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有 索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
  • 事务隔离级别为串行化时,读写数据都会锁住整张表
  • 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

查看MySQL默认的事务隔离级别

示例3:
数据库事务( 以MySQL8.0.11为例 )_第5张图片

注意:在MySQL8之前的命令为:select @@tx_isolation

读未提交(read uncommitted)

  • 两个事务,一个事务未提交的数据,另一个事务可以读取到,这里读取到的数据叫做“脏数据”。
  • 这种隔离级别最低,这种级别一般是在理论上存在,实际应用中数据库隔离级别一般都高于该级别。

示例4:
第一步:打开客户端A,设置当前事务模式为read uncommitted(未提交读),查询tb_balance表zhangsan的money的初始值:
数据库事务( 以MySQL8.0.11为例 )_第6张图片
第二步:在客户端A的事务提交之前,打开另一个客户端B,更新tb_balance表zhangsan的money的值:
数据库事务( 以MySQL8.0.11为例 )_第7张图片
第三步:此时,虽然客户端B的事务还没提交,但是在客户端A已经可以查询到B已经更新的数据:
数据库事务( 以MySQL8.0.11为例 )_第8张图片
第四步:客户端B的事务因为某种原因回滚,它的所有的操作都将会被撤销。
数据库事务( 以MySQL8.0.11为例 )_第9张图片
第五步:客户端A查询到的数据其实就是脏数据了:
数据库事务( 以MySQL8.0.11为例 )_第10张图片
在客户端A执行执行更新语句zhangsan的money没有变成300,居然是400,数据不一致!

读已提交(read committed)

  • Oracle默认隔离级别
  • 两个事务,数据只有一个事务提交了后,另一个事物才能读取到,即:对方事物提交之后的数据,当前事物才能读取到。
  • 读已提交隔离级别高于读未提交。这种级别可以避免“脏数据”,但是会导致“不可重复读取”,存在幻读问题

示例5:
第一步:打开客户端A设置当前的事务隔离级别为read committed (未提交读)查询表tb_balance的所有记录:
数据库事务( 以MySQL8.0.11为例 )_第11张图片
第二步: 将客户端B的事务的隔离级别调整为read committed级别,并开启事务,更新表tb_balance
数据库事务( 以MySQL8.0.11为例 )_第12张图片
第三步:此时,客户端B的事务还没有提交,客户端A是不能查询到B已经更新的数据,解决了脏数据的问题:
数据库事务( 以MySQL8.0.11为例 )_第13张图片
第四步:客户端B提交事务
在这里插入图片描述
第五步:客户端A执行查询,结果就回发现与上一次查询结果不同,就产生了不可重复读的问题
数据库事务( 以MySQL8.0.11为例 )_第14张图片

可重复读(repeatable read)

  • MySQL默认隔离级别
  • 两个事务,一个事务提交之后的数据,另一个事务读取不到
  • 这种隔离级别高于读已提交,可以避免“不可重复读取”,但是会导致“幻像读”

示例6:
第一步:打开客户端A将事务的隔离级别调整为repeatable read 级别

数据库事务( 以MySQL8.0.11为例 )_第15张图片
第二步: 打开客户端B将事务的隔离级别调整为repeatable read级别,并对表tb_balance进行更新,但并未提交事务
数据库事务( 以MySQL8.0.11为例 )_第16张图片
第三步:在客户端A查询表tb_balance的所有记录,没有出现脏读的情况。
数据库事务( 以MySQL8.0.11为例 )_第17张图片
第四步:客户端B修改数据后提交了事务。
数据库事务( 以MySQL8.0.11为例 )_第18张图片
第五步:在客户端A查询表tb_balance的所有记录,没有出现不可重复读的情况。
数据库事务( 以MySQL8.0.11为例 )_第19张图片
第六步:在客户端A执行更新语句发现数据的一致性得到保证。数据库事务( 以MySQL8.0.11为例 )_第20张图片
之所以数据的一致性得到保证,原因是:在可重复读的隔离级别下,MySQL采用的是MVCC机制,select 操作不会更新版本号,是快照读(历史版本);而insert、update和delete会更新版本号,是当前读(当前版本)。
第七步:打开客户端B,尝试更新zhangsan的数据,会失败,这说明可重复隔离读级别下MySQL8已经不出现幻读的情况了。
在这里插入图片描述

示例7:串行化(serializable)

Serializable完全串行化的读,每次读都需要获得表级共享锁,读写操作相互互斥,这样可以更好的解决数据一致性的问题,但是同样会大大的降低数据库的实际吞吐性能。所以该隔离级别因为并发性比较低、损耗太大,一般很少在开发中使用。

  • MySQL中事务隔离级别为serializable时会锁表,因此不可能出现脏读数据、不可重复读、幻读的情况。
  • 两个事务,当一个事务操作数据库时,另一个事务只能排队等待
  • 这种级别可以避免“幻像读”,每一次读取的都是数据库中真实存在数据,多个事务之间串行,而不并发。
  • 这种隔离级别很少使用,吞吐量太低,用户体验差

第一步:在客户端A中设置当前事务模式为Serializable,同时开启事务:
在这里插入图片描述
第二步:在客户端B中设置当前事务模式为serializable,插入一条记录,此时表tb_balance已经被锁定。
数据库事务( 以MySQL8.0.11为例 )_第21张图片
因为已经发生了锁表,所以此时在客户端中再次执行查询会报错:
在这里插入图片描述

你可能感兴趣的:(#,MySQL,数据库,事务,MySQL事务,MySQL,隔离级别)