InnoDB事务模型和锁定

阅读前警⚠️告

阅读本文须要对MySQL事务,隔离级别有些概念上的了解,关于这些概念你还不够了解的话,请你当即中止阅读!接下来的内容会让你头疼欲裂安全

读写锁

在处理并发读或者写时,能够经过实现一个由两种类型的锁组成的锁系统来解决并发问题。这两种类型的锁一般被称为共享锁(shared lock)和排他锁(exclusive lock),也叫读锁(read lock)和写锁(write lock)服务器

这里先不讨论锁的具体实现,描述一下锁的概念以下:读锁是共享的,或者说是相互不阻塞的。多个客户在同一时刻能够同时读取同一个资源,而互不干扰。写锁则是排他的,也就是说一个写锁会阻塞其余的写锁和读锁,这是出于安全策略的考虑,只有这样,才能确保在给定的时间里,只有一个用户能执行写入,并防止其余用户读取正在写入的同一资源并发

简而言之,容许同时读,可是不容许边读边写,也不容许同时写性能

锁粒度

表锁3d

表锁是MySQL中最基本的锁策略,而且是开销最小的策略。 它会锁定整张表。一个用户在对表进行写操做(插入、删除、更新等)前,须要先得到写锁,这会阻塞其余用户对该表的全部读写操做。 只有没有写锁时,其余读取的用户才能得到读锁,读锁之间是不相互阻塞的cdn

在特定的场景中,表锁也可能有良好的性能。例如,READ LOCAL表锁支持某些类型的并发写操做blog

尽管存储引擎能够管理本身的锁,MySQL自己仍是会使用各类有效的表锁来实现不一样的目的。例如,服务器会为诸如ALTER TABLE之类的语句使用表锁,而忽略存储引擎的锁机制事务

行锁资源

行级锁能够最大程度地支持并发处理(同时也带来了最大的锁开销)。众所周知,在InnoDB和XtraDB,以及其余一些存储引擎中实现了行级锁。行级锁只在存储引擎层实现,而MySQL服务器层有实现it

说在前面,关于意向锁

提出意向锁概念以前,先试想一个场景。当一个事务1已经对表A的某一行加了读锁,此时事务2想在表A上加写锁,那么这种状况应该容许吗?

固然不该该容许,事务2在表A上加写锁,那么是否是意味着事务2能够对表A的任意行进行修改?那么事务1加的读锁又有什么用?因此这就矛盾了

最简单的解决方案是,事务2再加表锁以前对表A进行全表扫描,看一看有没有行锁,若是有的话,还要判断要加的表锁和行锁会不会冲突。这种方案真的夸张,加个锁有必要全表扫描一遍吗?

事实上InnoDB确定不会这么干的,这里新增了一种锁,叫作意向锁

意向锁

在InnoDB中,意图锁定是表锁定。它的用途就是说告诉其它的表锁,如今这个表中有行锁;若是是读锁,那么就会给表加意图共享锁;若是是写锁,就会给表加意图占有锁;

事先明确

  • 共享的(S)锁容许一个事务去读一行/表。
  • 独占的锁(X)容许一个事务更新或删除行/表。
  • 意图共享(IS):事务T 意图给表T上单独的tuple设置S 锁定。
  • 意图独占(IX):事务T 意图给这些tuple设置X 锁定。

意图锁协议以下:

  • 在假设的事务能够得到对某假定行的S 锁定以前,它必须首先得到对包含该行的表的一个IS 或者更强的锁定。
  • 在假设的事务能够得到对某假定行的X 锁定以前,它必须首先得到对包含该行的表的一个IX 锁定。

这些结果能够方便地用一个锁类型兼容矩阵来总结:

X IX S IS
X 冲突 冲突 冲突 冲突
IX 冲突 兼容 冲突 兼容
S 冲突 冲突 兼容 兼容
IS 冲突 兼容 兼容 兼容

这个表很明了了,假设给表加了IS,那么就不能给表加写锁(X),以前提的问题就迎刃而解了

隐式和显式锁定

