【数据库】事务并发的三大问题以及事务的隔离级别

事务并发引起的三大问题

1.脏读

  • 脏读:一个事务会读取到另一个事务未提交的数据。(读取到未提交数据)

    例子:事务A修改了数据但还未提交,事务B读取到了事务A修改的数据。然后事务A因为某些错误回滚了,这个时候事务B读取到的数据就是脏的,这就是脏读。

脏读的发生流程:
在这里插入图片描述
使用数据库隔离级别为:读已提交(可避免脏读)
在这里插入图片描述

2.不可重复读

  • 不可重复读:在同一事务内,事务两次读取到的数据是不一样的。(原数据中同一条数据被修改或被删除)

    例子:事务A读取了一条数据之后,事务B修改了这条数据并提交了事务,然后事务A再次读取这条数据,就会发现两次结果不一致。这就是不可重复读。

不可重复读,示例1
在这里插入图片描述
不可重复读,示例2

左边的事务先查询一次数据,为salary值5000
右边的事务使用update更新salary为4500,但未提交commit事务。左边事务第二次查询,salary还是为5000(事务隔离级别为读已提交,已避免脏读)
右边的事务commit提交事务,左边事务第三次查询,salary为4500(左边事务还没有提交,多次查询中读取到右边的事务提交的数据)
在这里插入图片描述
使用数据库隔离级别为:可重复读(可避免不可重复读)
在这里插入图片描述
在可重复读的事务隔离级别中,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
例子如下:第一个事务是写事务,那么第二个事务也禁止写
在这里插入图片描述
但是事务在隔离级别为可重复读,并不能避免幻读(使用mysql InnoDB存储引擎,可以在可重复读隔离级别下阻止读数据下的幻读,原因见下)

3.幻读

  • 幻读:事务中的同一个查询在不同的时间产生不同的行集,这个就是幻读问题。(数据总条数新增)

    例子:事务A使用一定的条件查询,然后事务B增加了符合条件的记录,当事务A再次查询的时候,发现两次查询的结果集不一样,好像产生了幻觉。这就是幻读。

不可重复读和幻读,都是读取到其他事务已经提交的数据。而脏读是读取到其他事务还未提交的数据

在这里插入图片描述
解决幻读:(1)可以使用数据库隔离级别是串行化
(2)使用mysql innoDB存储引擎,在数据库隔离级别是可重复读也能解决了读数据情况下的幻读问题。而对于修改的操作依旧存在幻读问题,下面将详细讨论

场景一:读数据情况下的幻读,使用mysql innoDB存储引擎,在数据库隔离级别是可重复读,能解决
在这里插入图片描述
场景二:事务1更新数据,而事务2插入数据,这种情况使用mysql innoDB存储引擎,在数据库隔离级别是可重复读,不能解决

在这里插入图片描述
根据上面的结果我们期望的结果是这样的:

id  name
1   财务部
2   研发部

但是实际上我们的结果是:
在这里插入图片描述
场景一和场景二形成的根本原因是:快照读和当前读

对于场景一:
select 快照读
当执行select操作是innodb默认会执行快照读,会记录下这次select后的结果,之后select 的时候就会返回这次快照的数据,即使其他事务提交了不会影响当前select的数据,这就实现了可重复读了。

快照的生成是在第一次执行select的时候,也就是说假设当A开启了事务,然后没有执行任何操作,这时候B insert了一条数据然后commit,这时候A执行 select,那么返回的数据中就会有B添加的那条数据。之后无论再有其他事务commit都没有关系,因为快照已经生成了,后面的select都是根据快照来的。

所以场景一中,事务1先select之后,已经生成快照。第二次select也是返回快照(历史数据),不会返回事务2新加的数据,不会出现幻读

对于场景二:

当前读
对于会对数据修改的操作(update、insert、delete)都是采用当前读的模式。
如以下sql都是执行的是当前读

insert; 
update;      //如 update .... set .. where ... 
delete;      //如  delete from. . where ..

在执行这几个操作时会读取最新的记录,即使是别的事务提交的数据也可以查询到。假设要update一条记录,但是在另一个事务中已经delete掉这条数据并且commit了,如果update就会产生冲突,所以在update的时候需要知道最新的数据。

也正是因为这样,select快照读和update当前读混合使用,就出现了场景二的幻读现象。

select的当前读需要手动的加锁:

select * from table where ? lock in share mode;
select * from table where ? for update;

那么只用当前读可以避免幻读。当前读会阻塞新数据的插入,主要是间隙锁的加锁机制。
在这里插入图片描述
可以看到Session 2 被阻塞了。需要等到Session1 提交事务后才能完成。当我们在事务中每次读取都使用当前读,也就是人工把InnoDB变成了串行化。一定程度上降低了并发性,但是也同样避免了幻读的情况。

