百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Mysql锁机制深度剖析

toyiye 2024-06-21 12:23 14 浏览 0 评论


目 录 1 innoDB锁简介 3

2 表锁 3

3 意向锁 5

4 自增锁 5

5 行锁 5

5.1 记录锁(record locks) 5

5.2 间歇锁(gap locks) 6

5.3 临键锁(next-key locks) 6

5.4 插入意向锁(insert intention locks) 7

5.5 行锁小结 7

6 innodb四种事务隔离级别 8

7 死锁 9

7.1 死锁例一 9

7.2 死锁例二 9

7.3 死锁例三 10

7.4 死锁例四 10

7.5 死锁例五 11

7.6 死锁小结 11

innoDB锁简介

innoDb支持多种粒度的锁,按照粒度来分,可分为表锁(LOCK_TABLE)和行锁(LOCK_REC)。

按照机制来分,可分为共享锁和排他锁,共享锁也叫读锁,排他锁也叫写锁。加在同一个资源上,写锁会阻塞另外一把写锁或读锁的获取,读锁则允许另外一把读锁的获取,也就是读读之间允许并发,读写或者写写会阻塞,innodb中表锁和行锁都支持共享锁(简写S)和排他锁(简写X)。

因为innoDB支持多粒度的锁,允许表锁和行锁的并存,为了方便多粒度锁冲突的判断,innoDB中还存在一种名叫意向锁(Intention Locks)的锁。

除此之外,还有一种特殊的表锁,自增锁,主要用来并发安全的生成自增id;一种特殊的意向锁,插入意向锁,用来防止幻读问题。

表锁

表锁,锁定的粒度是整个表,也分共享锁和排他锁。不同于行锁,表锁MySQL Server层就有实现(所以MyISAM支持表锁,也只支持表锁),innoDb则在存储引擎层面也实现了一遍表锁。 哪些时候会触发表锁呢?在执行某些ddl时,比如alter table等操作,会对整个表加锁,禁止dml,允许查询(注:mysql5.6之前版本,5.6之后为online ddl)。也可以手动执行锁表语句:LOCK TALBES table_name [READ | WRITE],READ为共享锁,WRITE为排他锁,手动解锁的语句为:UNLOCK TABLES,会直接释放当前会话持有的所有表锁。

5.6 online ddl推出以前,执行ddl主要有两种方式copy方式和inplace方式,inplace方式又称为(fast index creation)。相对于copy方式,inplace方式不拷贝数据,因此较快。但是这种方式仅支持添加、删除索引两种方式,而且与copy方式一样需要全程锁表,实用性不是很强。下面以加索引为例,简单介绍这两种方式的实现流程。

5.6之后 ddl分成copy和online ddl两种方式,对于不支持online的ddl操作采用copy方式,比如修改列类型,删除主键,修改字符集等,这些操作都会导致记录格式发生变化,无法通过简单的全量+增量的方式实现online;对于online ddl方式,mysql内部以“是否修改记录格式”为基准分为两类,一类需要重建表(重新组织记录),比如optimize table、添加索引、添加/删除列、修改列NULL/NOT NULL属性等;另外一类是只需要修改表的元数据,比如删除索引、修改列名、修改列默认值、修改列自增值等。Mysql将这两类方式称为rebuild方式和no-rebuild方式。online ddl主要包括3个阶段,prepare阶段,ddl执行阶段,commit阶段,rebuild方式比no-rebuild方式实质多了一个ddl执行阶段(允许读写),prepare阶段和commit阶段(禁止读写,但速度极快)。下面将主要介绍ddl执行过程中三个阶段的流程。

参考文档:https://www.cnblogs.com/cchust/p/4639397.html

意向锁

意向锁是一种特殊的表级锁,意向锁是为了让InnoDB多粒度的锁能共存而设计的。取得行的共享锁和排他锁之前需要先取得表的意向共享锁(IS)和意向排他锁(IX),意向共享锁和意向排他锁都是系统自动添加和自动释放的,整个过程无需人工干预。主要是用来辅助表级和行级锁的冲突判断,因为Innodb支持行级锁,如果没有意向锁,则判断表锁和行锁冲突的时候需要遍历表上所有行锁,有了意向锁,则只要判断表是否存在意向锁就可以知道是否有行锁了。

自增锁

自增锁是一种特殊的表级别锁,如果一个表的某个行具有AUTO_INCREMENT的列,则一个事务在插入记录到这个表的时候,会先获取自增锁。如果一个事务持有自增锁,会阻塞其他事物对该表的插入操作,保证自增连续。

行锁

