InnoDB之锁

这里说的锁是指事务级别的对行记录/表进行加/解锁。
事务的开始--加锁,事务的提交/回滚--解锁。
和我们通常说的多线程对共享资源的锁是不一样的。

1. 锁的类型

1. 行级锁

我们知道InnoDB支持行级锁。即以下两种:

  • 共享锁(S Lock):允许事务读取某一行的数据
  • 排他锁(X Lock):允许事务写某一行的数据


    image.png

S锁和S锁是可以兼容的,S和X、X和X是不兼容的。

2. 表级锁

InnoDB也有表级锁,当遇到如下SQL就会加表级锁

ALTER TABLE, DROP TABLE, LOCK TABLES

LOCK TABLE my_tabl_name READ; 用读锁锁表,会阻塞其他事务修改表数据。
LOCK TABLE my_table_name WRITE; 用写锁锁表,会阻塞其他事务读和写。

即表级锁也有S锁和X锁。

意向锁

InnoDB中有一种表级锁叫意向锁。
InnoDB有一个这样的规定:在加行级锁之前自动会加意向锁。

  • 意向共享锁(IS Lock):事务“想要”读取某几行数据。
  • 意向排他锁(IX Lock):事务“想要”写某几行数据。

即在加S Lock时,会先加IS Lock。在加X Lock时,会先加IX Lock。
意向锁只是表达一个意愿,表达后面我将要做什么。

那意向锁的作用是什么呢?

考虑场景:我们使用LOCK TABLE语句希望对表A加读锁,这时我们应该先要判断是否有其他事务对表A进行写。

  • 没有意向锁
    做法是遍历表A的所有行级锁,看是否有X锁。
    显然这样的方式太耗时。
  • 有了意向锁
    做法判断一下表A是否有IX就可以了。就可以判断表A此时有没有在写的事务。

总结一下意向锁的作用:就是为了提高锁定表级数据的效率。

所以我们再来看一下意向锁和表锁之间的兼容性:


image.png
  • 意向锁之间都是兼容的
  • 表中的S和X都指的是表级锁。意向锁和行级锁不存在兼容性问题,都是兼容的。

2. 一致性非锁定读

读取数据时,如果读取的行已被加X锁,则无需等待,读取的是该行的快照版本。这种技术称为多版本控制(MVVC)。


image.png
  • 快照版本可以有多个,是由undo段来实现,而undo段用于回滚,所以快照版本无需额外的开销。
  • 读取快照版本不会加任何锁,因为没有事务会对历史版本进行修改。

不同事务隔离下不同的行为

READ COMMITTED
  • 使用的是一致性非锁定读。
  • 快照版本是该行的最新一条快照版本。
REPEATABLE READ(默认级别)
  • 使用的是一致性非锁定读。
  • 快照版本是事务开始时的版本。

这个行为也决定了RR为什么解决了不可重复读的问题。

解释一下上面两个快照版本的区别

事务A:读行r | --------------- | 读行r
事务B:--------| 写r commit |

主要是看事务A第二次读行r的结果

  • READ COMMITTED:读的是事务B提交后的快照
  • REPEATABLE READ:读的是事务A开始时读的快照。

3. 一致性锁定读

意思就是用户可以显示地指定读操作一定要加锁。

select ...... for update :这条select语句加X锁
select ......lock in share mode :这条select语句加S锁

4. 自增长与锁

自增长一般我们都会用,如何保证并发下插入的自增长都是1?

  • InnoDB使用一种AUTO-INC Locking,它是一个表锁。
    不同的是:这个锁的释放是在插入语句执行结束后就释放,而不是事务结束。所以这个效率就会还不错了。
    所以并不是全部的锁都是在事务结束后才释放。
  • 自增长的列一定要加索引且是索引的第一列(如果索引是组合索引),否则MySQL会报错。

5. 行锁的3种算法

Record Lock、Gap Lock和Next-Key Lock

  • Record Lock:单个行记录上的锁。
  • Gap Lock:间隙锁,锁定一个范围,不包含记录本身
  • Next-Key Lock:Record Lock+Gap Lock,锁定一个范围且包含记录本身。
    以上说的锁都是锁的索引。如果没有索引InnoDB会自动建一个rowId索引。

举个例子:
表A只有一个字段id,id为主键。此时表中有四行记录1, 3, 5, 7
对于一个查询语句而言:

Begin
select * from A where id > 2 for update;--加for update的目的是生成X锁。
--注意此时事务并没有提交
  • Record Lock:会锁住这个sql语句扫描到的符合条件的所有索引。即3,5,7会被锁住,即此时其他事务无法对id=3、5、7的行进行查询或修改。但可以插入id为4、6、8等行记录。
  • Gap Lock:首先会根据所有的索引值生成Gap: (-无穷,1),(1, 3),(3,5),(5,7),(7,+无穷)。然后会锁住根据sql语句扫描到的符合条件的所有gap,不包含索引值本身。即此时其他事务可以修改记录3、5、7但不可以插入4、6、8等。
  • Next-Key Lock:也是会先生成Gap:(-无穷,1],(1, 3],(3,5],(5,7],(7,+无穷)。注意这里的Gap是左开右闭的,即包含了扫描到的索引本身。所以此时会锁住所有大于1的范围,即其他事务无法写入/修改和读取所有大于1的记录。

Next-Key Lock

Next-Key Lock是InnoDB在默认隔离级别(REPEATABLE READ)下对于行查询默认的算法。设计的目的为了解决幻读问题。

幻读

幻读:相同两个SQL查询,由于两次SQL查询期间有其他事务的操作,导致执行的结果不同。

  • 幻读:第二次读和第一次读比较:多了一些行或少了一些行(其他事务插入/删除了这些行记录)
  • 不可重复读:第二次读和第一次读比较:一些行的值不一样(其他事务修改了这些行记录)

