Mysql隔离级别,锁的原理

Mysql隔离级别,锁的原理


抛开剂量谈毒性,就是耍流氓。

抛开存储引擎和隔离级别谈锁,也是耍流氓。

所以,先介绍一下存储引擎和隔离级别。

1.存储引擎

MyISAM:每个表有三个文件,.frm文件用于存储表结构,.myd文件用于存储数据,.myi文件用于存储索引

不支持事务,不支持外键,不支持聚集索引,不支持行锁

Innodb:每个表有两个文件,.frm文件用于存储表结构,.idb文件用于存储表的索引和数据

InnoDB为什么推荐使用自增ID作为主键?

    答:自增ID可以保证每次插入时B+索引是从右边扩展的,可以避免B+树的频繁合并和分裂(对比使用UUID)。如果使用字符串主键和随机主键,会使得数据随机插入,效率比较差。

2.隔离级别

2.1.脏读

脏读就是指一个事务访问到了另一个事务还未提交的数据

将隔离级别设置为read uncommitted(读未提交)

A:启动事务,查询数据,此时数据为初始状态

B:启动事务,更新数据,但不提交

A:再次读取数据,发现数据已经被修改了,这就是所谓的“脏读”

 

2.2.不可重复读

不可重复读是指在一个事务内两次读到的数据是不一样的(侧重修改数据)

将隔离级别设置为read committed(已提交读)

A:启动事务,查询数据,此时数据为初始状态

B:启动事务,更新数据,提交事务

A:再次读取数据,发现数据已发生变化,说明B提交的修改被事务中的A读到了,这就是所谓的“不可重复读”

2.3.幻读

幻读是指在一个事务内两次读取到的数据数量不一致(侧重插入数据)

A:启动事务,查询数据,此时数据为初始状态

B:启动事务,插入数据,提交事务

A:再次查询、更新数据,发现数据量不一致,这就是所谓的“幻读”

第二次查询,数据量没有变化,更新的时候,数据量发生变化,是因为MVCC解决了部分幻读的问题

2.4.MVCC (多版本并发控制) 

MVCC作用于RC和RR隔离级别,用于解决数据库的并发读写操作

MVCC解决了不可重复读和部分幻读的问题

MVCC的实现原理:

2.4.1

  • 6字节的事务ID(DB_TRX_ID)
  • 7字节的回滚指针(DB_ROLL_PTR)

事务100插入初始数据

 

事务200更改数据

当事务200更改该行的值时,会进行如下操作:

  • 用排他锁锁定该行
  • 记录redo log
  • 把该行修改前的值copy到undo log
  • 修改当前行的值,填写事务编号,使回滚指针指向undo log中的修改前的行
  • 当事务正常提交时Innbod只需要更改事务状态为COMMIT即可,不需做其他额外的工作
  • 而Rollback则稍微复杂点,需要根据当前回滚指针从undo log中找出事务修改前的版本,并恢复
  • Innodb中存在purge线程,它会查询那些比现在最老的活动事务还早的undo log,并删除它们,从而保证undo log文件不至于无限增长。

2.4.2

2.4.3

为了判断版本链中的哪个版本是当前事务可见的,提出了ReadView的概念,它包含4个主要内容

m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表;

min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值;

max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值;

creator_trx_id:表示生成该ReadView的事务的事务id。

A•如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,该版本可以被访问。
B•如果被访问版本的trx_id属性值大于或等于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,该版本不可以被访问
C•如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
D•如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断

RC隔离级别下

每次查询都新建一个read view

事务100第一次查询, m_ids{100,200},版本链{90},90符合A,可见

事务100第二次查询,m_ids{100,200},版本链{90,200},200符合D而且在列表中,不可见,然后看90

事务100第三次查询,m_ids{100},max_trx_id=201,版本链{90,200},200符合D而且不在列表中,可见

事务100第四次查询,m_ids{100},max_trx_id=301,版本链{90,200,300},300符合D而且不在列表中,可见

事务100第五次查询,m_ids{100},版本链{90,200,300,100},100符合C,可见

RR隔离级别下