Innodb中的行锁种类繁多,可以分为:记录锁(record locks)、间隙锁(gap locks)、临键锁(next-key locks),插入意向锁(insert intention locks)。行锁在逻辑上都可以看作作用于索引或者索引间隙之上,索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。

很多语句都会加行锁,比如Update、Delete、Insert等操作,或者使用SELECT ... FOR SHARE | UPDATE [NOWAIT |SKIP LOCKED]来进行当前读(Locking Reads),其中SHARE表示加共享锁,UPDATE表示加排他锁。当要加的锁与当前行已有锁互斥时,会一直阻塞等待一段时间(innodb_lock_wait_timeout定义了等待时间)。加上NOWAIT参数则不会阻塞,会立即返回,并显示一个错误,加上SKIP LOCKED则会在结果集中跳过这些冲突的记录(慎用)。

在不同的语句,不同的事务隔离级别下,甚至不同的索引类型下,行锁会表现成不同的形式,下面介绍这些形式:

记录锁(record locks)

在逻辑上,记录锁可以理解为锁定的是某个具体的索引,当SQL执行按照唯一性(Primary key、Unique key)索引进行数据的检索时,查询条件等值匹配且查询的数据是存在,这时 SQL 语句加上的锁即为记录锁

间歇锁(gap locks)

在逻辑上,间隙锁可以理解为锁住的是索引之间的间隙,是一个左开右开的区间。当SQL执行按照索引进行数据的检索时,查询条件的数据不存在,这时SQL语句加上的锁即为间隙锁

如上图,因为这些语句查询的值都不存在,所以锁住的都是间隙。并且在 InnoDb 存储引擎里,每个数据页中都会有两个虚拟的行记录,用来限定记录的边界,分别是:Infimum Record 和 Supremum Record,Infimum 是比该页中任何记录都要小的值,而 Supremum 比该页中最大的记录值还要大,这两条记录在创建页的时候就有了,并且不会删除。所以当查询的值比当前已有记录最大值还大时候,锁住的会是最大值到Supremum之间的间隙。比如第一条语句,查询的时候就算是等值匹配,只要这个不存在的数据落在两个索引节点之间,就算不是一个范围,也会锁住索引节点间的所有数据即gap3,范围(7,11)。

间隙锁是可以共存的,共享间隙锁与独占间隙锁之间是没有区别的,两者之间并不冲突。其存在的目的都是防止其他事务往间隙中插入新的纪录,故而一个事务所采取的间隙锁是不会去阻止另外一个事务在同一个间隙中加锁的。

间隙锁是设计用来防止幻读的,当锁定一个gap时,其他事务没有办法再往这个gap中插入数据,PostgreSQL没有这种机制,所以PostgreSQl没有办法锁住不存在的行,无法防止幻读。

临键锁(next-key locks)

在逻辑上,临键锁可以理解为锁住的是索引本身以及索引之前的间隙,是一个左开右闭的区间。当SQL执行按照非唯一索引进行数据的检索时,会给匹配到行上加上临键锁

如上图,当执行select * from table_name where id = 3 for update时会锁定(-∞,3 ]区间,因为按照这个SQL的语义,即是为了锁住id=3的数据,不允许其他操作,如果只是锁住记录本身,肯定是没有办法保证的,因为这是非唯一索引,还有可能插入其他id=3的数据,如果把间隙都给锁住,则其他对这个间隙的插入操作都会被阻塞,从而保证了一致性,这也是临键锁的用意。

如果加锁时,查询条件没有命中索引(非ICP的查询),则InnoDB会尝试给全表每一条记录都加上临键锁,效果相当于锁表了。

插入意向锁(insert intention locks)

插入意向锁是一种间隙锁形式的意向锁,在真正执行INSERT操作之前设置。当执行插入操作时,总会检查当前区间是否存在间隙锁或者临键锁,如果存在,则判定和插入意向锁冲突,当前插入操作就需要等待,也就是配合上面的间隙锁或者临键锁一起防止了幻读操作。

因为插入意向锁是一种意向锁,意向锁只是表示一种意象,所以插入意向锁之间不会互相冲突,多个插入操作同时插入同一个gap时,无需互相等待,比如当前索引上有记录4和8,两个并发session同时插入记录6,7。他们会分别为(4,8)加上插入意向锁,但相互之间并不冲突。

行锁小结

行锁在不同的语句中和环境条件下可以表现成:记录锁(record locks)、 间隙锁(gap locks)、临键锁(next-key locks)和插入意向锁(insert intention locks)。记录锁锁住具体的记录,间隙锁锁住记录之间的间隙,临键锁锁住记录和记录前面的间隙,插入意向锁则是特殊的间隙锁,在插入前判断行将要插入的间隙是否会有冲突。

