学习mysql过程10-mvcc和锁

MVCC(MUTI VERSION CURRENCY CONTRL)

隔离级别

并发遇到了哪些问题

脏写:一个事务在写的过程中,另一个事务也在写,导致的数据异常
脏读:读到了其他事务还未提交的数据
不可重复读:一个事务在多次读取一个数据的过程中,可能会发生多次查询的结果不一样
幻读:开始根据条件查询出来的结果,因为新事务的提交第二次查询出来的结果不一样

隔离级别

read uncommitted: 未提交读
read committed:已提交的读
repeated read : 可重复读
serializable:可串行化

简单的说一下隔离级别解决的问题,首先这四种都能解决脏写,也就是说一个事务再修改某个数据时,另一个事务不能同时来写。其次read uncommitted会发生除了脏写之外的问题,read committed能避免事务A读到,其他未提交的事务数据,解决脏读的问题,repeated read通过产生一个readview的方式,能解决不可重复读的问题,最后的serializable可串行化,也就是对每个读写都加锁的处理,能解决所有问题,但是效率很低。

mysql可以设置的隔离级别的范围:
使⽤GLOBAL关键字(在全局范围影响)
使⽤SESSION关键字(在会话范围影响)
上述两个关键字都不⽤(只对执⾏语句后的下⼀个事务产⽣影响):

MVCC的原理

版本链

对于使用mysql的innodb的存储引擎下,每个聚簇索引对应的记录都有两个隐藏列:
trx_id:每次⼀个事务对某条聚簇索引记录进⾏改动时,都会把该事务的事务id赋值给trx_id隐藏列。
roll_pointer:每次对某条聚簇索引记录进⾏改动时,都会把旧的版本写⼊到undo⽇志中,然后这个隐藏列就相当于⼀个指针,可以通过它来找到该记录修改前的信息。

insert的undo log一但自身事务提交了,就可以直接清除了,insert行上留着的trx_id就是当前事务的id,roll_pointer指向的就是undo log。后续更新了这条记录,roll_point就指向修改之前的这条记录,这样每条数据多次更新就构成了一个版本链。
在这里插入图片描述

Read View

