暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

关于mysql事务看这些就够了

不高兴就喝水 2021-08-10
692

前言

我们能经常听到类似的词语,如脏写,脏读,幻读等。这些其实都是属于事务的并发问题。多个事务操作同一批数据时可能出现的各种数据问题,大致分类也就是那几类。
数据库了解决数据的事务并发问题,设计了事务隔离机制,锁机制,MCVV多版本并发控制隔离机制,等一整套机制来解决多事务并发的问题。我们先从事务开始说起。

什么是事务

在关系型数据库内,事务是由一个SQL或一组SQL语句组成的逻辑处理单元。

事务的属性

原子性(Atomicity):
对数据的操作都视为一个原子一样不可分割,要么全部都执行,要么全部都不执行。当在执行中间过程出现错误的时候,会回滚到事务开始前的状态。
一致性(Consistent):
事务的执行结果必须是从一个一致性状态向另一个一致性状态的变更。比如说,A和B两个人一共有5000元,那么不管A转钱给B,或者B转钱给A,A+B的金额永远是5000。
隔离性(Isolation):
允许多个并发事务同时对数据进行读写和修改的能力,并发执行的多个事务之间相互隔离,不受到其他事务的干扰。
持久性(Durable):
事务完成后,对数据的修改是永久性的。任何事务或故障都不应导致数据丢失。

常见的并发异常

脏读:
一个事务在对一条数据进行了修改,在这个事务还没提交时,另一个事务也读取了这条数据,如果没有控制,那么第二个事务读到的就是一条脏数据,如果第二个事务还需要对这条数据进行操作,那么这两个事务之间就存在依赖关系,这就是叫脏读。一句话说就是事务A读取到了事务B已经修改但未提交的数据。
不可重复读:
同一个事务在读取某个数据后,隔一段时间再次读取该条数据,同一组数据发生了修改或者删除的情况,叫做不可重复读。
幻读:
同一个事务,使用相同的查询条件读取以前检索的数据,查询到了其他事务插入的符合条件的数据,这种现象叫做幻读。一句话来说,就是事务A读取到了事务B提交的新增数据。

事务的隔离级别

上面的脏读,不可重复读,幻读等都是数据库读一致性的问题,可以使用数据库的隔离机制来进行避免。

隔离级别

脏读

不可重复读

幻读

读未提交

(Read uncommitted)

可能

可能

可能

读已提交

(read committed)

不可能

可能

可能

可重复读

(repeatable read)

不可能

不可能

可能

可串行化

(Serializable)

不可能

不可能

不可能

PS:mysql默认的事务隔离级别是可重复读,查看当前数据库的事务隔离级别可以使用show variables like 'tx_isolation';设置事务的隔离级别用 set tx_isolation = ‘REPEATABLE-READ’;

锁机制

锁是计算机协调多个进程或线程并发访问某一资源的一种机制。从性能上来分,锁分为乐观锁和悲观锁。
从数据库操作的类型分,锁分读锁和写锁(都是悲观锁)。
读锁(共享锁,S锁 Shared):针对同一份数据,多个读操作可以同时进行并且不会相互影响
写锁(排它锁,X锁 eXclusive):在当前写操作完成前,会阻断其他一切的锁
从操作数据库的粒度来说,分表锁和行锁
表锁:操作锁住整张表,加锁速度快,不会出现死锁;但锁定的粒度太大,很容易发生锁冲突。一般在整张表数据迁移时使用。
行锁:每次操作锁住当前操作的行数据。加锁比较慢,会出现死锁的情况。锁定的粒度小,发生锁冲突的概率低。

MVCC版本控制

mysql除了串行化隔离级别是通过互斥锁进行实现的,其他的隔离级别都是通过MVCC机制来保证隔离性的。通过MVCC机制,对同一行数据的读和写操作默认是不需要通过加互斥锁来保证隔离性的。
MVCC机制的实现是通过read-view机制与undo日志版本链对比,来使得不同的事务会根据数据版本链进行比对来读取同一条数据在版本链上的不同版本数据,进行数据的隔离。下面通过一张图来详细描述MVCC的实现机制。
首先我们介绍一下日志版本链,日志版本链是指一行数据被多个事务依次修改后,每次事务修改完,mysql都会保留修改前的数据的undo回滚日志,同时使用了两个隐藏字段trx_id(事务号)和roll_pointer(回滚指针)把这些回滚日志串联起来形成一个版本链。具体的如图所示:
图中每一行代表一个回滚日志,每个日志末尾都有一个roll_pointer指向上一条日志。

当事务开启时,执行任何查询sql都会生成当前事务的一致性视图read-view,生成的视图在事务结束之前都不会变化,这个视图由查询时所有未提交事务id数组和已创建的最大事务id组成。

在我们当前的日志版本链内,查询了一条数据,创建了一个对应的read-view,那么它应该是这样,[80,81,82],202 ,数组内最小的id为min_id,最大的事务id(max_id)是202。
具体的版本链比对规则:
  1. 如果trx_id<min_id , 表示这个版本是已提交的事务产生的,可见

  2. 如果trx_id >max_id,表示是由生成read-view之后的事务版本生成的,不可见

  3. 如果trx_id落在min_id和max_id之间,分为两种情况:

    1. 如果trx_id在read-view的数组内,不可见

    2. 如果trx_id不在read-view的数组内,可见

PS:事务的trx_id生成规则是依次递增的,所以后面生成的trx_id一定比前面的大。事务的真正启动,是在执行第一个修改操作数据库的语句开始的,查询语句不生成事务id。

最后,再提供一张图方便大家理解:


文章转载自不高兴就喝水,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论