在我刚开始工作的时候,就听前辈说过这个概念,然而还是似懂非懂。这个概念也是躺在我的学习打算中,搁置到明天才筹备去彻底去学些这个概念。

我会列一个学习纲要,下面有我感兴趣的概念,每当关上Typora , 就会想写这个,又想写这个。想起高中期间比拟喜爱的一位武侠小说作者沧月,按她的说法是她的电脑外面也存了一些未实现的小说,而后每次关上电脑创作,这些小说就如同对她说: 写我,写我。本文次要讲述的是InnoDB中锁的相干概念,次要内容也是参考自掘金小册《MySQL 是怎么运行的:从根儿上了解 MySQL》,我次要是选取了本人想晓得的内容,又组合了一下。

倡议浏览本系列的文章的时候能够先看:

  • MySQL优化学习手札(一)
  • MySQL优化学习笔记手札(二)
  • MySQL优化学习手札(三)
  • MySQL事务学习笔记(一) 初遇篇
  • MySQL事务学习笔记(二) 相识篇

会出问题的状况

只是读的话,是不会呈现并发问题的,那咱们首先将这种状况疏忽,那么问题呈现的重灾区就是:

  • 写 - 写

即并发事务相继对一条记录进行改变,在这种状况就可能产生脏写。在任何隔离级别下MySQL都不容许脏写产生。所以在多个并发事务对雷同记录进行改变的时候,MySQL会让他们排队执行,这个排队执行事实上是通过锁来实现的,相似于当初的面试(不思考群面这种状况),个别面试官面试完这个人之后,才会叫HR让下集体进来。在MySQL中当一个事务要对某条事务进行改变的时候,首先会看内存中有没有与这条记录关联的锁构造,当没有的时候就会在内存中生成一个锁构造与之相关联。

  • 读 - 写 或 写 - 读

在这种状况下,可能产生脏读、不可反复读、幻读的问题。这里留神一下幻读问题的产生是因为某个事务读取了一个范畴的记录,之后其余事务又在该范畴内插入新记录,该事务再次读取该范畴的记录的时候,能够读到新插入的记录。所以幻读并不是因为读取和写入一条雷同的记录而产生。

在后面咱们曾经唠叨过在不同的隔离级别下可能会产生的问题了,这里再简略的提一下,不再过多的进行赘述,不同数据库厂商对SQL规范的反对可能都不一样,MySQL在 REPEATABLE READ隔离级别实际上就曾经解决了幻读问题。

解决方案在MySQL也就是两个:

  • MVCC: 读利用多版本并发管制(MVCC), 写操作进行加锁。
  • 读、写都采取加锁形式

在一些业务场景下,咱们不容许读取记录的旧版本,而是每次都必须去读取记录的最新版本。比方说在银行的贷款事务中,咱们就须要先读取账户余额,而后将其加上本次贷款的数额,最初再写入到数据库中。在将账户余额读取进去后,就不想让别的事务再拜访该余额。直到本次贷款事务执行实现,其余事务才能够拜访账户的余额。这样在读取记录的时候也就须要对其进行加锁操作。

为什么呢,咱们来剖析一下,咱们探讨的还是在可反复读这个级别上进行探讨,假如咱们以转账业务为例,如果容许读旧的账户余额会产生什么样的问题。

如果是像上面的语句来执行:

 UPDATE Student Set money = money + 50 where id = '1';

两次提交并不会产生问题,因为两个事务排队执行。 我原先的想法是在执行UPDATE的时候MySQL也读了,这样读到事务提交之前的记录,最终就会是转了两次账,只加了五十块钱。那如果是如果是做运算呢,比如说咱们在代码外面做运算,而后最初的更新语句像上面这样就会有问题:

 UPDATE Student Set money = 60 where id = '1';

后提交的事务就会把先提交的事务笼罩掉,这并不算脏写,只是两次独立的更新操作。

采纳MVCC形式的话,读-写操作并不抵触,性能更好,采纳加锁形式的话,须要排队执行,影响性能。个别状况下咱们更违心采取MVCC来解决事务并发执行带来的问题,然而业务在某些状况下,不能承受MVCC,在MySQL外面仿佛也只剩下了加锁这一个选项。

事务利用MVCC进行的读取操作称之为一致性读,或者一致性无锁读、快照读。所有的SELECT语句在READ COMMITTED、REPEATABLE READ隔离级别都算是一致性读。

锁概念浅析

行共享锁和独占锁

在MySQL中锁能够大抵分为两类:

  • 共享锁 英文名 Shared Locks,简称为S锁。
如果一个事务某个记录上有共享锁, 如果另一个事务也想获取这把锁,也是能够的,这就意味着两个事务在该记录上能够同时持有该锁。
  • 独占锁 也常称 排他锁,英文名: Exclusive Locks , 简称为S锁,