以上说的各种行锁的加锁情况都是在可重复读(REPEATABLE READ)隔离级别下,这个级别可以防止幻读,也是innoDB默认的事务隔离级别,但是其实不同语句在不同隔离级别下加锁的情况会有非常大的区别,以下会简单说明。

innodb四种事务隔离级别

未提交读:会读到其他事务中未提交修改的数据

已提交读:只能读取到提交事务的数据,但不可重复读

可重复读:innodb默认级别,保证了在同一个事务内同一查询结果一致,并且可以防止幻读

串行读:完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

已提交读和可重复读在行锁方面主要的区别是已提交读取消了间隙锁,临键锁也退化成了记录锁。也就是说加锁时,如果没有符号条件的查询并不加锁,有符合条件的查询也只会给记录加上记录锁。因为没有了间隙锁,所以会出现幻读问题。在可重复读隔离级别下,加锁时如果查询条件没有命中索引(非ICP的查询),则会给表中每条记录都加上临键锁。而不可重复读隔离级别下因为没有间隙锁,则会退化成给表中每条数据加上记录锁,并且还会把没有匹配的行上的锁给释放掉,而不是把全表所有记录不管有没有匹配都给锁上

死锁

因为使用表锁时,需要一次性申请所有所需表的锁,所以在只使用表锁的情况下不会出现死锁,一般出现死锁的情况都是行锁。innoDB有死锁探测机制,在申请锁的时候,都会先进行死锁判断,采用的算法深度优先搜索,并且如果在搜索过程中发现有环,就说明发生了死锁。出现死锁时,innoDB会选择一个回滚代价比较小的事务进行回滚。以下会举几个比较典型的死锁例子(均在可重复度隔离级别下),首先会先建一张测试的表:

死锁例一

这是最简单最典型的死锁情况了,两个事务互相锁定持有资源,并且等待对方的资源,最后形成一个环,死锁出现。最后某个事务回滚,写业务代码的时候,应该对并发条件可能出现这种情况的语句有所警觉。

死锁例二

前提:事务开始时,student表里有id=1的记录

两个事务分别对某个记录申请共享锁,因为共享锁性质,两个事务都能获取到。然后又都对这条记录申请排他锁,T3中事务一申请排他锁,等待事务二的共享锁释放,加入锁等待队列,T4中事务二又申请排他锁,于是形成环,死锁条件达成。所以在事务开始时就要想到后面可能会做的操作,提前获取足够强度的锁,而不是中途升级。

死锁例三

前提:事务开始时,student表里没有id=100的记录

如上,在可重复读隔离级别下,如果两个事务同时对某个间隙用SELECT...FOR UPDATE加排他锁,在没有符合该条件记录情况下,两个线程都会加间歇锁成功。程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。因为在记录真正插入之前会加插入意向锁,插入意向锁和间隙锁互斥,所以在T3时,事务一阻塞申请插入意向锁排队等待事务二的间隙锁释放,T4时,事务二又申请插入意向锁,需要等待事务一的间隙锁释放,形成环,死锁条件达成。

死锁例四

前提:事务开始时,student表里没有uuid=uuid100的记录

注意这里插入意向锁之间不互斥,这种死锁的原因是INSERT的时候会对唯一索引进行Duplicate Key判断,如果唯一键冲突,则会加共享锁等待,也就是T3时候的事务二和事务三,都会获得共享锁。T4时,事务一回滚,事务二和事务三都会申请升级排他锁,这样就造成类似死锁案例二的情况,形成死锁了。

死锁例五

这个例子引用自淘宝数据库内核月报-InnoDB 事务锁系统简介,这个地方的死锁是因为事务一的加锁顺序是先锁二级索引name_index,再锁聚集索引,事务二的加锁顺序是,先锁聚集索引,再锁二级索引name_index,不同的加锁顺序在并发时可能导致死锁。

死锁小结

出现死锁后某个事务会回滚,其他事务成功,上层业务会捕获到死锁错误,再重试一般会成功,如果出现大量锁重试,则说明哪里出了问题,写代码的时候可以注意以下几点可以减少死锁出现的概率:

  1. 类似的业务逻辑尽量以固定的顺序访问表和行;
  2. 如果业务允许,大事务拆小,大事务持有锁的时间更长,更容易出现死锁
  3. 为表添加合理的索引,可以看到可重复读级别下,如果不走索引(非ICP的查询)将会为表的每一行记录加锁
  4. dml语句的where条件尽量用主键,因为间隙锁和临键锁的存在,锁住的可能不止是一行记录

5)禁止使用读锁(for share),因为先用for share锁住行,后面再update很容易死锁

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码