只用快照读也可以避免幻读,但是快照读不加锁,所以不阻塞插入新数据,如上述场景一,同样能避免幻读(参考https://zhuanlan.zhihu.com/p/103580034?utm_source=wechat_session)

快照读和当前读混合使用,如上述的场景二,就会出现幻读的情况

参考链接
当前读与快照读的知识https://blog.csdn.net/hello_world_cy/article/details/82109806

数据库的四个隔离级别

读未提交(Read Uncommitted):一个事务还没提交时,它做的变更就能被别的事务看到。解决更新丢失问题。如果一个事务已经开始写操作,那么其他事务则不允许同时进行写操作,但允许其他事务读此行数据。

读已提交(Read Committed):一个事务提交之后,它做的变更才会被其他事务看到。解决了脏读。读取数据的事务允许其他事务继续访问(访问指读和写)该行数据,但是未提交的写事务将会禁止其他事务访问该行。

可重复读取(Repeatable Read):可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。解决了不可重复读取和脏读取,但是有时可能出现幻读数据。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。Mysql默认使用该隔离级别。

串行化(Serializable):对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。解决了幻读的提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。

关于串行化的示例:
在这里插入图片描述

若隔离级别是“串行化”,则在事务 B 执行“将 1 改成 2”的时候,会被锁住(被锁住的原因见下)。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2

现象解释:
事务A一开始查询值1的时候就获得了读锁,根据两阶段加锁,事务A获得的锁要在commit的时候才释放,所以事务B在修改1为2的时候申请写锁会阻塞直到事务A提交,事务A提交之前获取的值都是1,所以V1 V2都是1,事务A提交后事务B获取到写锁完成更新操作,所以V3是2

实现4种隔离级别的实现方式

在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。
在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。
在“读已提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。
这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;
而“串行化”隔离级别下直接用加锁的方式来避免并行访问。

参考链接:
推荐以下三篇文章
https://juejin.im/post/6844903799534911496
https://www.cnblogs.com/CoderAyu/p/11525408.html
https://github.com/Snailclimb/JavaGuide/blob/master/docs/database/%E4%BA%8B%E5%8A%A1%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB(%E5%9B%BE%E6%96%87%E8%AF%A6%E8%A7%A3).md#%E8%84%8F%E8%AF%BB%E8%AF%BB%E6%9C%AA%E6%8F%90%E4%BA%A4

https://juejin.im/entry/6844903665367547918

查看数据库隔离级别的方法

如果要把隔离级别设置成“读已提交”

transaction-isolation 的值设置成 READ-COMMITTED。你可以用 show variables 来查看当前的值

mysql> show variables like 'transaction_isolation';

+-----------------------+----------------+

| Variable_name | Value |

+-----------------------+----------------+

| transaction_isolation | READ-COMMITTED |

+-----------------------+----------------+

事务之间相互隔离的实现方式

可重复读为例
MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作除了记录变更记录,还会记录一条变更相反的回滚操作记录,前者记录在redo log,后者记录在undo log)。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。
在这里插入图片描述
当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view(在可重复读隔离级别下,每个事务在启动时都会新建一个事务视图,在接下来的事务中,都会使用这个视图)。如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。

回滚日志删除的时机:当系统里没有比这个回滚日志更早的 read-view 的时候。如:read-viewA已经没了,那么对应的2改为1的回滚段就可以删掉了

事务的启动方式

主要是两种:
(1)显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。

PS:
在自动提交模式关闭(关闭隐式提交)的情况下,开启一个事务上下文。首先数据库会隐式提交之前隐式事务的还未被提交的操作

(2)隐式启动事务。set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接。
关闭autocommit=0,示例SQL如下:

select name from student where id=1;
commit;

PS:如果autocommit=1,数据库会自动提交事务。但是在autocommit=1的情况下,显式启动事务( begin 或 start transaction),事务也不会自动提交,除非手动commit。
如果执行 commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行 begin 语句的开销
在这里插入图片描述
总结:

1、不管autocommit 是1还是0 ,START TRANSACTION 后,只有当commit数据才会生效,ROLLBACK后就会回滚。
2、当autocommit 为 0 时,不管有没有START TRANSACTION。只有当commit数据才会生效,ROLLBACK后就会回滚。
3、如果autocommit 为1 ,并且没有START TRANSACTION 。调用ROLLBACK是没有用的。即便设置了SAVEPOINT。

查询长事务

information_schema 库的 innodb_trx 这个表中查询长事务,比如下面这个语句,用于查找持续时间超过 60s 的事务。

select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60

参考链接

https://www.cnblogs.com/hider/p/9103051.html
https://blog.csdn.net/aitangyong/article/details/50481161
https://www.cnblogs.com/kerrycode/p/8649101.html
https://zhuanlan.zhihu.com/p/57302592

关于事务是否隔离

在 MySQL 里,有两个“视图”的概念:

  • 一个是 view。它是一个用查询语句定义的虚拟表(view的数据不占物理存储空间,但view本身会有物理结构),在调用的时候执行查询语句并生成结果。创建视图的语法是 create view … ,而它的查询方法与表一样。
  • 另一个是 InnoDB 在实现 MVCC 时用到的一致性读视图(没有物理结构),即 consistent read view,用于支持 RC(Read Committed,读提交)和 RR(Repeatable Read,可重复读)隔离级别的实现。
    在这里插入图片描述
  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bug 挖掘机

支持洋子

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值