如果事务T1曾经获取了该记录的S锁,事务T2想要再获取一条记录的X锁,那么此操作就会被阻塞,直到T1开释掉S锁。

MySQL提供了两种语法来让咱们在读取记录的时候就能够获取该记录的X锁、S锁:

SELECT ... LOCK IN SHARE MODE; 在读取记录的时候获取该记录的共享锁SELECT ... FOR UPDATE;在该事务中获取该记录的X锁

如果以后事务执行了获取共享锁的语句(SELECT ... LOCK IN SHARE MODE),那么它会为读取到的记录加S锁,这样容许别的事务持续获取这些记录的S锁(如果其余事务也应用 SELECT ... LOCK IN SHARE MODE;来读取这些记录),然而不能获取这些记录的X锁(比方说应用SELECT ... FOR UPDATE来读取这些记录,或者批改这些记录 ) 如果别的事务想要获取这些记录的X锁,那么它们会阻塞,直到以后事务提交之后将这些记录尚的S锁开释掉。

如果事务中执行了SELECT ... FOR UPDATE, 那么它会为读取到的记录加X锁,别的事务即无奈获取这个记录的S锁和X锁,如果事务想要获取这些记录的S锁和X锁,那么它们会阻塞,直到以后事务提交之后将这些记录上的X锁开释掉。

表共享锁和独占锁

下面咱们提到的能够认为是行锁,也就是针对若干行进行加锁,这个锁的粒度是比拟细的。其实一个事务也能够在表级别进行加锁,天然就被称之为表级锁或者表锁,对表加锁影响整个表中的记录,给表加的锁也能够被分为共享锁(S锁)和独占锁(X锁):

如果一个事务给表加了S锁:

  • 其余事务能够持续获取得该表的S锁
  • 其余事务能够持续获取该表中记录的S锁
  • 别的事务无奈获取该表的X锁
  • 如果该行记录下面有S锁,那么其余事务无奈取得该行记录的X锁

如果一个事务给表加了X锁(独占这个表):

  • 其余事务无奈取得该表的S锁
  • 如果该表中的记录上有X锁,那么其余事务无奈取得该记录的S锁。
  • 如果该表中的记录上有S锁,那么其余事务无奈取得该记录的X锁
  • 其余事务不能够持续取得该表中的某些记录的X锁。

但这事实上有两个问题,如果MySQl想对表整体上S锁,首先须要确保表中的记录不能有X锁,遍历表的记录? MySQL的开发人员设计了意向锁:

  • 动向共享锁 Intention Shared Lock, 简称为IS锁。当事务筹备在某条记录尚加S锁时,须要先在表加上IS锁。
  • 动向独占锁, Intention Exclusive Lock,简称为IX锁。当事务筹备在某条记录上加X锁,须要先在表级别加一个IX锁。

这样就能够防止全表扫表。

表锁概述

在对某个表执行一些ALTER TABLE、DROP TABLE这里的DDL语句时,其余事务对这个表并发执行增删改查会产生阻塞,同理,某个事务对某个表执行SELECT、INSERT、DELETE、UPDATE语句时,其余会话中执行DDL语句也会产生阻塞。这个过程其实是通过Server层应用一种元数据锁(Metadata Locks ,简称为MDL)来实现的,个别状况下也不会应用InnoDB存储引擎提供的表级别S锁、X锁。所以这个表级的S、X锁颇有种鸡肋的感觉。咱们重点关注行锁。下面咱们唠叨的意向锁也属于表锁。再有就是MySQL的自增属性,在MySQL有两种形式实现自增:

  • 采纳AUTO-INC锁,也就是在执行插入语句的时候就在表上加上一个AUTO-INC锁,而后为每条自增属性的赋值。一个事务持有AUTO—INC锁,其余插入事务会被阻塞。插入语句执行之后就被开释。

不确定插入的记录数量可采取AUTO-INC锁。

  • 轻量级锁,在为插入语句生成自增属性列的值之后,就把该轻量级锁开释掉,并不需要等到整个插入语句执行实现才开释锁。

    如果语句执行前就能够确定插入记录数量,个别采纳轻量级锁的形式对自增列赋值。

那么该如何抉择自增形式呢,InnoDB中有一个innodb_autoinc_lock_mode的零碎变量来管制哪两种形式来为自增列赋值,为0时,一律采纳AUTO-INC锁,为2时,一律采纳轻量级锁(可能会造成不同事务中的插入语句的自增列是穿插的,在有主从复制的场景是不平安的)。当为1时,插入记录数量确定时,采取轻量级锁,不确定时采取AUTO-INC锁。

总结

MySQL锁的概念比我原想的要简单,知也无涯,生也有涯哉,原本打算这一节将MySQL中的锁大抵过一遍,然而到当初还是没有梳理出一条主线将这些内容连在一起。到最初就是只介绍了行锁、表锁的基本概念。

参考资料

  • 《MySQL 是怎么运行的:从根儿上了解 MySQL》 掘金小册