具体细节 请去掘金购买《MySQL 是怎样运行的:从根儿上理解 MySQL》
解决并发事务带来问题的两种基本方式
读-读情况:即并发事务相继读取相同的记录。
- 1.该事务无影响
写-写情况:即并发事务相继对相同的记录做出改动。
- 1.需要加锁不然会发生脏写
读-写或写-读情况:也就是一个事务进行读取操作,另一个进行改动操作
- 1.能发生脏读、不可重复读、幻读的问题
什么是锁
- 1.锁是一种结构,javaer可以理解为一个对象。
- 2.当一个事务想对一条记录进行修改,会去内存中寻找该记录关联的锁结构(估计是hash),如果没有锁结构
则生成。 - 3.当生成锁结构将is_waiting为false,代表锁获取成功,此时其他事务发现事务1的锁结构后,也生成一个
自己事务的锁结构,只是其is_waiting是为true,表示事务需要等待。当前面获取锁的事务结束之后,会把其自己的
锁结构给释放,然后检查是否有等待事务,如果有则把等待事务的锁结构is_waiting改为false,并且唤醒等待线程。
锁结构
- 1.trx信息:代表这个锁结构是哪个事务生成的。
- 2.is_waiting:代表当前事务是否在等待。
- 3.type:锁的类型,gap,插入意向锁,record,表锁
- 4.锁信息:表锁有表信息和其他信息,行锁有spaceid+pageNumber+n_bit(代表使用了多少个比特位)
- 5.一堆比特位:如果是行锁结构的话,在该结构末尾还放置了一堆比特位,比特位的数量是由上边提到的n_bits属性表示的
锁重用的条件(最后只需要修改锁结构对应的事务即可)
- 1.在同一个事务中进行加锁操作
- 2.被加锁的记录在同一个页面中
- 3.加锁的类型是一样的
- 4.等待状态是一样的
名词解释
- 1.不加锁:意思就是不需要在内存中生成对应的锁结构,可以直接执行操作。
- 2.获取锁成功,或者加锁成功:意思就是在内存中生成了对应的锁结构,而且锁结构的is_waiting属性为false,也就是事务可以继续执行操作
- 3.获取锁失败,或者加锁失败,或者没有获取到锁:意思就是在内存中生成了对应的锁结构,不过锁结构的is_waiting属性为true,也就是事务需要等待,不可以继续执行操作。
怎么解决脏读、不可重复读、幻读这些问题呢
方案一:读操作利用多版本并发控制(MVCC),写操作进行加锁。
- 1.而写操作肯定针对的是最新版本的记录,读记录的历史版本和改动记录的最新版本本身并不冲突,也就是采用MVCC时,读-写操作并不冲突
方案二:读、写操作都采用加锁的方式。
- 1.如果我们的一些业务场景不允许读取记录的旧版本,而是每次都必须去读取记录的最新版本,也就是当前读
一致性读(Consistent Reads)
- 1.事务利用MVCC进行的读取操作称之为一致性读,或者一致性无锁读,有的地方也称之为快照读
锁定读(Locking Reads)
- 1.共享锁,S锁。在事务要读取一条记录时,需要先获取该记录的S锁。
- 2.独占锁,也差排他锁 X锁,在事务要改动一条记录时,需要先获取该记录的X锁。
锁定读的语句
- 1.SELECT ... LOCK IN SHARE MODE;---S锁
- 2.SELECT ... FOR UPDATE;---X锁
写操作--DELETE
- 1.对一条记录做DELETE操作的过程其实是先在B+树中定位到这条记录的位置
- 2.然后获取一下这条记录的X锁,然后再执行delete mark操作。
- 3.我们也可以把这个定位待删除记录在B+树中位置的过程看成是一个获取X锁的锁定读。
写操作--UPDATE(一)
- 1.如果未修改该记录的键值并且被更新的列占用的存储空间在修改前后未发生变化
- 2.则先在B+树中定位到这条记录的位置,然后再获取一下记录的X锁,最后在原记录的位置进行修改操作
- 3.这个定位待修改记录在B+树中位置的过程看成是一个获取X锁的锁定读。
写操作--UPDATE(二)
- 1.如果未修改该记录的键值并且至少有一个被更新的列占用的存储空间在修改前后发生变化
- 2.则先在B+树中定位到这条记录的位置,然后获取一下记录的X锁,将该记录彻底删除掉(就是把记录彻底移入垃圾链表),最后再插入一条新记录
- 3.定位待修改记录在B+树中位置的过程看成是一个获取X锁的锁定读,新插入的记录由INSERT操作提供的隐式锁进行保护。
写操作--UPDATE(三)
- 1.如果修改了该记录的键值,则相当于在原记录上做DELETE操作之后再来一次INSERT操作,加锁操作就需要按照DELETE和INSERT的规则进行了。
写操作--INSERT
- 1.一般情况下,新插入一条记录的操作并不加锁,通过一种称之为隐式锁来保护这个新插入的记录在本事务提交前不被别的事务访问
多粒度锁
- 1.我们前边提到的锁都是针对记录的,也可以被称之为行级锁或者行锁
- 2.其实一个事务也可以在表级别进行加锁,自然就被称之为表级锁或者表锁
- 3.给表加的锁也可以分为共享锁(S锁)和独占锁(X锁)
如果一个事务给表加了S锁
- 1.别的事务可以继续获得该表的S锁
- 2.别的事务可以继续获得该表中的某些记录的S锁
- 3.别的事务不可以继续获得该表的X锁
- 4.别的事务不可以继续获得该表中的某些记录的X锁
如果一个事务给表加了X锁
- 1.别的事务不可以继续获得该表的S锁
- 2.别的事务不可以继续获得该表中的某些记录的S锁
- 3.别的事务不可以继续获得该表的X锁
- 4.别的事务不可以继续获得该表中的某些记录的X锁
意向共享锁,英文名:Intention Shared Lock,简称IS锁
- 1.当事务准备在某条记录上加S锁时,需要先在表级别加一个IS锁。
意向独占锁,英文名:Intention Exclusive Lock,简称IX锁
- 1.当事务准备在某条记录上加X锁时,需要先在表级别加一个IX锁
加表锁逻辑
- 1.事务A先去查看是否存在意向锁,如果存在再坚持该意向锁和自己的表锁是否冲突
- 2.如果不冲突则加表锁,冲突则等待锁释放
IX锁和IS的关系
- 1.当我们给表加了IX锁,并不影响其他事务给其加IS锁
- 2.原因是因为意向锁只是真多表锁,具体到记录那边。如果有锁冲突,则会等到行锁释放。
InnoDB存储引擎中的锁
- 1.在对某个表执行一些诸如ALTER TABLE、DROP TABLE这类的DDL语句时,其他事务对这个表并发执行诸如SELECT、INSERT、DELETE、UPDATE的语句会发生阻塞
- 2.上述1不是通过表锁来执行的,而是通过server层的一种元数据锁(mdl)
表级别的AUTO-INC锁
- 1.我们自增的时候需要确保自增id唯一采用的锁,该锁是表级别的
- 2.个AUTO-INC锁的作用范围只是单个插入语句,插入语句执行完成后,这个锁就被释放了,跟我们之前介绍的锁在事务结束时释放是不一样的。
- 3.如果我们的插入语句在执行前不可以确定具体要插入多少条记录(无法预计即将插入记录的数量),一般是使用AUTO-INC锁为AUTO_INCREMENT修饰的列生成对应的值。
- 4.采用一个轻量级的锁(非表锁),在为插入语句生成AUTO_INCREMENT修饰的列的值时获取一下这个轻量级锁,然后生成本次插入语句需要用到的AUTO_INCREMENT列的值之后,就把该轻量级锁释放掉,并不需要等到整个插入语句执行完才释放锁。
InnoDB中的行级锁
Record Locks
- 1.仅仅给一条记录(叶子节点)加锁--有S和X之分
Gap Locks
- 1.在REPEATABLE READ隔离级别下是可以解决幻读问题的,其中gap锁就是一个解决方案
- 2.我理解的gap是在我们查询记录的边界加锁
- 3.gap锁的提出仅仅是为了防止插入幻影记录而提出的,虽然有共享gap锁和独占gap锁这样的说法,但是它们起到的作用都是相同的
- 4.而且如果你对一条记录加了gap锁(不论是共享gap锁还是独占gap锁),并不会限制其他事务对这条记录加Record锁或者继续加gap锁
- 5.那对于最后一条记录之后的间隙 或者第一条用户记录记录可以陪Infimum和Supremum
Next-Key Locks:
- 1.gap+record的组合锁
Insert Intention Locks(插入意向锁)
- 1.事务在等待的时候也需要在内存中生成一个锁结构,表明有事务想在某个间隙中插入新记录,这个结构就是Insert Intention Locks
- 2.插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁(插入意向锁就是这么鸡肋),前提是gap锁被释放
隐式锁
- 1.一个事务对新插入的记录可以不显式的加锁(生成一个锁结构),但是由于事务id这个牛逼的东东的存在,相当于加了一个隐式锁。别的事务在对这条记录加S锁或者X锁时,由于隐式锁的存在,会先帮助当前事务生成一个锁结构,然后自己再生成一个锁结构后进入等待状态。
- 2.对于主键索引,如果记录生成了但是还未提交,此时其他事务想对该记录加锁,发现事务还活着,则帮忙生成锁结构,然后自己等待
- 3.对于二级索引,本身并没有trx_id隐藏列,但是在二级索引页面的Page Header部分有一个PAGE_MAX_TRX_ID属性,该属性代表对该页面做改动的最大的事务id
- 4.如果PAGE_MAX_TRX_ID属性值小于当前最小的活跃事务id,那么说明对该页面做修改的事务都已经提交了
否则就需要在页面中定位到对应的二级索引记录,然后回表找到它对应的聚簇索引记录,然后再重复2的做法。
拾遗
- 1.我们说过普通的SELECT语句在READ COMMITTED和REPEATABLE READ隔离级别下会使用到MVCC读取记录
- 2.如果相加表S锁,必须确保没有行级X锁(当然了表级X锁也不行)
- 3.如果相对表加X锁,则必须保证行级锁都不存在
- 4.加表锁的时候通过意向锁来判断是否存在行锁。
网友评论