原文连接:blog.ouyangsihai.cn >> MySQL的又一神器-锁,MySQL面试必备html
在生活中锁的例子多的不能再多了,从古老的简单的门锁,到密码锁,再到如今的指纹解锁,人脸识别锁,这都是锁的鲜明的例子,因此,咱们理解锁应该是很是简单的。java
再到MySQL中的锁,对于MySQL来讲,锁是一个很重要的特性,数据库的锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性,这样才能保证在高并发的状况下,访问数据库的时候,数据不会出现问题。mysql
在数据库中,lock和latch均可以称为锁,可是意义却不一样。面试
Latch通常称为闩锁
(轻量级的锁),由于其要求锁定的时间必须很是短。若持续的时间长,则应用的性能会很是差,在InnoDB引擎中,Latch又能够分为mutex
(互斥量)和rwlock
(读写锁)。其目的是用来保证并发线程操做临界资源的正确性,而且一般没有死锁检测的机制。算法
Lock的对象是事务
,用来锁定的是数据库中的对象,如表、页、行。而且通常lock的对象仅在事务commit或rollback后进行释放(不一样事务隔离级别释放的时间可能不一样)。sql
在数据库中,锁的粒度的不一样能够分为表锁、页锁、行锁,这些锁的粒度之间也是会发生升级的,锁升级的意思就是讲当前锁的粒度下降,数据库能够把一个表的1000个行锁升级为一个页锁,或者将页锁升级为表锁,下面分别介绍一下这三种锁的粒度(参考自博客:http://www.javashuo.com/article/p-kqibkcao-kb.html)。数据库
表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特色是实现逻辑很是简单,带来的系统负面影响最小。因此获取锁和释放锁的速度很快。因为表级锁一次会将整个表锁定,因此能够很好的避免困扰咱们的死锁问题。安全
固然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的几率也会最高,导致并大度大打折扣。微信
使用表级锁定的主要是MyISAM,MEMORY,CSV等一些非事务性存储引擎。markdown
特色: 开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的几率最高,并发度最低。
页级锁定是MySQL中比较独特的一种锁定级别,在其余数据库管理软件中也并非太常见。页级锁定的特色是锁定颗粒度介于行级锁定与表级锁之间,因此获取锁定所须要的资源开销,以及所能提供的并发处理能力也一样是介于上面两者之间。另外,页级锁定和行级锁定同样,会发生死锁。
在数据库实现资源锁定的过程当中,随着锁定资源颗粒度的减少,锁定相同数据量的数据所须要消耗的内存数量是愈来愈多的,实现算法也会愈来愈复杂。不过,随着锁定资源 颗粒度的减少,应用程序的访问请求遇到锁等待的可能性也会随之下降,系统总体并发度也随之提高。
使用页级锁定的主要是BerkeleyDB存储引擎。
特色: 开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度通常。
行级锁定最大的特色就是锁定对象的粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。因为锁定颗粒度很小,因此发生锁定资源争用的几率也最小,可以给予应用程序尽量大的并发处理能力而提升一些须要高并发应用系统的总体性能。
虽然可以在并发处理能力上面有较大的优点,可是行级锁定也所以带来了很多弊端。因为锁定资源的颗粒度很小,因此每次获取锁和释放锁须要作的事情也更多,带来的消耗天然也就更大了。此外,行级锁定也最容易发生死锁。
特色: 开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的几率最低,并发度也最高。
比较表锁咱们能够发现,这两种锁的特色基本都是相反的,而从锁的角度来讲,表级锁更适合于以查询为主,只有少许按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少许不一样数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。
InnoDB存储引擎中存在着不一样类型的锁,下面一一介绍一下。
S or X (共享锁、排他锁)
数据的操做其实只有两种,也就是读和写,而数据库在实现锁时,也会对这两种操做使用不一样的锁;InnoDB 实现了标准的行级锁,也就是共享锁(Shared Lock)和互斥锁(Exclusive Lock)。
IS or IX (共享、排他)意向锁
为了容许行锁和表锁共存,实现多粒度锁机制,InnoDB存储引擎支持一种额外的锁方式,就称为意向锁,意向锁在 InnoDB 中是表级锁,意向锁分为:
另外,这些锁之间的并非必定能够共存的,有些锁之间是不兼容的,所谓兼容性就是指事务 A 得到一个某行某种锁以后,事务 B 一样的在这个行上尝试获取某种锁,若是能当即获取,则称锁兼容,反之叫冲突。
下面咱们再看一下这两种锁的兼容性。
这里用一个思惟导图把前面的概念作一个小结。
在一个事务中查询数据时,普通的SELECT语句不会对查询的数据进行加锁,其余事务仍能够对查询的数据执行更新和删除操做。所以,InnoDB提供了两种类型的锁定读来保证额外的安全性:
SELECT ... LOCK IN SHARE MODE
SELECT ... FOR UPDATE
SELECT ... LOCK IN SHARE MODE
: 对读取的行添加S锁,其余事物能够对这些行添加S锁,若添加X锁,则会被阻塞。
SELECT ... FOR UPDATE
: 会对查询的行及相关联的索引记录加X锁,其余事务请求的S锁或X锁都会被阻塞。 当事务提交或回滚后,经过这两个语句添加的锁都会被释放。 注意:只有在自动提交被禁用时,SELECT FOR UPDATE才能够锁定行,若开启自动提交,则匹配的行不会被锁定。
一致性非锁定读(consistent nonlocking read) 是指InnoDB存储引擎经过多版本控制(MVVC)读取当前数据库中行数据的方式。若是读取的行正在执行DELETE或UPDATE操做,这时读取操做不会所以去等待行上锁的释放。相反地,InnoDB会去读取行的一个快照。因此,非锁定读机制大大提升了数据库的并发性。
一致性非锁定读是InnoDB默认的读取方式,即读取不会占用和等待行上的锁。在事务隔离级别READ COMMITTED
和REPEATABLE READ
下,InnoDB使用一致性非锁定读。
然而,对于快照数据的定义却不一样。在READ COMMITTED
事务隔离级别下,一致性非锁定读老是读取被锁定行的最新一份快照数据。而在REPEATABLE READ
事务隔离级别下,则读取事务开始时的行数据版本。
下面咱们经过一个简单的例子来讲明一下这两种方式的区别。
首先建立一张表;
插入一条数据;
insert into lock_test values(1);
查看隔离级别;
select @@tx_isolation;
下面分为两种事务进行操做。
在REPEATABLE READ
事务隔离级别下;
在REPEATABLE READ
事务隔离级别下,读取事务开始时的行数据,因此当会话B修改了数据以后,经过之前的查询,仍是能够查询到数据的。
在READ COMMITTED
事务隔离级别下;
在READ COMMITTED
事务隔离级别下,读取该行版本最新的一个快照数据,因此,因为B会话修改了数据,而且提交了事务,因此,A读取不到数据了。
InnoDB存储引擎有3种行锁的算法,其分别是:
Record Lock:老是会去锁住索引记录,若是InnoDB存储引擎表在创建的时候没有设置任何一个索引,那么这时InnoDB存储引擎会使用隐式的主键来进行锁定。
Next-Key Lock:结合了Gap Lock和Record Lock的一种锁定算法,在Next-Key Lock算法下,InnoDB对于行的查询都是采用这种锁定算法。举个例子10,20,30,那么该索引可能被Next-Key Locking的区间为:
除了Next-Key Locking,还有Previous-Key Locking技术,这种技术跟Next-Key Lock正好相反,锁定的区间是区间范围和前一个值。一样上述的值,使用Previous-Key Locking技术,那么可锁定的区间为:
不是全部索引都会加上Next-key Lock的,这里有一种特殊的状况,在查询的列是惟一索引(包含主键索引)的状况下,Next-key Lock
会降级为Record Lock
。
接下来,咱们来经过一个例子解释一下。
CREATE TABLE test ( x INT, y INT, PRIMARY KEY(x), // x是主键索引 KEY(y) // y是普通索引 ); INSERT INTO test select 3, 2; INSERT INTO test select 5, 3; INSERT INTO test select 7, 6; INSERT INTO test select 10, 8;
咱们如今会话A中执行以下语句;
SELECT * FROM test WHERE y = 3 FOR UPDATE
咱们分析一下这时候的加锁状况。
用户能够经过如下两种方式来显示的关闭Gap Lock:
Gap Lock的做用:是为了阻止多个事务将记录插入到同一个范围内,设计它的目的是用来解决Phontom Problem(幻读问题)。在MySQL默认的隔离级别(Repeatable Read)下,InnoDB就是使用它来解决幻读问题。
幻读:是指在同一事务下,连续执行两次一样的SQL语句可能致使不一样的结果,第二次的SQL可能会返回以前不存在的行,也就是第一次执行和第二次执行期间有其余事务往里插入了新的行。
脏读: 在不一样的事务下,当前事务能够读到另外事务未提交的数据。另外咱们须要注意的是默认的MySQL隔离级别是REPEATABLE READ
是不会发生脏读的,脏读发生的条件是须要事务的隔离级别为READ UNCOMMITTED
,因此若是出现脏读,可能就是这种隔离级别致使的。
下面咱们经过一个例子看一下。
从上面这个例子能够看出,当咱们的事务的隔离级别为READ UNCOMMITTED
的时候,在会话A尚未提交时,会话B就可以查询到会话A没有提交的数据。
不可重复读: 是指在一个事务内屡次读取同一集合的数据,可是屡次读到的数据是不同的,这就违反了数据库事务的一致性的原则。可是,这跟脏读仍是有区别的,脏读的数据是没有提交的,可是不可重复读的数据是已经提交的数据。
咱们经过下面的例子来看一下这种问题的发生。
从上面的例子能够看出,在A的一次会话中,因为会话B插入了数据,致使两次查询的结果不一致,因此就出现了不可重复读的问题。
咱们须要注意的是不可重复读读取的数据是已经提交的数据,事务的隔离级别为READ COMMITTED
,这种问题咱们是能够接受的。
若是咱们须要避免不可重复读的问题的发生,那么咱们可使用Next-Key Lock算法(设置事务的隔离级别为READ REPEATABLE
)来避免,在MySQL中,不可重复读问题就是Phantom Problem,也就是幻像问题。
丢失更新:指的是一个事务的更新操做会被另一个事务的更新操做所覆盖,从而致使数据的不一致。在当前数据库的任何隔离级别下都不会致使丢失更新问题,要出现这个问题,在多用户计算机系统环境下有可能出现这种问题。
如何避免丢失更新的问题呢,咱们只须要让事务的操做变成串行化,不要并行执行就能够。
咱们通常使用SELECT ... FOR UPDATE
语句,给操做加上一个排他X锁。
这里咱们作一个小结,主要是在不一样的事务的隔离级别下出现的问题的对照,这样就更加清晰了。
文章有不当之处,欢迎指正,若是喜欢微信阅读,你也可×××学java`,获取优质学习资源。