面试官问我知不知道 MySQL 的锁,接下来的 15 分钟让他另眼相看

咱们知道,数据也是一种供许多用户共享访问的资源。如何保证数据并发访问的一致性、有效性,是全部数据库必须解决的一个问题,锁的冲突也是影响数据库并发访问性能的一个重要因素。从这一角度来讲,锁对于数据库而言就显得尤其重要。本文将带领你们一块儿深刻领略Mysql锁的各类风采。

表锁

表级锁是mysql锁中粒度最大的一种锁,表示当前的操做对整张表加锁,资源开销比行锁少,不会出现死锁的状况,可是发生锁冲突的几率很大。该锁定机制最大的特色是实现逻辑很是简单,带来的系统负面影响最小。因此获取锁和释放锁的速度很快。因为表级锁一次会将整个表锁定,因此能够很好的避免困扰咱们的死锁问题。表锁被大部分的mysql引擎支持,MyISAM和InnoDB都支持表级锁。
MyISAM只是支持表锁,所以性能相对Innodb来讲相对下降,而Innodb也支持表锁,可是默认的行锁,并且只有在查询或者其余SQL语句经过索引才会使用行锁。

行锁

行锁的是mysql锁中粒度最小的一种锁,由于锁的粒度很小,因此发生资源争抢的几率也最小,并发性能最大,可是也会形成死锁,每次加锁和释放锁的开销也会变大。目前主要是Innodb使用行锁,Innodb也是mysql在5.5.5版本以后默认使用的存储引擎。
行锁按照使用方式也氛围共享锁(S锁或者读锁)和排它锁(X锁或者写锁)

共享锁(S锁,读锁)

使用说明:若事务A对数据对象1加上S锁,则事务A能够读数据对象1但不能修改,其余事务只能再对数据对象1加S锁,而不能加X锁,直到事务A释放数据对象1上的S锁。这保证了其余事务能够读数据对象1,但在事务A释放数据对象1上的S锁以前不能对数据对象1作任何修改。
用法:
select ... lock in share mode;
----共享锁就是多个事务对于同一数据能够共享一把锁,都能访问到数据,可是只能读不能修改。复制代码

排它锁(X锁,写锁)

使用说明:若事务A对数据对象1加上X锁,事务A能够读数据对象1也能够修改数据对象1,其余事务不能再对数据对象1加任何锁,直到事务A释放数据对象1上的锁。这保证了其余事务在事务A释放数据对象1上的锁以前不能再读取和修改数据对象1。
select ... for update
----排他锁就是不能与其余所并存,如一个事务获取了一个数据行的排他锁,其余事务就不能再获取该行的其余锁复制代码

意向共享锁(IS)和意向排它锁(IX)

释义:
意向共享锁(IS):事务想要在得到表中某些记录的共享锁,须要在表上先加意向共享锁。
意向互斥锁(IX):事务想要在得到表中某些记录的互斥锁,须要在表上先加意向互斥锁。复制代码
意向共享锁和意向排它锁总称为意向锁。意向锁的出现是为了支持Innodb支持多粒度锁。
首先,意向锁是表级别锁。
理由:当咱们须要给一个加表锁的时候,咱们须要根据意向锁去判断表中有没有数据行被锁定,以肯定是否能加成功。若是意向锁是行锁,那么咱们就得遍历表中全部数据行来判断。若是意向锁是表锁,则咱们直接判断一次就知道表中是否有数据行被锁定了。因此说将意向锁设置成表级别的锁的性能比行锁高的多。
有了意向锁以后,前面例子中的事务A在申请行锁(写锁)以前,数据库会自动先给事务A申请表的意向排他锁。当事务B去申请表的写锁时就会失败,由于表上有意向排他锁以后事务B申请表的写锁时会被阻塞。
因此,意向锁的做用就是:
当一个事务在须要获取资源的锁定时,若是该资源已经被排他锁占用,则数据库会自动给该事务申请一个该表的意向锁。若是本身须要一个共享锁定,就申请一个意向共享锁。若是须要的是某行(或者某些行)的排他锁定,则申请一个意向排他锁。

乐观锁