之前提到的几种事务的隔离级别,我们提到过read committed和repeated read两种隔离级别在mysql中,其中repeated read是如何做到防止不可重复读的问题的,其中最关键的点在于本次读的事务是被系统允许读到版本链中的那条记录。所以系统又提出了一个Read View的概念,我们简单的看一下Read View有哪些内容:
m_ids:表⽰在⽣成ReadView时当前系统中活跃的读写事务的事务id列表。
min_trx_id:表⽰在⽣成ReadView时当前系统中活跃的读写事务中最⼩的事务id,也就是m_ids中的最⼩值。
max_trx_id:表⽰⽣成ReadView时系统中应该分配给下⼀个事务的id值。
⼩贴⼠注意max_trx_id并不是m_ids中的最⼤值,事务id是递增分配的。⽐⽅说现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么⼀个新的读事务在⽣成ReadView 时,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4。
creator_trx_id:表⽰⽣成该ReadView的事务的事务id。
⼩贴⼠我们前边说过,只有在对表中的记录做改动时(执⾏INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在⼀个只读事务中的事务id值都默认为0

那么我们在那些条件下判断记录的某个版本是否可见?
如果版本链中的trx_id和creator_trx_id相同,可见。
如果min_trx_id<trx_id可见,很好理解之前就提交的内容
如果max_trx_id<trx_id不可见,之后才生成的内容
如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断⼀下trx_id属性值是不是在m_ids列表中,如果在的话,不可见。不在的话可见

如果在这个版本里不可见,继续往上个版本找,一直到找到头

READ COMMITTED —— 每次读取数据前都⽣成⼀个ReadView
第一次读取生成一个readview,第二次新生成readview
REPEATABLE READ —— 在第⼀次读取数据时⽣成⼀个ReadView
只生成一个readview

小结

所谓的MVCC(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使⽤READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执⾏普通的SEELCT操作时 访问记录的版本链的过程,这样⼦可以使不同事务的读-写、写-读操作并发执⾏,从⽽提升系统性能。READ COMMITTD、REPEATABLE READ这两个隔离级别的⼀个很⼤不同就是:⽣成ReadView的时机不 同,READ COMMITTD在每⼀次进⾏普通SELECT操作前都会⽣成⼀个ReadView,⽽REPEATABLE READ只在第⼀次进⾏普通SELECT操作前⽣成⼀个ReadView,之后的查询操作都重复使⽤这个 ReadView就好了。

关于purge ⼤家有没有发现两件事⼉:
我们说insert undo在事务提交之后就可以被释放掉了,⽽update undo由于还需要⽀持MVCC,不能⽴即删除掉。 为了⽀持MVCC,对于delete mark操作来说,仅仅是在记录上打⼀个删除标记,并没有真正将它删除掉。
随着系统的运⾏,在确定系统中包含最早产⽣的那个ReadView的事务不会再访问某些update undo⽇志以及被打了删除标记的记录后,有⼀个后台运⾏的purge线程会把它们真正的删除掉。

解决并发的方式

首先并发分为这几种:
读-读:不会互相影响、
读-写:会产生各种脏读,幻读,不可重复读的问题
写-写:目前写和写都是锁处理的

那么我们现在针对并发的问题:
主要有两种解决方案:
读-写通过MVCC+写-写锁的方式:通过mvcc解决脏读,不可重复读以及幻读的问题,每次在一个事务里生成一个Read View,之后的每次查询都是通过这个获取结果。写和写依旧还是采用加锁的方式。那么我们可以思考一下MVCC最大的一个问题是啥?
读-写和写-写都是加锁的方式:也就是采用了serializable的隔离级别了。无论是读还是写,都需要等待。等上一条读或者写完成之后,会释放对应的锁,效率很低但是不容易出现问题。

在银行业务的场景下:mvcc并不能满足客户需求,他需要获取到最新的提交数据,你查询余额和更新余额需要串行。除了这种需要读写隔离的场景,其他的应该不需要读写加锁

对应的出现一致性读也就是MVCC生成一个read view的方式,锁定读也就是通过加锁的方式读取
锁定读还分了两种模式:共享锁S,还是独占锁X,怎么理解呢共享锁和共享锁是兼容的,也就是说读和读是可以并行的,其他的不能兼容

多粒度锁

我们之前提到的锁,都是针对数据行锁。锁的粒度比较细,现在我们对表加锁,会遇到哪些场景?
现在我们要对A表加锁,加X锁,那么我们是不是需要对数据所有行排查其中占用了那些锁。这样就很麻烦,关键点在于效率很低,所以我们在对行加锁的时候,会对表加一个意向锁。如果我们对行加了X锁,那么对表加的就是IX锁,也就是意向独占锁,如果对记录假的是S锁,会对表加IS锁,也就是意向共享锁
IS、IX锁是表级锁,它们的提出仅仅为了在之后加表级别的S锁和X锁时可以快速判断表中的记录是否被上锁,以避免⽤遍历的⽅式来查看表中有没有上锁的记录,也就是说其实IS锁和IX锁是 兼容的,IX锁和IX锁是兼容的。
在这里插入图片描述

行锁和表锁

InnoDB中的表级锁

表级别的S锁、X锁:增删改查语句一般是不会对表加锁的,而是执行对表变更ddl的语句时,其他事务语句就会发生阻塞。同理如果在执行表语句的时候,ddl语句也会发生阻塞。这个过程是通过在server层使⽤⼀种称之为元数据锁,⼀般情况下也不会使⽤InnoDB存储引擎⾃⼰提供的表级别的S锁和X锁。
表级别的IS锁、IX锁:当我们在对使⽤InnoDB存储引擎的表的某些记录加S锁之前,那就需要先在表级别加⼀个IS锁,当我们在对使⽤InnoDB存储引擎的表的某些记录加X锁之前,那就需要先在表级别加⼀个IX锁。IS锁和IX 锁的使命只是为了后续在加表级别的S锁和X锁时判断表中是否有已经被加锁的记录,以避免⽤遍历的⽅式来查看表中有没有上锁的记录
表级别的AUTO-INC锁:采⽤AUTO-INC锁,也就是在执⾏插⼊语句时就在表级别加⼀个AUTO-INC锁,然后为每条待插⼊记录的AUTO_INCREMENT修饰的列分配递增的值,在该语句执⾏结束后,再把AUTO-INC锁释放掉。这 样⼀个事务在持有AUTO-INC锁的过程中,其他事务的插⼊语句都要被阻塞,可以保证⼀个语句中分配的递增值是连续的。
采⽤⼀个轻量级的锁,在为插⼊语句⽣成AUTO_INCREMENT修饰的列的值时获取⼀下这个轻量级锁,然后⽣成本次插⼊语句需要⽤到的AUTO_INCREMENT列的值之后,就把该轻量级锁释放掉,并不 需要等到整个插⼊语句执⾏完才释放锁。

InnoDB的行锁

行锁的类型:
Record Locks:经典行锁,当一个事务对聚簇索引的获取了s锁,其他事务就不能对这个列再获取x锁,但是获取s锁可以。反之如果一个事务对该列获取了x锁,其他事务就不能继续获取其他锁了。
Gap Locks:主要是为了解决幻读的问题,我们之前说过mvcc采用了只读区一个read view的方式避免幻读的问题。也可以采用加锁的方式,也就是采用一个范围的锁,如果在聚簇索引的某一条a上获取了gap锁,那么a之前的这些数据范围再插入数据就会被阻塞。
Next-Key Locks:插入意向锁,比如上面的例子上a获取了gap锁,这时候我们有两个事务都想往a前插入数据,这时候t1就会获取插入意向锁,t2也会获得插入意向锁,等gap释放之后,这两个并不会互相阻塞。事实上插⼊意向锁并不会阻⽌别的事务 继续获取该记录上任何类型的锁
隐式锁:隐式锁的概念和行对应的隐藏trx_id列有关,也就是说我们在聚簇索引里插入了一条记录之后,有其他事务想对这条记录加锁,我们会对比一下当前活跃的事务列表,如果是的话,对帮助insert当前事务加锁,然后给自己加锁。
⼀个事务对新插⼊的记录可以不显式的加锁(⽣成⼀个锁结构),但是由于事务id这个⽜逼的东东的存在,相当于加了⼀个隐式锁。别的事务在对这条记录加S锁或者X 锁时,由于隐式锁的存在,会先帮助当前事务⽣成⼀个锁结构,然后⾃⼰再⽣成⼀个锁结构后进⼊等待状态。

InnoDB锁的内存结构

前边说对⼀条记录加锁的本质就是在内存中创建⼀个锁结构与之关联,那么是不是⼀个事务对多条记录加锁,就要创建多个锁结构呢?
mysql设计了锁内存结构如果满足下面的条件,就会在内存里创建锁结构:
在同⼀个事务中进⾏加锁操作
被加锁的记录在同⼀个页⾯中
加锁的类型是⼀样的
等待状态是⼀样的

在这里插入图片描述
锁所在的事务信息:只是记录一下事务对应的指针地址
索引信息:需要记录⼀下加锁的记录是属于哪个索引的。
表锁: 记载着这是对哪个表加的锁,还有其他的⼀些信息
⾏锁: 记载了三个重要的信息: Space ID:记录所在表空间。 Page Number:记录所在页号。n_bits:对于⾏锁来说,⼀条记录就对应着⼀个⽐特位,⼀个页⾯中包含很多记录,⽤不同的⽐特位来区分到底是哪⼀条记录加了锁。为此在⾏锁结构的末尾放置了⼀堆⽐特位,这 个n_bits属性代表使⽤了多少⽐特位。
type_mode: 这是⼀个32位的数,被分成了lock_mode、lock_type和rec_lock_type三个部分,如图所⽰:

锁的模式(lock_mode),占⽤低4位,可选的值如下: LOCK_IS(⼗进制的0):表⽰共享意向锁,也就是IS锁。 LOCK_IX(⼗进制的1):表⽰独占意向锁,也就是IX锁。 LOCK_S(⼗进制的2):表⽰共享锁,也就是S锁。 LOCK_X(⼗进制的3):表⽰独占锁,也就是X锁。 LOCK_AUTO_INC(⼗进制的4):表⽰AUTO-INC锁。
锁的类型(lock_type),占⽤第5~8位,不过现阶段只有第5位和第6位被使⽤: LOCK_TABLE(⼗进制的16),也就是当第5个⽐特位置为1时,表⽰表级锁。 LOCK_REC(⼗进制的32),也就是当第6个⽐特位置为1时,表⽰⾏级锁。

⾏锁的具体类型(rec_lock_type),使⽤其余的位来表⽰。只有在lock_type的值为LOCK_REC时,也就是只有在该锁为⾏级锁时,才会被细分为更多的类型: LOCK_ORDINARY(⼗进制的0):表⽰next-key锁。 LOCK_GAP(⼗进制的512):也就是当第10个⽐特位置为1时,表⽰gap锁。 LOCK_REC_NOT_GAP(⼗进制的1024):也就是当第11个⽐特位置为1时,表⽰正经记录锁。 LOCK_INSERT_INTENTION(⼗进制的2048):也就是当第12个⽐特位置为1时,表⽰插⼊意向锁。

⼀堆⽐特位: 如果是⾏锁结构的话,在该结构末尾还放置了⼀堆⽐特位,⽐特位的数量是由上边提到的n_bits属性表⽰的。我们前边唠叨InnoDB记录结构的时候说过,页⾯中的每条记录在记录头信息中都包含⼀ 个heap_no属性,伪记录Infimum的heap_no值为0,Supremum的heap_no值为1,之后每插⼊⼀条记录,heap_no值就增1。锁结构最后的⼀堆⽐特位就对应着⼀个页⾯中的记录,⼀个⽐特位映射⼀ 个heap_no,不过为了编码⽅便,映射⽅式有点怪
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值