在事务执行过程当中,随时均可以执行锁定,锁只有在执行COMMIT或者ROLLBACK的时候才会释放,而且全部的锁是在同一时刻被释放。诸如SELECT... UPDATE... DELETE... 的是隐式锁定,InnoDB会根据隔离级别在须要的时候自动加锁

另外,InnoDB也支持经过特定的语句进行显式锁定,这些语句不属于SQL规范

  • SELECT ... LOCK IN SHARE MODE,顾名思义,这个就是加共享锁
  • SELECT ... FOR UPDATE,加排它锁
多版本并发控制(MVCC)

InnoDB存储引擎实现了行锁,这确实提升了并发性能,但这只是提升了表层面的并发性能,若是单行数据被频繁访问,使用行锁的话,并发性能仍是不够理想。MySQL的大多数事务型存储引擎实现的都不是简单的行级锁。基于提高并发性能的考虑,它们通常都同时实现了多版本并发控制(MVCC)

关于MVCC的博文不少,再也不花时间啰嗦,本身搜去吧

若是你不了解MVCC,另外你对脏读,幻读,不可重复读的概念不理解的话,请你当即中止阅读!接下来的内容会让你产生头晕眼花,恶心干呕等不良反应

事务隔离级别

InnoDB实现了四种隔离级别:

  1. READ UNCOMMITTED(未提交读)

    这个级别中,SELECT语句以非锁定方式被执行。事务中的修改,即便没有提交,对其余事务也都是可见的。事务能够读取未提交的数据,这也被称为脏读(Dirty Read)。

  2. READ COMMITTED(提交读)

    一个事务从开始直到提交以前,所作的任何修改对其余事务都是不可见的。这个级别有时候也叫作不可重复读(nonrepeatable read),由于两次执行一样的查询,可能会获得不同的结果

  3. REPEATABLE READ(可重复读)

    可重复读隔离级别仍是没法解决另一个幻读(Phantom Read)的问题。InnoDB和XtraDB存储引擎经过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。

  4. SERIALIZABLE(可串行化)

    SERIALIZABLE是最高的隔离级别。它经过强制事务串行执行,避免了前面说的幻读的问题。简单来讲,SERIALIZABLE会在读取的每一行数据上都加锁,因此可能致使大量的超时和锁争用的问题。

各个隔离级别的实现

READ UNCOMMITTED(未提交读)的实现

SELECT语句以非锁定方式被执行

READ COMMITTED(提交读)的实现

这个隔离级别是经过MVCC实现的,事务每次读的时候,都会读刷新过的快照版本,也就是最新的数据。

REPEATABLE READ(可重复读)的实现

这个隔离级别也是经过MVCC实现的,事务每次读的时候,都会读快照版本,也就是旧的版本数据。

SERIALIZABLE(可串行化)的实现

这个级别相似REPEATABLE READ,可是全部无格式SELECT语句被 隐式转换成SELECT ... LOCK IN SHARE MODE,也就是默认的加共享锁!

如何避免脏读,幻读,不可重复读

InnoDB的默认隔离级别是RR(REPEATABLE READ),这个级别使用了MVCC+快照读,完美的解决了不可重复读和幻读的问题

虽然RC(READ COMMITTED)也是MVCC实现的,可是每次读的时候,都会刷新快照,因此会存在不可重复读和幻读的现象

PS:关于幻行,在RR级别,事务A虽然SELECT不到事务B的INSERT,可是能够经过UPDATE,DELETE感知到事务B事务B的INSERT,能够去实验一下

间隙锁和next-lock

上面说到了使用MVCC+快照读实际上是能够避免幻读的,不过还有一种状况没有考虑到

在InnoDB下,使用SELECT ... FOR UPDATE 和 SELECT ... LOCK IN SHARE MODE时,如何避免幻读?

单纯的使用行锁的话,是没法阻止幻读的(想一想为何);解决这个问题,蠢办法就是使用表锁;InnoDB设置者固然不会这么干,这里须要先了解间隙锁

关于间隙锁和next-lock。。。。百度去吧,累了

相关文章
相关标签/搜索