mysql锁总结

背景

很多时候有一种感觉,自己写的代码还没有出bug有两个原因,一个是运气好,一个是mysql优化得好,出错的几率很小,所以随便怎么写,没有达到百万级数据量,没有达到一定的并发,一般是不会出问题的,但是自己对mysql知道的还是太少,只会实现业务的增删改查,加加索引,这样在高并发的情况下程序会怎么运行,是否会有问题,很难预测,听天由命了,因此学习了一下mysql锁的知识,如果知道了什么时候会加什么锁,加了锁之后有什么现象,那么面对高并发下的程序运行情况,就是可以推测的了,也能写出更稳定的程序了

锁的类型

表锁和行锁

myisam和innodb引擎,除了事务之外的区别,还有一个很重要的区别就是,innodb支持行锁,而myisam只支持表锁,普通的操作可能看不出什么差别,但是如果是并发比较大的话,性能差别就很明显了。但是行锁相比于表锁,实现起来也更加的复杂,要考虑更多的细节

快照读和当前读

在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。

快照读:简单的select操作,属于快照读,不加锁
select * from table where ?;
当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁,试想一下,updatedelete之前总得找到那条记录,而且还得保证这条记录是最新的,那么必须是当前读。
select * from table where ? lock in share mode;  //会加共享锁
select * from table where ? for update; //会加排它锁
insert into table values (); //会加排它锁和间隙锁
update table set ? where ?; //会加排它锁,如果是范围更新,会加间隙锁
delete from table where ?; //会加排它锁,如果范围删除,会加间隙锁

乐观锁和悲观锁

乐观锁

乐观锁总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制来实现,这种一般在程序中实现:例如:

//更新订单状态
update table set status = 20 where status = 10 and id = 1; 
//后面的status = 10 就是相当于乐观锁的表现,如果这条记录已经被更新了,那么就不会更新了,这个也叫做原子性操作
悲观锁

悲观锁总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁,当其他线程想要访问数据时,都需要阻塞挂起
,innodb的共享锁和排它锁都属于悲观锁,不同的是共享锁允许其他会话读,阻塞写,排它锁,阻塞其他会话的写和读

共享锁和排它锁

共享锁:允许其他会话读,阻塞其他会话写,其他会话要写,得等当前锁释放,如果一直不释放,会等到超时
排它锁:阻塞其他会话的写和读,其他会话要写或者读,都要等待当前锁释放,如果一直不释放,会等到超时
innodb中,select 操作默认不加锁,但是

select * from table where ??  LOCK IN SHARE MODE   //会加共享锁,其他会话不能写
select * from table where ?? FOR UPDATE            //会加排它锁,其他会话不能读写

innodb中,其他操作,insert update delete 都是会加排它锁

意向共享锁和意向排它锁

在这里插入图片描述
上图中的X表示排它锁,S表示共享锁,IS表示意向共享锁,IX表示,意向排它锁,红色表示不能并存,绿色表示可以并存
首先说明一下意向锁都是表级的锁,它的作用是解决innodb的行锁和表锁的冲突,假设有一个场景,事务A要更新A表的一行,给这一行加了一个排它锁,事务B要给A表一个表级的排它锁,如果没有意向锁,事务B的表级排它锁是可以加的,表示事务B是可以写表中的任意行,这个事务A的排它锁是冲突的,因此不应该给事务B加表级的排它锁,这个时候,意向锁来了,事务A申请行锁排它锁之前给表加个意向排它锁,事务B来加行级排它锁,也先加意向排它锁,不冲突,可以继续加行级排它锁(和事务A锁定的不是同一行就行),如果事务C来申请表级的排它锁,由于意向排它锁和排它锁是不能兼容的,因此会阻塞,就不会和事务A的行级排它锁冲突了。

间隙锁(gap 锁)

当innodb进行delete, update的范围更新,以及进行insert的时候,会加间隙锁,间隙锁也属于排它锁,防止出现delete,update,insert还未结束,又有新的数据写入,导致数据出现不一致

不同隔离级别下不同

innodb默认RR隔离级别,也有RC隔离级别,其他两种应该没什么人用了,在不同的隔离级别下,加锁也有所不同,这里只分析RR级别下的加锁情况

加锁的场景分析

假设sql:

delete from t1 where id = 10;

会加什么锁,这里有一些不同的场景

1,,id列是主键,RR隔离级别
这个组合,是最简单,最容易分析的组合。id是主键,给定SQL:delete from t1 where id = 10; 只需要将主键上,id = 10的记录加上X锁即可。如下图所示:
在这里插入图片描述
2,id列是二级唯一索引,RR隔离级别
此组合中,id是unique索引,而主键是name列。此时,加锁的情况由于组合一有所不同。由于id是unique索引,因此delete语句会选择走id列的索引进行where条件的过滤,在找到id=10的记录后,首先会将unique索引上的id=10索引记录加上X锁,同时,会根据读取到的name列,回主键索引(聚簇索引),然后将聚簇索引上的name = ‘d’ 对应的主键索引项加X锁。
在这里插入图片描述
3,,id列是二级非唯一索引,RR隔离级别
在这里插入图片描述
考虑到B+树索引的有序性,满足条件的项一定是连续存放的。记录[6,c]之前,不会插入id=10的记录;[6,c]与[10,b]间可以插入[10, aa];[10,b]与[10,d]间,可以插入新的[10,bb],[10,c]等;[10,d]与[11,f]间可以插入满足条件的[10,e],[10,z]等;而[11,f]之后也不会插入满足条件的记录。因此,为了保证[6,c]与[10,b]间,[10,b]与[10,d]间,[10,d]与[11,f]不会插入新的满足条件的记录,MySQL选择了用GAP锁,将这三个GAP给锁起来。Insert操作,如insert [10,aa],首先会定位到[6,c]与[10,b]间,然后在插入前,会检查这个GAP是否已经被锁上,如果被锁上,则Insert不能插入记录。因此,通过第一遍的当前读,不仅将满足条件的记录锁上 (X锁),与组合三类似。同时还是增加3把GAP锁,将可能插入满足条件记录的3个GAP给锁上,保证后续的Insert不能插入新的id=10的记录,也就杜绝了同一事务的第二次当前读,出现幻象的情况。

4,id列上没有索引,RR隔离级别
在这里插入图片描述
如图,这是一个很恐怖的现象。首先,聚簇索引上的所有记录,都被加上了X锁。其次,聚簇索引每条记录间的间隙(GAP),也同时被加上了GAP锁。这个示例表,只有6条记录,一共需要6个记录锁,7个GAP锁,在这种情况下,这个表上,除了不加锁的快照度,其他任何加锁的并发SQL,均不能执行,不能更新,不能删除,不能插入,全表被锁死。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值