mysql 锁机制

Posted zhuxineli

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql 锁机制相关的知识,希望对你有一定的参考价值。

innodb的锁分为共享锁和排它锁。这跟myisam中的读锁和写锁有很多不同,也是大多数人容易混淆的地方。myisam中的两种锁默认都是存储引擎自己加上去的(当然自己手动加也可以),查询时即(加了读锁之后),可以对该表进行读,但不能写。更新删除插入(加了写锁之后),不能读写。
《----来自20180718的更改-----经过一段时间的学习,现在对于锁的认识和当初有了较大不同,当然现在仍然有可能是不准确的。上面我说到,innodb的锁分为共享锁和排它锁,这句话就不严谨,准确的说应该是共享锁、排它锁、意向共享锁、意向排它锁、插入锁。同时锁的算法又分为:record lock/gap lock/next-key lock。其次,我说到myisam有两种锁,我现在认为myisam只有一种锁,就是表锁,存储引擎自己加的,自己没法加其他锁。之前的理解是有问题的。》
但innodb的锁很大不同,查询时默认是不加任何锁的《准确的是说应该是非事务中的查询是不加任何锁的,因为锁的对象是事务》。添加删除修改时默认会加排它锁。
共享锁:即给锁定的行加锁,其他事务也可以给锁定的行加共享锁,但不能加排它锁。即此时,可以查询该行,但不能删除修改。当前事务也不能删除修改,除非等到共享锁释放。
排它锁:即给锁定的行加锁,别的事物不能给锁定的行加任何锁,什么锁都不行。注意:这里是不能添加任何锁,**但可以普通的查询,因为简单的select语句是不加任何锁的,所以可以查询,这是很多人会混淆的地方。但**很明显不能select ... lock in share mode 和 select .... for update.当然更不能删除修改了,因为这两个操作存储引擎默认会加排它锁。
那什么是死锁?说到这里就头大啊!!!之前总是混淆锁等待和死锁的概念,死锁是指两个资源相互争夺造成等待的情况,没有外界条件干预就要挂的情况,而锁等待是指比如我加了排他锁,你想要加共享锁,不好意思,你得等我释放了该锁之后你才能用。完全是两回事,说起来好理解,但实际情况下比较容易搞混。下面说几个会导致死锁的场景
1 导致死锁的场景一 
session1对select * from table where id = 100 lock in share mode<1>;语句加共享锁,然后update .....where id = 100;<3>
session2对select * from table where id = 100 lock in share mode;<2>语句也加共享锁,然后update.....where id = 100;<4>注意我标注的顺序,实际执行语句是按我标注的语句来的,注意这是分别在事务中进行,不在事务中的话没有意义。
这种情况就会导致在执行<4>的时候死锁。
第一条语句给当前行加共享锁,
第三条语句想要修改该行,因为了该行加了共享锁所以不能被修改,第三条语句要等待session2的共享锁的释放......,
第四条语句也想要修改该行,它要等待session1中共享锁的释放,它这时候检测到session1中有语句等待本session的锁的释放,造成了死锁。然后自动退出....
一个比较弱智但又考验基础的问题,为什么这两个会话一定要在事务中才会产生死锁呢?因为这两种锁必须和事务一起结合使用,单独使用的话可以说没有任何意义;试想,如果单独使用的话,sql语句一执行完,锁就马上释放,那刚才我们的锁还有什么用?自己读取的时候不让别人写,这个可以实现,但是自己一释放锁,别人就可以马上实现自己想做的事了,试问,你加这个锁干嘛?不就是为了读的时候锁住,然后干自己的下一步想做的事嘛!!!这可倒好,第一句执行完,让别人先干了,那用这第一句有什么用?那如何让锁不释放,放到事务中,事务一提交,锁才会释放。所以,这时候我们就知道了,所有导致死锁的情况一定是在事务中的,因为如果不在事务中的话就没有资源循环争夺这一说,顶多是你占用了这个锁,我也想用,我就等呗,按顺序来,谁等到了算谁的,如果真慢的不行了,那就让服务器挂了,但不会出现死锁。
那这种情况如何解决?很明显,这种问题的出现是session1加了锁的情况下,session2仍然能加锁,那就不让session2加锁就可以了呗,将共享锁,换成排它锁就可以了。session1在执行过程中,别的会话不能获得任何锁的权利,你只能等我所有语句都执行完了你才能继续往下走。
2 导致死锁的场景一
session1 中update table set username = 'kobe' where id = 1;<1> update table set username = 'byrant' where id = 2;<3>
session2 中update table set username = 'paul' where id = 2;<2>update table set username = 'gusal' where id = 1;<4>
同上,我标注的是执行顺序,理想情况下当然不是这样了,如果都是理想情况,哪还有什么锁的存在。
语句一执行,对相应id=1的行加排它锁。
语句二执行,对相应的id=2的行加排它锁。
语句三执行,想要修改id=2的行,那就要先获得相应的id=2的行的锁,但已经被session2占据,session1等待session2锁的释放
语句四执行,想要修改id=1的行,那就要先获得相应的id=1的行的锁,但已经是session1占据,session2等待session1锁的释放.....
互相等待,互相伤害,死锁。

