MySQL-8事务与隔离级别IO

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL-8事务与隔离级别IO相关的知识,希望对你有一定的参考价值。

参考技术A

大家好,我是安小生,本节文章说的是事务的隔离级别IO

读取未提交 ,简称:RU。 没有隔离性,所以在隔离级别中的性能是最好ode。
脏读 :指的是读到了其他事物提交的数据。即使是未提交
读取已提交 ,简称:RC。 性能相对而言比读取未提交要低。
不可重复读 :一致性。 性能相对而言比读取已提交要低。
可重复读取 ,简称: RR(默认)。
幻读的问题 :mvc 但是对于新增来时候可能依然存在。
串行化 :原本是多进程:强行转化为单进程。 在性能方面是最低的。但是解决的问题是最多的。
总结:从上往下依次降低,隔离级别的强度依次增强。

下面我们有一个图来进行解释:

读取未提交:不可重复读,幻读问题。
解决问问题:没有使用事物的时候数据不一致的问题,就是做到知行的sql一起成功,一起回滚。
脏读:可以读取到其他事物所知行的sql的结果=》脏读
假设事物1要进行回滚操作,事务
读取未提交以及串行话不在我们权衡以及考虑的范围,。

深入了解mysql锁机制与事务的隔离级别

锁的定义

锁是计算机协调多个线程或进程共享并发访问某一资源的机制。

在mysql中,除了传统的计算资源(io,cpu的资源分配等),本身的数据也是用户共享的资源,数据库提供的锁机制对于保证数据的一致性,有效性的访问非常重要。


锁的分类:

按性能分:悲观锁,乐观锁

按操作方式分:读锁,写锁(都属于悲观锁)

按操作粒度分:表锁,行锁


表锁:

表锁偏向MyISAM存储引擎,开销小,加锁快,无思索,锁定粒度大,发生锁冲突的概率最高,并发度最低。

当对表增加读锁(又称共享锁)时,当前session和其他session都可以读该表,当前session中插入或者更新锁定的表都会报错,其他session插入或更新则会等待。当对表增加写锁(又称排他锁,独占锁)时,当前session对该表的增删改查都没有问题,其他session对该表的所有操作被阻塞。

MyIsam在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。


行锁:

行锁偏向InnoDB存储引擎,开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度也最高。InnoDB与MYISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁。

行级锁特点:session1更新某条数据未提交,此时session2再去更新某条数据会被阻塞。


事务:

事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。

原子性(Atomicity) :事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。

一致性(Consistent) :在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。

隔离性(Isolation) :数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。

持久性(Durable) :事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。


并发事务产生的问题:

脏读:事务a读取到事务b未提交的事务

不可重复读:一次事务两次读取到的数据不一致,及事务a在事务中读取到了事务b更新过的数据,不符合事务的隔离性

幻读:一次事务中读取到之前不存在的数据,及事务a在事务中读取到了事务b插入的数据,不符合事务的隔离性


脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。

数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。

同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读"和“幻读”并不敏感,可能更关心数据并发访问的能力。

常看当前数据库的事务隔离级别: show variables like 'tx_isolation';

设置事务隔离级别:set tx_isolation='REPEATABLE-READ';


解决脏读:设置隔离级别为读已提交

解决不可重复读:设置隔离级别为可重复读

解决幻读:设置隔离级别为串形化(及串形处理)


可重复读的隔离级别下利用了mvcc机制:

select操作会生成一个快照版本的数据,再次select操作会查看快照版本,增删会使用最新数据update account set balance = balance - 50;用的是最新版本,实战中可用于如抢购防止超卖的问题 


由于幻读是采用的串形化隔离级别,不具有并发性,性能极差,因此基本不会采用。如果要防止出现幻读在java代码中可以采用redis锁等操作,mysql中可以使用间隙锁。如:在session1下面执行update account set name = 'aaa' where id > 10 and id <=20;,则其他session没法插入这个范围内的数据


死锁:

死锁主要产生的原因是两个事务分别等待对方所占有锁资源的释放所引起的,如在可重复读隔离级别中执行以下sql及可产生死锁:

session1执行:select * from account where id=1 for update;

session2执行:select * from account where id=2 for update;

session1执行:select * from account where id=2 for update;

session2执行:select * from account where id=1 for update;

查看近期死锁日志信息:show engine innodb status\G; 

大多数情况mysql可以自动检测死锁并回滚产生死锁的那个事务,但是有些情况mysql没法自动检测死锁


悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。mysql中通过在sql语句后开启排他锁来实现悲观锁

乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。mysql中通过对表增加version字段来实现,

每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。



mysql中关于锁的优化建议:

  • 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁

  • 合理设计索引,尽量缩小锁的范围

  • 尽可能减少检索条件,避免间隙锁

  • 尽量控制事务大小,减少锁定资源量和时间长度

  • 尽可能低级别事务隔离


以上是关于MySQL-8事务与隔离级别IO的主要内容,如果未能解决你的问题,请参考以下文章

事务隔离级别

MySQL:事务四大特性与隔离级别

如何更改mysql事务隔离级别

mysql 的事务隔离级别 及各个隔离级别应用场景,详细

SQL SERVER的锁机制——概述(锁与事务隔离级别)

数据库的隔离性与隔离级别以及隔离级别产生的影响