从上面我们可以看到Next-Key Lock解决幻读的具体过程。其实是for update+Next-Key Lock才可以解决幻读现象。
这里我们总结一下原因:

  • for update:决定了读必须加锁
  • Next-Key Lock:决定了加的锁是Gap+Record Lock。
  • 查询SQL的条件:决定了Gap Lock的范围。
降级

Next-Key Lock在一些情况下可以降级为Record Lock。

Begin
select * from A where id = 3 for update;
select * from A where id in (1,3) for update;

当查询的字段是唯一索引,且搜索的条件也是唯一的时,此时Next-Key Lock就会降级为Record Lock。原因是根据这个条件查询,不会出现因为其他事务insert导致同一个事务内的两次查询,记录不一样。

  • REPEATABLE READ:使用next-key lock来解决这个问题,即锁住的是一个范围。
  • READ COMMITTED:使用Record Lock,所以无法避免幻读现象。
一个死锁问题-蛮有意思的
image.png
  • 在时间点4时,会话A需要等待会话B的S锁结束,所以这里会阻塞。
  • 时间点5时,会话A需要等待会话B释放S锁,会话B需要等待会话A释放S锁,这样就形成了循环等待,造成了死锁。

InnoDB会检测出这种死锁条件,直接抛出异常。

6. 锁的问题

1. 脏读

脏读是指一个事务中读到了其他事务未提交的数据。
一般不会出现,除非隔离级别设为:READ UNCOMMITTED,其他隔离级别都不会出现。

2. 丢失更新

比如说有一个这样的逻辑:

[1] select `account` from a where `name` = 'kobe';
....
[2] update a 
set `account` = '[1]查询结果 + 100' where `name` = `kobe`;
  • 首先查询kobe的账户余额,然后根据账户余额去更新这个账户。
  • 出现的问题:在[1]和[2]之间其他事务对kobe的账户做了改动,在执行[2]时,其实kobe的账户并不是[1]查到的结果了。
  • 比如:kobe账户总共1万,在执行[1]时查到1万。此时其他事务将账户减去9千,还剩一千,然后执行[2],认为账户余额还是1万,则把账户设为1万+1百=1万1了。
  • 解决办法:使用一致性锁定读,即在[1]读的时候加X锁,则其他事务就无法进行修改和读取,这样[2]使用的余额就是数据库中最新的值。
[1] select `account` from a where `name` = 'kobe' for update;
....
[2] update a 
set `account` = '[1]查询结果 + 100' where `name` = `kobe`;

7. 数据库隔离级别与读问题

image.png

1. 解释一下

这个是数据库系统的规范,即任何具体的数据库都必须满足的特性。只是不同的数据库有自己的对这种规范的具体实现方式。

2. Lost updates

  • 即一个事务修改行r,另一个事务也可以修改行r,这样后一个事务相当于把前一个事务修改的数据覆盖掉了。即前一个事务修改的数据“Lost”。
  • 所有的数据库隔离级别都不会发生这种事,即行r被一个事务修改的时候,另一个事务是不可以修改的。
  • 上节说的“丢失更新”是业务级别上的,并不是数据库事务级别上的。

3. Dirty reads

  • 这个上面已经说过了,RC和RR都不会发生。RC通过名字就可以看出来,read已经committed的数据。
  • 在InnoDB中,RC由于一致性非限定读,读的是最新的快照版本,所有读的一定是已经committed的数据。

4. Non-repeatable reads

  • RR不会发生,通过名字就可以看出来,repeatable read。
  • 在InnoDB中,RR由于一致性非限定读,读的是事务开始的快照版本,所以是repeatable read的。
  • 所以跟具体使用的哪种锁(Next-Key Lock)没有太大关系。其实个人感觉Next-Key Lock并没有太大意义,可重复读是靠MVVC解决的,而它只是在特定情况下能够解决幻读问题,这个特定情况又是那么特定。

5. Phantoms

  • 由图可以看出,只有Serializable才不会出现。
  • 在InnoDB中,从上面我们可以看到RR的Next-Key Lock说可以解决幻读现象,那与这里是否矛盾呢?
  • 其实并不矛盾,因为上面说RR的Next-Key Lock+for update才可以解决幻读现象,只是单纯的Next-Key Lock并不能解决幻读现象。

8. 阻塞

当一个事务等待另一个事务commit时,我们称为阻塞。
默认情况下,当一个事务阻塞到一定时间时(默认50s),会抛出异常。注意此时此事务并没有把之前的操作进行commit或者roll back,此时处于一种非常危险的状态。所以一定要设置当事务超时时,是commit还是roll back。

9. 死锁

回忆下死锁的知识:

数据库中的死锁是指:多个事务竞争同一资源锁,而出现互相等待,若无外力则无法进行下去。

解决死锁的办法
  • 事务超时机制
    当出现死锁时,事务阻塞时间超过某个值,则让这个事务roll back释放资源,这样其他事务就可以继续进行下去。
    缺点:roll back的事务永远是先阻塞的事务,如果先阻塞的事务的undo太大,则代价很大。简单地说就是无法控制应该让哪个事务roll back。
  • wait for graph
    维护一张有向图,节点就是事务,节点之间的连线说明事务A需要等待事务B释放资源。类似于下图:


    image.png

    当图中出现回路时,则说明有死锁。图中的t1和t2就是一个回路。
    当任意一个事务申请锁时,都回去更新这张图,如果存在回路则挑undo量最小的事务进行roll back。

死锁的示例

死锁的场景还是很简单的,就是A等待B,B等待A。就会出现死锁。下面这个例子很简单,看一看。


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容