其实,以上两个例子,我们就可以推断出造成死锁的原因是什么!
事务1中想使用的锁已经被事务2占用,需要啊等待事务2的释放。事务2想使用的锁也被事务1占用,需要等待事务1的释放,造成了死循环等待。
所以,造成死锁不是哪一种锁的特权,共享锁和排它锁都有可能造成死锁。

锁问题
1 丢失更新
一个事务的更新操作被另一个事务的更新操作锁覆盖。
2 脏读
什么是脏数据?未提交的数据。在一个事务中,读到了其他事务未提交的数据即为脏读
3 不可重复读
在一个事务中,多次读取同一数据集合,在这个事务还没结束时,另外一个事务也在访问同一个数据集合,并做了一些DML操作,导致第一个事务多次读取的数据不同
2和3的不同是:脏读是因为读到了未提交的数据,而不可重复读是读到了已提交的数据。
4 幻读
和不可重复读很像,不同的是不可重复读是因为修改导致的数据集不同,而幻读是因为插入导致的不同,仿佛产生了幻觉(??怎么比上次多了一些数据??)所以叫幻读

事务的隔离级别
read uncommited 读未提交
read commited 读已提交
repeatable read 可重复读
searialize 串行化
下面来说一下这几种隔离级别分别解决什么锁问题,以及背后的原理
首先,第一个读未提交,从字面上就可以了理解,可以读取另一个事务未提交的数据,很明显这违背了事务的隔离性,是不被允许的,在实际生产环境中也没有人会使用该隔离级别,这会导致脏读、不可重复读和幻读。
读已提交,这个就要比上一个好一些了,只能读取别的事务已经提交了的数据,正因为如此可以避免脏读的出现,但它无法避免不可重复读的出现,具体原因稍后再讲。
可重复读,意思是相对于解决的‘不可重复读’这个问题来命名的吧,我允许你这个事务重复读,并且保证在事务运行过程中多次读到的数据是相同的,所以叫可重复读。
串行化,为了解决幻读而生。
相信,看了上面这些还是比较懵的吧------------------比较懵的分割线------------------------
让我们闲来了解两个概念:一致性非锁定读(consistent nonlocking read)和一致性锁定读
一致性非锁定读是指:InnoDB通过行多版本控制的方式来读取当前执行时间数据库中行的数据,如果读取的行正在执行delete或update操作,这是读取操作不会等待行上锁的释放。相反地,InnoDB会去读取行的一个快照数据。可以看到,非锁定读机制极大的提高了数据库的并发性。这是InnoDB默认的读取方式,即读取不会占用和等待表上的锁。但不同的隔离级别对数据快照的定义也不同。
在read commited下, 对于快照数据,非一致性读总是读取被锁定行的最新一份快照。
而repeatable read下,对于快照数据,非一致性读总是读取事务开始时的行数据版本。
所以,这时候你就明白了,为什么可重复读(repeatable read)可以防止不可重复读的问题的出现,就是因为这个机制决定了在事务运行过程中读取的数据是一致的,而不论这个过程中该行是否被其他事务修改或删除。
一致性锁定度:和非锁定读对应,有些情况下用户需要对读取操作显式进行加锁以保证数据逻辑的一致性。InnoDB对于select 语句支持两种一致性的锁定度操作:即共享锁和排它锁。现在明白了吧,x/s锁是一致性锁定度,不是存储引擎默认的读取方式(想想也是哈,存储引擎默认肯定是为了保证数据并发的,你数据一致性有了问题,好,我给你提供一种别的解决方法,如果没有问题,你就继续用我默认的读取方式)。x/s锁必须在事务中(因为锁的对象是事务),当事务提交了,锁也就释放了。所以说如果你平时想测试select …for update/lock in share mode的效果的话在普通的sql语句中是不起作用的,必须声明在事务中begin/start transaction/set autocommit = 0;
---------------------------------------再深入一点----------------------------------------------
行锁的三种算法(这是隔离级别实现效果的原理)
record lock:单个行记录上的锁
gap lock:间隙锁,锁定一个范围,但不包含记录本身
next-key lock:gap lock+record lock 锁定一个范围,并且锁定行记录本身
InnoDB默认隔离级别下的默认行锁算法是next-key lock
举例说明
表a,插入三条数据,值分别为1、2、5
create table t (a int primary key);
insert into t select 1;
insert into t select 2;
insert into t select 5;

时间事务一事务二
1begin
2select * from t where a = 5 for update;
3begin
4insert into t select 4
5commit;//成功,不需要等待
6commit

事先说明一下,当查询的索引含有唯一属性时,比如主键或者唯一索引,InnoDB会对next-key lock进行优化,将其降级为record lock,即仅锁住索引本身,而不是范围。上述例子中,在事务一中对a=5进行x锁定,而由于a是主键,所以紧紧对a=5这条记录进行锁定而不是(2,5)这个范围,这样事务二插入a=4的时候不会阻塞,可以立即插入成功,即锁定有next-key lock降级为record lock,提高系统的并发性。所以这时候你就明白了,如果是非唯一索引的话,会使用next-key lock算法,会锁定范围,导致范围之内的数据不能被修改和插入,这就是为什么设置了不同隔离级别能够防止不可重复读、幻读的原因。

以上是关于mysql 锁机制的主要内容,如果未能解决你的问题,请参考以下文章

Mysql各种锁机制

MySQL锁机制

MySQL锁机制

MySQL锁机制

mysql插入意向锁测试

mysql innodb插入意向锁