乐观锁不是数据库自带的,须要咱们本身去实现。乐观锁是指操做数据库时(更新操做),想法很乐观,认为此次的操做不会致使冲突,在操做数据时,并不进行任何其余的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。 一般实现是这样的:在表中的数据进行操做时(更新),先给数据表加一个版本(version)字段,每操做一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,若是要对那条记录进行操做(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,若是相等,则说明这段期间,没有其余程序对其进行操做,则能够执行更新,将version字段的值加1;若是更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其余程序对其进行操做了,则不进行更新操做。
使用实例:
1. SELECT data AS old_data, version AS old_version FROM …;
2. 根据获取的数据进行业务操做,获得new_data和new_version
3. UPDATE SET data = new_data, version = new_version WHERE version = old_version
if (updated row > 0) {
// 乐观锁获取成功,操做完成
} else {
// 乐观锁获取失败,回滚并重试
}复制代码
优势:从上面的例子能够看出,乐观锁机制避免了长事务中的数据库加锁开销,大大提高了大并发量下的系统总体性能表现。 缺点:乐观锁机制每每基于系统中的数据存储逻辑,所以也具有必定的局限性,如在上例中,因为乐观锁机制是在咱们的系统中实现,来自外部系统的更新操做不受咱们系统的控制,所以可能会形成脏数据被更新到数据库中。在系统设计阶段,应该充分考虑到这些状况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程当中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。 总结:读用乐观锁,写用悲观锁。

悲观锁

悲观锁介绍(引自百科): 悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其余事务,以及来自外部系统的事务处理)修改持保守态度,所以,在整个数据处理过程当中,将数据处于锁定状态。悲观锁的实现,每每依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,不然,即便在本系统中实现了加锁机制,也没法保证外部系统不会修改数据)
悲观锁的实现:首先实现悲观锁时,咱们必须先使用set autocommit=0; 关闭mysql的autoCommit属性。由于咱们查询出数据以后就要将该数据锁定。
关闭自动提交后,咱们须要手动开启事务。
//1.开始事务
begin; 或者 start transaction;
//2.查询出商品信息,而后经过for update锁定数据防止其余事务修改
select status from t_goods where id=1 for update;
//3.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//4.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit; --执行完毕,提交事务复制代码
上述就实现了悲观锁,悲观锁就是悲观主义者,它会认为咱们在事务A中操做数据1的时候,必定会有事务B来修改数据1,因此,在第2步咱们将数据查询出来后直接加上排它锁(X)锁,防止别的事务来修改事务1,直到咱们commit后,才释放了排它锁。
优势:保证了数据处理时的安全性。缺点:加锁形成了开销增长,而且增长了死锁的机会。下降了并发性。
乐观锁更新有可能会失败,甚至是更新几回都失败,这是有风险的。因此若是写入居多,对吞吐要求不高,可以使用悲观锁。
下面三种锁都是innodb的行锁,前面咱们说过行锁是基于索引实现的,一旦加锁操做没有操做在索引上,就会退化成表锁。

间隙锁(Next-Key锁)

间隙锁,做用于非惟一索引上,主要目的,就是为了防止其余事务在间隔中插入数据,以致使“不可重复读”。
若是把事务的隔离级别降级为读提交(Read Committed, RC),间隙锁则会自动失效。
如图:(1,4),(4,7),(7,11),(11,∞)即为间隙锁要锁定的位置。
举例说明:
SELECT * FROM table WHERE id = 8 FOR UPDATE;
----此时,(7,11)就会被锁定
SELECT * FROM table WHERE id BETWEN 2 AND 5 FOR UPDATE;
----此时,(1,4)和(4,7)就会被锁定复制代码

记录锁

记录锁,它封锁索引记录,做用于惟一索引上,以下图所示:


select * from t where id=4 for update;
它会在id=4的索引记录上加锁,以阻止其余事务插入,更新,删除id=1的这一行。
须要说明的是:
select * from t where id=4;
则是快照读(SnapShot Read),它并不加锁,不影响其余事务操做该数据。复制代码

临键锁

临键锁,做用于非惟一索引上,是记录锁与间隙锁的组合,以下图所示:

它的封锁范围,既包含索引记录,又包含索引以前的区间,即(负无穷大,1],(2,4],(5,7],(8,11],(12,无穷大]。
在事务A中执行:
UPDATE table SET name = 'javaHuang' WHERE age = 4;
SELECT * FROM table WHERE age = 4 FOR UPDATE;
这两个语句都会锁定(2,4],(4,7)这两个区间。
即, InnoDB 会获取该记录行的 临键锁 ,并同时获取该记录行下一个区间的间隙锁。复制代码
临键锁的出现是为了innodb在rr隔离级别下,解决幻读问题(如何解决幻读问题,后续会出文章详细解答,也能够关注公众号【ToBeTopJavaer】,查看)。
若是把事务的隔离级别降级为RC,临键锁则也会失效。

死锁

释义:死锁是指两个或两个以上事务在执行过程当中因争抢锁资源而形成的互相等待的现象

上图所示,即为死锁产生的常规情景。
那么如何解决死锁?
1.等待事务超时,主动回滚。
2.进行死锁检查,主动回滚某条事务,让别的事务能继续走下去。
下面提供一种方法,解决死锁的状态:
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;--查看正在被锁的事务复制代码


kill trx_mysql_thread_id;--(上图trx_mysql_thread_id列的值)复制代码
死锁是一个很复杂的话题,此处只能简而言之,后续会写一篇专门讲解死锁的文章。

总结

经过本文,大体了解了mysql大部分锁的功能,做用,实现以及解决方法,我想作为了一个java开发工程师,了解到这个程度应该已经够了,毕竟咱们不是DBA,否则了解太深,抢了DBA的饭碗可就不太好了,开个玩笑,毕竟学无止境。
更多精彩文章,关注公众号【ToBeTopJavaer】,更有以下数万元精品vip资源免费等你来拿!!!复制代码

1574903758(1).jpg

1574903796(1).jpg

相关文章
相关标签/搜索