一个事务中每次查询使用一个read view:m_ids{100,200},max_trx_id=201

事务100第一次查询,版本链{90},90符合A,可见

事务100第二次查询,版本链{90,200},200符合D而且在列表中,不可见,然后看90

事务100第三次查询,版本链{90,200},200符合D而且在列表中,不可见,然后看90

事务100第四次查询,版本链{90,200,300},300符合B,不可见,然后看200

事务100第五次查询,版本链{90,200,300,100},100符合C,可见

3.锁

共享锁、排他锁、意向共享锁、意向排他锁、记录锁、间隙锁、临键锁、插入意向锁、自增锁

3.1.共享锁(S)

共享锁也称读锁,行级别的锁,当前事务加共享锁之后,其他事务可以加共享锁,但不能加排他锁

加锁方式:select colname from tablename where ... lock in share mode;

                  insert into tablename select colname from tablename2 (对tablename2自动加共享锁)

A事务对数据加共享锁之后,B事务也可以对同一数据加共享锁,但加排他锁超时失败

3.2.排他锁(X)

排他锁也称写锁,行级别的锁,当前事务加排他锁之后,其他事务不能加共享锁和排他锁

加锁方式:select colname from tablename where ... for update;

                 insert ... delete... update... 

3.3.意向共享锁(IS)

表级别的锁,加共享锁之前,先要获取意向共享锁

3.4.意向排他锁(IX)

表级别的锁,加排他锁之前,先要获取意向排他锁

3.5.记录锁(Record Lock)

锁精确加在某一行上,基于主键和唯一索引

  select * from test where id=5 for update;//该句只锁id=5这一行

3.6.间隙锁(Gap Lock)

只在RR隔离级别下生效

锁加在不存在的空闲空间,可以是两个索引记录之间,也可能是第一个索引记录之前(负无穷,first-key)或最后一个索引之后(last-key,正无穷)的无限空间。

下表中的间隙锁(-∞,3),(3,5)......(13,+∞)

3.7.临键锁(Next-Key Lock)

基于非唯一索引的,每个非唯一索引上都有一把临键锁

只在RR隔离级别下生效

记录锁与间隙锁组合起来用就叫做临键锁

下表中存在的临键锁:(-∞,3],(3,5],......(13,+∞)

3.8.插入意向锁(Insert Intention)

插入意向锁是一种Gap锁,不是意向锁

插入的时候会获取当前间隙的gap锁,和插入行的X锁

假如有一个索引间隙4-7,不同的事务分别插入5和6,每个事务都会产生一个(4,7)插入意向锁,所以插入意向锁和插入意向锁是兼容的

插入意向锁和间隙锁是不兼容的

4.案例分析

id是主键,name是普通索引

4.1.唯一键+存在记录

select * from test where id = 5 for update;

只锁id=5的这一行

4.2.唯一键+不存在记录

select * from test where id = 18 for update;

RR隔离级别:获得间隙锁(16,20),id为17,18,19的不能插入,id为16,20的可以修改

RC隔离级别:只锁猪id=18的行锁

4.3.普通索引+存在记录

select * from test where num = 7 for update;

RR隔离级别:获得临键锁(5,7]和区间锁(7,9),num为6,8的不能插入,num为5,9的可以修改

RC隔离级别:获得num=7的行锁

4.4.普通索引+不存在记录

select * from test where num = 12 for update;

RR隔离级别:获得间隙锁(10,13)

RC隔离级别:获得num=12的行锁

5.死锁

两个事务都持有对方需要的锁,并且在等待对方释放,并且双方都不会释放自己的锁。

案例1

循环对同一资源加锁

解决办法:两个事务的业务顺序一致

案例2

S锁X锁

解决办法:把S锁改成X锁

案例3

Gap锁

解决办法:大事务化小事务,或者降低隔离级别

6.锁状态查询

https://www.sohu.com/a/253338562_495675

http://www.bubuko.com/infodetail-3344103.html

 

 

参考:https://tech.meituan.com/2014/08/20/innodb-lock.html

          https://blog.csdn.net/sofia1217/article/details/50778906

          

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值