前言
先介绍一个日常开发中的场景:
比如我们需要对一个账户加1000块钱,现在并发有两个人同时给该账号转载,那么会不会出现明明转载了两笔,缺只加了1000块钱呢?
当然我们知道这肯定是不能容忍的,那么mysql是如何解决这个问题的呢?
首先需要说的时mysql在innodb下默认情况下一个DML语句会自动开启一个事务,执行结束后默认提交事务,那么转账两笔的场景如下:
账号1:
begin;// 开启事务
update 账单表 set money+1000 where id=xiaowang;
// 代码1
commit; // 提交事务
账号2:
begin;// 开启事务
update 账单表 set money+1000 where id=xiaowang;
// 代码2
commit;// 提交事务
那么当账号1执行到代码1的位置时,账号2开始执行并且也执行到代码2了,那么假设初始money为0,账号1和账号2当前的money就是1000,当事务都提交后money就是1000,也就是出现了事务的隔离性问题了
我们自己在mysql下测试可以发现当账号1的事务没有提交前,账号2的update语句会被阻塞。
锁类型
mysql在innodb下主要的锁类型有以下几种:
共享锁
也叫做读锁,获得一行数据的读锁之后,其他事务也可以读取该数据,但是不可以修改该数据,所以称之为读锁。
共享锁的作用:因为共享锁会阻塞其他事务的修改,所以可以用在不允许其他事务修改数据的场景。
那么怎么加一个读锁呢?
select * from table where id = 1 LOCK IN SHARE MODE;
排它锁
只要一个事务获取了一行数据的排他锁,那么其他事务就不能获得该数据的排它锁或者共享锁了。
加排他锁的方式有两种:
- 1、我们在操作数据时,包括增删改都会给数据加上一个排他锁,这也就是上面我们的问题解决隔离性的方案,数据加上了排他锁,那么其他事务就不能同时来修改数据了
- 2、手动加排它锁,select * from table where id = 1 for update; 使用for update,使用过oracle的同学应该很熟悉
排他锁和共享锁都是行锁,接下来是表锁
意向锁
当我们给一行数据加上一个意向锁时在这个表上加上一个意向共享锁
当我们给一行数据加上一个排他锁时在这个表上加上一个意向排他锁
那么这个意向锁有什么用呢,如果没有意向锁,当我们想给一个表加表锁时需要去检查表里面的每一行数据上是否有行锁,如果有,那么加表锁就不应该成功,这样无疑是非常耗时的。那么如果存在意向锁就不需要去检索数据了。
笔者的一个案例,笔者在本地跑代码时在某个开启事务的代码里面打上了断点,导致边上另外一位同事修改数据表结构时一直处于阻塞状态。
后来查询事务和锁的状态后发现和这个有关,笔者释放了当前事务后,另外一位同事的修改就执行成功了
这么看来原因是我对一行数据加了行的排它锁,同时表上会被加上一个意向排它锁,当修改数据表结构时需要给表加表锁,此时表上已经存在一个表锁(意向排他锁),所以只能阻塞
行锁的原理
行锁是mysql innodb存储引擎下才有的一个特性,其他存储引擎不支持行锁。
行锁锁住的范围是什么呢?
行锁锁住的其实是索引
如果我们有一张表,id是主键
那么当我们执行select * from table where id = 1 for update时,语句select * from table where id = 2 for update不会被阻塞,因为主键索引上id=1的索引的锁住了,但是id=2索引没有被锁住
当我们的id不是主键时,则select * from table where id = 2 for update会被阻塞,原因是加锁时id不是索引,那么mysql会把整个表锁住
所以我们在使用排它锁时一定要记得查询语句需要使用到索引,最好是使用到主键索引
另外,当我们使用某个二级索引进行查询条件加排它锁时,在主键索引中对应的索引也会被加上排它锁,原因是二级索引的该条行数据在查询时会去主键索引回表,索引会同时把主键索引的对应行锁住,本质上这是同一条数据。
以上就是笔者对mysql锁的一些理解