前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【MySQL源码分析】浅谈Mysql的锁

【MySQL源码分析】浅谈Mysql的锁

作者头像
桶哥
发布2019-06-04 18:24:11
2.1K0
发布2019-06-04 18:24:11
举报
文章被收录于专栏:PHP饭米粒PHP饭米粒

什么是锁

锁是计算机协调多个进程或线程并发访问某一资源的机制。

Mysql锁

  • 行锁 开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
  • 页锁 开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
  • 表锁 开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。

事务

事务是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。

事务的四个特性条件

  • 原子性:一组事务,要么全部成功;要么撤回。
  • 一致性 :满足模式锁指定的约束,比如银行转账前后总金额应该不变。事务结束时,所有的内部数据结构(如B树索引)也都必须是正确的。
  • 隔离性:事务独立运行。一个事务所做的修改在最终提交之前,对其它事务是不可见的。事务的100%隔离,需要牺牲速度。
  • 持久性:软、硬件崩溃后,InnoDB数据表驱动会利用日志文件重构修改,或者通过数据库备份和恢复来保证。

在默认情况下,MySQL每执行一条SQL语句,都是一个单独的事务。

事务并发的问题

  • 脏读(Dirty Reads):一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做"脏读"。
  • 不可重复读(Non-Repeatable Reads):一个事务读取某些数据,在它结束读取之前,另一个事务可能完成了对数据行的更改。当第一个事务试图再次执行同一个查询,服务器就会返回不同的结果。
  • 幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

事务隔离级别

事务隔离级别

脏读

不可重复读

幻读

读未提交(read-uncommitted)

不可重复读(read-committed)

可重复读(repeatable-read)

串行化(serializable)

mysql默认的事务隔离级别为repeatable-read

  • 读未提交 :事务可以读取到其他事务未提交的数据,此时若A事务读取到B事务未提交的修改,后B回滚就会产生脏读。
  • 不可重复读:事务只能读取到其他事务提交的数据,不会产生脏读,但若事务B提交在A的两次查询间就会产生不可重复读。
  • 可重复读:可重复读的隔离级别下使用了MVCC机制,A事务中读取的是记录的快照版本,而非最新版本,B事务的更新是创建了一个新版本来更新,不同事务的读和写是分离的
  • 串行化:mysql中事务隔离级别为serializable时会锁表,因此不会出现幻读的情况,这种隔离级别并发性极低。

事务加锁方式

  • 一次性锁协议,事务开始时,即一次性申请所有的锁,之后不会再申请任何锁,如果其中某个锁不可用,则整个申请就不成功,事务就不会执行,在事务尾端,一次性释放所有的锁。一次性锁协议不会产生死锁的问题,但事务的并发度不高。
  • 两阶段锁协议,整个事务分为两个阶段,前一个阶段为加锁,后一个阶段为解锁。在加锁阶段,事务只能加锁,也可以操作数据,但不能解锁,直到事务释放第一个锁,就进入解锁阶段,此过程中事务只能解锁,也可以操作数据,不能再加锁。两阶段锁协议使得事务具有较高的并发度,因为解锁不必发生在事务结尾。它的不足是没有解决死锁的问题,因为它在加锁阶段没有顺序要求。如两个事务分别申请了A, B锁,接着又申请对方的锁,此时进入死锁状态。

Innodb的事务隔离

在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。

MySQL/InnoDB定义的4种隔离级别:

  • Read Uncommited

可以读取未提交记录

  • Read Committed (RC) 当前读操作RC隔离级别保证对读取到的记录加锁 (记录锁),存在幻读现象。使用MVCC,但读取数据时读取自身版本和最新版本,以最新为主,可以读已提交记录,存在不可重复读现象。
  • Repeatable Read (RR) 当前读操作RR隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),不存在幻读现象。使用MVCC保存两个事物操作的数据互相隔离,不存在不可重复读现象。
  • Serializable 从MVCC并发控制退化为基于锁的并发控制。不区别快照读与当前读,所有的读操作均为当前读,读加读锁 (S锁),写加写锁 (X锁)。

Serializable隔离级别下,读写冲突,因此并发度急剧下降,在MySQL/InnoDB下不建议使用。

InnoDB的MVCC多版本并发控制

MVCC是一种多版本并发控制机制。锁机制可以控制并发操作,但是其系统开销较大,而MVCC可以在大多数情况下代替行级锁,使用MVCC,能降低其系统开销。

MVCC是通过保存数据在某个时间点的快照来实现的。 不同存储引擎的MVCC、不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制。

InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列,分别保存了这个行的创建时间,一个保存的是行的删除时间。这里存储的并不是实际的时间值,而是系统版本号(可以理解为事务的ID),没开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的ID。IN

  • INSERT InnoDB为新插入的每一行保存当前系统版本号作为版本号.
  • UPDATE InnoDB执行UPDATE,实际上是新插入了一行记录,并保存其创建时间为当前事务的ID,同时保存当前事务ID到要UPDATE的行的删除时间。
  • DELETE InnoDB会为删除的每一行保存当前系统的版本号(事务的ID)作为删除标识
  • SELECT InnoDB会根据以下两个条件检查每行记录,需要同时满足以下两个条件:
    • InnoDB只会查找版本早于当前事务版本的数据行(也就是行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的.
    • 行的删除版本要么未定义要么大于当前事务版本号,这可以确保事务读取到的行,在事务开始之前未被删除。

注意:

在SELECT时,只满足上述两个条件也是不能达到快照读的要求的比如在RR的隔离级别下会有如下情况:

启动1号事务、启动2号事务、1号事务更新x行并提交事务(此时x行的修改版本号为1,删除版本号为未定义)、2号事务读取x行

按照如上步骤,如果只满足上述两个条件的话

显然2号事务时可以读取到1号事务所做的更新

(x行修改版本号为1满足小于2删除版本号为未定义满足事务开始之前未删除),显然是不足够满足快照读的要求

事实上,在读取到满足上述两个条件的行时,InnoDB还会进行二次检查,如上图所示

活跃事务列表:RC隔离级别下,在语句开始时从全局事务表中获取活跃(未提交)事务构造Read View,RR隔离级别下,在事务开始时从全局事务表中获取活跃事务构造Read View

  • 1.取当前行的修改事务ID,和Read View中的事务ID做比较,若小于最小的ID或小于最大ID但不在列表中,转2步骤,若大于最大ID,转3步骤
  • 2.满足进入此步骤的条件,即可说明,最后更新当前行的事务,在构造Read View时已经提交,则返回当前行的数据
  • 3.满足进入此步骤的条件,即可说明,最后更新当前行的事务,在构造Read View时还未创建或者还未提交,则取undo log中的记录的事务ID,重新进入步骤1,重复此操作

至此,通过上述步骤,可以实现真正的快照读。

上述策略的结果就是,在读取数据的时候,InnoDB几乎不用获得任何锁,每个查询都通过版本检查,只获得自己需要的数据版本,从而大大提高了系统的并发度。 这种策略的缺点是,每行记录都需要额外的存储空间,更多的行检查工作和一些额外的维护工作。

上述更新前建立undo log,根据各种策略读取时非阻塞就是MVCC,undo log中的行就是MVCC中的多版本,这个可能与我们所理解的MVCC有较大的出入,一般我们认为MVCC有下面几个特点:

  • 每行数据都存在一个版本,每次数据更新时都更新该版本
  • 修改时Copy出当前版本随意修改,各个事务之间无干扰
  • 保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)

就是每行都有版本号,保存时根据版本号决定是否成功,听起来含有乐观锁的味道,而Innodb的实现方式是:

  • 事务以排他锁的形式修改原始数据
  • 把修改前的数据存放于undo log,通过回滚指针与主数据关联
  • 修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)

二者最本质的区别是,当修改数据时是否要排他锁定,如果锁定了还算不算是MVCC?

Innodb的实现真算不上MVCC,因为并没有实现核心的多版本共存,undo log中的内容只是串行化的结果,记录了多个事务的过程,不属于多版本共存。但理想的MVCC是难以实现的,当事务仅修改一行记录使用理想的MVCC模式是没有问题的,可以通过比较版本号进行回滚;但当事务影响到多行数据时,理想的MVCC据无能为力了。

比如,如果Transaciton1执行理想的MVCC,修改Row1成功,而修改Row2失败,此时需要回滚Row1,但因为Row1没有被锁定,其数据可能又被Transaction2所修改,如果此时回滚Row1的内容,则会破坏Transaction2的修改结果,导致Transaction2违反ACID。

理想MVCC难以实现的根本原因在于企图通过乐观锁代替二段提交。修改两行数据,但为了保证其一致性,与修改两个分布式系统中的数据并无区别,而二提交是目前这种场景保证一致性的唯一手段。二段提交的本质是锁定,乐观锁的本质是消除锁定,二者矛盾,故理想的MVCC难以真正在实际中被应用,Innodb只是借了MVCC这个名字,提供了读的非阻塞而已。

Innodb事务锁

锁模式

  • 共享锁(S):又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
  • 排他锁(X):又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。

  • 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
  • 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

意向锁仅仅用于表锁和行锁的共存使用。如果我们的操作仅仅涉及行锁,那么意向锁不会对我们的操作产生任何影响。在任一操作给表A的一行记录加锁前,首先要给该表加意向锁,如果获得了意向锁,然后才会加行锁,并在加行锁时判断是否冲突。如果现在有一个操作要获得表A的表锁,由于意向锁的存在,表锁获取会失败(如果没有意向锁的存在,加表锁之前可能要遍历整个聚簇索引,判断是否有行锁存在,如果没有行锁才能加表锁)。   

同理,如果某一操作已经获得了表A的表锁,那么另一操作获得行锁之前,首先会检查是否可以获得意向锁,并在获得意向锁失败后,等待表锁操作的完成。也就是说:1.意向锁是表级锁,但是却表示事务正在读或写某一行记录;2.意向锁之间不会冲突, 因为意向锁仅仅代表要对某行记录进行操作,在加行锁时,会判断是否冲突;3.意向锁是InnoDB自动加的,不需用户干预。

锁类型

  • 间隙锁(Gap Lock),只锁间隙。表现为锁住一个区间(注意这里的区间都是开区间,也就是不包括边界值)。
  • 记录锁(Record Lock),只锁记录。表现为仅仅锁着单独的一行记录。
  • Next-Key锁(源码中称为Ordinary Lock),同时锁住记录和间隙。从实现的角度为record lock+gap lock,而且两种锁有可能只成功一个,所以next-key是半开半闭区间,且是下界开,上界闭。一张表中的next-key锁包括:(负无穷大,最小的第一条记录],(记录之间],(最大的一条记录,正无穷大)。
  • 插入意图锁(Insert Intention Lock),插入操作时使用的锁。在代码中,插入意图锁实际上是Gap锁上加了一个LOCK_INSERT_INTENTION的标记。也就是说insert语句会对插入的行加一个X记录锁,但是在插入这个行的过程之前,会设置一个Insert intention的Gap锁,叫做Insert intention锁。
  • 乐观锁 : 总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
  • 悲观锁: 总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁。

一致性非锁定读

InnoDB使用MVCC来实现一致性非锁定读,在read-committed和repeatable-read两种事务隔离级别下使用,且效果不同,具体如下。

read-committed

在读已提交的隔离级别下,事务在一致性非锁定读始终读取当前最新的数据快照,即当其他事务提交更新后快照更新也会读取最新的,也就是出现不可重复读。

repeatable-read

在可重复读的隔离级别下,事务始终读取事务开始时的快照版本。

一致性锁定读

一致性锁定读有两种实现方式,一种是加X锁,一种是加S锁

select ... for update 显示的使用加X锁的方式读取

select ... lock in share mode 显示的使用加S锁的方式读取

自增长与锁

innodb_autoinc_lock_mode有3种配置模式:0、1、2,

  • 0:涉及auto-increment列的插入语句加的表级AUTO-INC锁,只有插入执行结束后才会释放锁,即事务在进行插入时获取自增长值时先加锁,后插入,插入完释放
  • 1:对于可以事先确定插入行数的语句(包括单行和多行插入),使用互斥量操作自增值,分配连续的确定的auto-increment值,对于插入行数不确定的插入语句仍使用表级AUTO-INC锁。这种模式下,事务回滚,auto-increment值不会回滚,换句话说,自增列内容会不连续。
  • 2:对于所有的插入操作使用互斥量操作自增值,来一个插入分配一个auto-increment值,此时一个批量插入的自增长值就可能不连续,且在sql语句级的主从同步可能会出现问题

锁升级

InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

InnoDB目前处理死锁的方法是:将持有最少行级排它锁的事务回滚。如果是因为死锁引起的回滚,可以考虑在应用程序中重新执行。

在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁,更新时再申请排他锁,因为当用户申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁。

死锁

通常来说,死锁都是应用设计的问题,通过调整业务流程、数据库对象设计、事务大小,以及访问数据库的SQL语句,绝大部分死锁都可以避免。介绍几种避免死锁的常用方法。

  1. 在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。
  2. 在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能。比如两个会话读取前十个用户的信息,每次读取一个,那么我们可以规定他们从第一个用户开始读,而不是倒序,这样不会死锁。
  3. 在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁,更新时再申请排他锁,因为当用户申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁。
  4. 选择合理的事务大小,小事务发生锁冲突的几率也更小;   

如果出现死锁,可以用SHOW INNODB STATUS命令来确定最后一个死锁产生的原因。返回结果中包括死锁相关事务的详细信息,如引发死锁的SQL语句,事务已经获得的锁,正在等待什么锁,以及被回滚的事务等。

死锁的发生与否,并不在于事务中有多少条SQL语句,死锁的关键在于:两个(或以上)的Session加锁的顺序不一致。而使用本文上面提到的,分析MySQL每条SQL语句的加锁规则,分析出每条语句的加锁顺序,然后检查多个并发SQL间是否存在以相反的顺序加锁的情况,就可以分析出各种潜在的死锁情况,也可以分析出线上死锁发生的原因。

问题:

按索引项来加锁的话,不同索引相同行,会不会同时获得不同的锁却操作同一行

  • 聚簇索引也会加锁,也就是主键会加锁,这样就防止并发修改了

自增锁,是语句级的锁,如果当前事务先获取锁,却后执行完,在从库按语句复制的话,会不会出现ID不一致

InnoDB的锁实现

代码语言:javascript
复制
lock0types.h   事务锁系统的类型定义,包含了 lock_mode定义

lock0priv.ic   锁模块内部的一些方法,被用于除了lock0lock.cc的三个文件里,

lock_get_type_low 获取锁是表锁还是行锁

lock_clust_rec_some_has_impl 检查一行数据上是否有隐示的x锁
lock_rec_get_n_bits 获取一个记录锁的锁位图的bit数目
lock_rec_set_nth_bit 设置第n个记录锁bit位为true
lock_rec_get_next_on_page 获取当前page上的下一个记录锁
lock_rec_get_next_on_page_const 
lock_rec_get_first_on_page_addr 获取当前page上第一个记录锁
lock_rec_get_first_on_page
lock_rec_get_next  返回当前记录上的下一个显示锁请求的锁
lock_rec_get_next_const
lock_rec_get_first 获取当前记录上的第一个显示锁请求的锁
lock_rec_get_nth_bit    Gets the nth bit of a record lock.
lock_get_mode  获取一个锁的 lock_mode
lock_mode_compatible  判断两个lock_mode是否兼容
lock_mode_stronger_or_eq 判断lock_mode 1 是否比 lock_mode 2更强
lock_get_wait 判断一个锁是不是等待锁
lock_rec_find_similar_on_page  查找一个合适的锁结构在当前事务当前页面下???找到的话就不用新创建锁结构???
lock_table_has  检查一个事务是否有指定类型的表锁,只能由当前事务调用

lock0priv.h  锁模块内部的结构和方法

struct lock_table_t   表锁结构
struct lock_rec_t   行锁结构
struct lock_t 锁通用结构
static const byte lock_compatibility_matrix[5][5]  锁的兼容关系
static const byte lock_strength_matrix[5][5] 锁的强弱关系
enum lock_rec_req_status 记录锁请求状态
struct RecID  记录锁ID
class RecLock   记录锁类
add_to_waitq   入队一个锁等待
create   为事务创建一个锁并初始化
is_on_row  Check of the lock is on m_rec_id.
lock_alloc 创建锁实例
prepare 做一些检查个预处理为创建一个记录锁
mark_trx_for_rollback 收集需要异步回滚的事务
jump_queue   跳过所有低优先级事务并添加锁,如果能授予锁,则授予,不能的话把其他都标记异步回滚
lock_add   添加一个记录锁到事务锁列表和锁hash表中
deadlock_check 检查并解决死锁
check_deadlock_result  检查死锁检查的结果
is_predicate_lock  返回时不是predictate锁
init 按照要求设置上下文
lock_get_type_low 返回行锁还是表锁
lock_rec_get_prev 获取一个记录上的前一个锁 

锁类型

在 Innodb 内部用一个 unsiged long 类型数据表示锁的类型, 最低的 4 个 bit 表示 lock_mode, 5-8 bit 表示 lock_type, 剩下的高位 bit 表示行锁的类型。

  • lock_type

5-8 bit 位标识 lock_type 目前只使用了两个,第5位标识是表锁,第6位标识是行锁

代码语言:javascript
复制
#define LOCK_TABLE  16  /*!< table lock */ //表锁
#define LOCK_REC    32  /*!< record lock */ //记录锁
  • lock_mode

lock描述了锁的基本模式,目前有5种模式,IS、IX、S、X、AI

代码语言:javascript
复制
enum lock_mode {
    LOCK_IS = 0,    /* intention shared */
    LOCK_IX,    /* intention exclusive */
    LOCK_S,     /* shared */
    LOCK_X,     /* exclusive */
    LOCK_AUTO_INC,  /* locks the auto-inc counter of a table
            in an exclusive mode */
    LOCK_NONE,  /* this is used elsewhere to note consistent read */
    LOCK_NUM = LOCK_NONE, /* number of lock modes */
    LOCK_NONE_UNSET = 255
};

以下是锁的基本模式的兼容关系和强弱关系

代码语言:javascript
复制
/* LOCK COMPATIBILITY MATRIX
 *    IS IX S  X  AI
 * IS +  +  +  -  +
 * IX +  +  -  -  +
 * S  +  -  +  -  -
 * X  -  -  -  -  -
 * AI +  +  -  -  -
 *
 * Note that for rows, InnoDB only acquires S or X locks.
 * For tables, InnoDB normally acquires IS or IX locks.
 * S or X table locks are only acquired for LOCK TABLES.
 * Auto-increment (AI) locks are needed because of
 * statement-level MySQL binlog.
 * See also lock_mode_compatible().
 */
static const byte lock_compatibility_matrix[5][5] = {
 /**         IS     IX       S     X       AI */
 /* IS */ {  TRUE,  TRUE,  TRUE,  FALSE,  TRUE},
 /* IX */ {  TRUE,  TRUE,  FALSE, FALSE,  TRUE},
 /* S  */ {  TRUE,  FALSE, TRUE,  FALSE,  FALSE},
 /* X  */ {  FALSE, FALSE, FALSE, FALSE,  FALSE},
 /* AI */ {  TRUE,  TRUE,  FALSE, FALSE,  FALSE}
};

/* STRONGER-OR-EQUAL RELATION (mode1=row, mode2=column)
 *    IS IX S  X  AI
 * IS +  -  -  -  -
 * IX +  +  -  -  -
 * S  +  -  +  -  -
 * X  +  +  +  +  +
 * AI -  -  -  -  +
 * See lock_mode_stronger_or_eq().
 */
static const byte lock_strength_matrix[5][5] = {
 /**         IS     IX       S     X       AI */
 /* IS */ {  TRUE,  FALSE, FALSE,  FALSE, FALSE},
 /* IX */ {  TRUE,  TRUE,  FALSE, FALSE,  FALSE},
 /* S  */ {  TRUE,  FALSE, TRUE,  FALSE,  FALSE},
 /* X  */ {  TRUE,  TRUE,  TRUE,  TRUE,   TRUE},
 /* AI */ {  FALSE, FALSE, FALSE, FALSE,  TRUE}
};
  • record_lock_type

剩下的高位标识行锁的模式,对于表锁这些位都是空的

目前record_lock_type有以下值

代码语言:javascript
复制
#define LOCK_WAIT   256 /*!< Waiting lock flag; when set, it  //锁等待
                means that the lock has not yet been
                granted, it is just waiting for its
                turn in the wait queue */
/* Precise modes */
#define LOCK_ORDINARY   0   /*!< this flag denotes an ordinary
                next-key lock in contrast to LOCK_GAP
                or LOCK_REC_NOT_GAP */
#define LOCK_GAP    512 /*!< when this bit is set, it means that the
                lock holds only on the gap before the record;
                for instance, an x-lock on the gap does not
                give permission to modify the record on which
                the bit is set; locks of this type are created
                when records are removed from the index chain
                of records */
#define LOCK_REC_NOT_GAP 1024   /*!< this bit means that the lock is only on
                the index record and does NOT block inserts
                to the gap before the index record; this is
                used in the case when we retrieve a record
                with a unique key, and is also used in
                locking plain SELECTs (not part of UPDATE
                or DELETE) when the user has set the READ
                COMMITTED isolation level */
#define LOCK_INSERT_INTENTION 2048 /*!< this bit is set when we place a waiting
                gap type record lock request in order to let
                an insert of an index record to wait until
                there are no conflicting locks by other
                transactions on the gap; note that this flag
                remains set when the waiting lock is granted,
                or if the lock is inherited to a neighboring
                record */
#define LOCK_PREDICATE  8192    /*!< Predicate lock */
#define LOCK_PRDT_PAGE  16384   /*!< Page lock */

锁结构

  • lock_sys

首先是锁系统结构,在Innodb启动的时候初始化,在Innodb结束的时候释放

主要保存着锁的hash表,以及相关事务、线程的一些信息

代码语言:javascript
复制
/** The lock system struct */
struct lock_sys_t{
    char        pad1[CACHE_LINE_SIZE];  /*!< padding to prevent other
                        memory update hotspots from
                        residing on the same memory
                        cache line */
    LockMutex   mutex;          /*!< Mutex protecting the
                        locks */
    hash_table_t*   rec_hash;       /*!< hash table of the record
                        locks */
    hash_table_t*   prdt_hash;      /*!< hash table of the predicate
                        lock */
    hash_table_t*   prdt_page_hash;     /*!< hash table of the page
                        lock */
    char        pad2[CACHE_LINE_SIZE];  /*!< Padding */
    LockMutex   wait_mutex;     /*!< Mutex protecting the
                        next two fields */
    srv_slot_t* waiting_threads;    /*!< Array of user threads
                        suspended while waiting for
                        locks within InnoDB, protected
                        by the lock_sys->wait_mutex */
    srv_slot_t* last_slot;      /*!< highest slot ever used
                        in the waiting_threads array,
                        protected by
                        lock_sys->wait_mutex */
    ibool       rollback_complete;
                        /*!< TRUE if rollback of all
                        recovered transactions is
                        complete. Protected by
                        lock_sys->mutex */
    ulint       n_lock_max_wait_time;   /*!< Max wait time */
    os_event_t  timeout_event;      /*!< Set to the event that is
                        created in the lock wait monitor
                        thread. A value of 0 means the
                        thread is not active */
    bool        timeout_thread_active;  /*!< True if the timeout thread
                        is running */
};

lock_sys_create .   Creates the lock system at database start.
lock_sys_close     Closes the lock system at database shutdown
  • lock_t

无论是行锁还是表锁都使用lock_t结构保存,其中用一个union来分别保存行锁和表锁不同的数据,分别为lock_table_t和lock_rec_t

代码语言:javascript
复制
struct lock_t {
    trx_t*      trx;        /*!< transaction owning the lock */
    UT_LIST_NODE_T(lock_t)
            trx_locks;  /*!< list of the locks of the transaction */

    dict_index_t*   index;      /*!< index for a record lock */

    lock_t*     hash;       /*!< hash chain node for a record lock. The link node in a singly linked list, used during hashing. */

    union { //表锁和记录锁不同的数据
        lock_table_t    tab_lock;/*!< table lock */   //表锁结构体
        lock_rec_t  rec_lock;/*!< record lock */  //记录锁结构体
    } un_member;            /*!< lock details */ 

    ib_uint32_t type_mode;  /*!< lock type, mode, LOCK_GAP or LOCK_REC_NOT_GAP, LOCK_INSERT_INTENTION, wait flag, ORed */  //锁类型

    /** Remove GAP lock from a next Key Lock */
    void remove_gap_lock()   //移除一个next-key锁的gap锁
    {
        ut_ad(!is_gap());
        ut_ad(!is_insert_intention());
        ut_ad(is_record_lock());
        type_mode |= LOCK_REC_NOT_GAP;
    }

    /** Determine if the lock object is a record lock.
    @return true if record lock, false otherwise. */
    bool is_record_lock() const   //判断是否是记录锁
    {
        return(type() == LOCK_REC);
    }

    /** Determine if it is predicate lock.
    @return true if predicate lock, false otherwise. */
    bool is_predicate() const
    {
        return(type_mode & (LOCK_PREDICATE | LOCK_PRDT_PAGE));
    }

    bool is_waiting() const
    {
        return(type_mode & LOCK_WAIT);
    }

    bool is_gap() const
    {
        return(type_mode & LOCK_GAP);
    }

    bool is_record_not_gap() const
    {
        return(type_mode & LOCK_REC_NOT_GAP);
    }

    bool is_insert_intention() const
    {
        return(type_mode & LOCK_INSERT_INTENTION);
    }

    ulint type() const {
        return(type_mode & LOCK_TYPE_MASK);
    }

    enum lock_mode mode() const
    {
        return(static_cast<enum lock_mode>(type_mode & LOCK_MODE_MASK));
    }

    /** Get lock hash table
    @return lock hash table */
    hash_table_t* hash_table() const
    {
        return(lock_hash_get(type_mode));
    }

    /** Get tablespace ID for the lock
    @return space ID */
    ulint space() const
    {
        return(un_member.rec_lock.space);
    }

    /** Get page number of the lock
    @return page number */
    ulint page_number() const
    {
        return(un_member.rec_lock.page_no);
    }

    /** Print the lock object into the given output stream.
    @param[in,out]  out the output stream
    @return the given output stream. */
    std::ostream& print(std::ostream& out) const;

    /** Convert the member 'type_mode' into a human readable string.
    @return human readable string */
    std::string type_mode_string() const;

    const char* type_string() const
    {
        switch (type_mode & LOCK_TYPE_MASK) {
        case LOCK_REC:
            return("LOCK_REC");
        case LOCK_TABLE:
            return("LOCK_TABLE");
        default:
            ut_error;
        }
    }
};
 
 
/** A table lock */
struct lock_table_t {
    dict_table_t*   table;      /*!< database table in dictionary
                    cache */
    UT_LIST_NODE_T(lock_t)
            locks;      /*!< list of locks on the same
                    table */
    /** Print the table lock into the given output stream
    @param[in,out]  out the output stream
    @return the given output stream. */
    std::ostream& print(std::ostream& out) const;
};


 
 
/** Record lock for a page */
struct lock_rec_t {
    ib_uint32_t space;      /*!< space id */
    ib_uint32_t page_no;    /*!< page number */
    ib_uint32_t n_bits;     /*!< number of bits in the lock
                    bitmap; NOTE: the lock bitmap is
                    placed immediately after the
                    lock struct */

    /** Print the record lock into the given output stream
    @param[in,out]  out the output stream
    @return the given output stream. */
    std::ostream& print(std::ostream& out) const;
};
  • bitmap

Innodb 使用位图来表示锁具体锁住了那几行,在函数 lock_rec_create 中为 lock_t 分配内存空间的时候,会在对象地址后分配一段内存空间(当前行数 + 64)用来保存位图。n_bits 表示位图大小。

代码语言:javascript
复制
/* Make lock bitmap bigger by a safety margin */
n_bits = page_dir_get_n_heap(page) + LOCK_PAGE_BITMAP_MARGIN;
n_bytes = 1 + n_bits / 8;

lock = static_cast<lock_t*>(
    mem_heap_alloc(trx->lock.lock_heap, sizeof(lock_t) + n_bytes));

显示锁和隐示锁

explicit lock 显示锁 implicit lock 隐示锁

InnoDB增加隐示锁的目的是在INSERT的时候不加锁

具体实现为

  • 1.在insert的时候,不进行加锁
  • 2.在当前读访问到一行的时候,判断是否有隐示锁且事务是活跃事务,有的话先转为显示锁

锁流程

lock system 开始启动 申请lock_sys_t结构,初始化结构体

lock system 结束关闭 释放lock_sys_t结构的元素,释放结构体

插入加锁流程

https://www.colabug.com/32979...

问题:为什么有GAP也能插入(有GAP是不能插入的),插入意向锁什么时候加(插入之前尝试加插入意向锁,冲突加等待,不冲突直接插数据), 有什么用,唯一键冲突如何处理的(需要检测冲突会先尝试给行加S|next-key lock,加成功再检测)

和加锁有关的流程大概如下

  1. 对表加IX锁
  2. 对修改的页面加X锁
  3. 如果需要检测唯一键冲突,尝试给需要加的唯一键列加一个S|next-key lock锁,可能会产生锁等待
  4. 判断是否插入意向锁冲突,冲突加等待的插入意向锁,不冲突直接插入数据
  5. 释放页面锁

删除加锁流程

删除加锁有个重要的问题是,删除并发的时候的加锁会有以下死锁问题

  1. 事务1获取表IX锁
  2. 事务1获取页面X锁
  3. 事务1获取第n行的x|not gap 锁
  4. 事务1删除第n行
  5. 事务1释放页面X锁
  6. 事务2获取页面X锁
  7. 事务2尝试获取第n行的x|not gap锁,发现冲突,等待
  8. 事务2释放页面X锁
  9. 事务1释放第n行的锁,提交事务
  10. 释放第n行锁的时候,检查到事务2有一个等待锁,发现可以加锁了,唤醒事务2,成功加锁
  11. 事务3获取页面X锁
  12. 事务3尝试删除第n行,发现第n行被删除(注意,此时记录还在还没被从页面刷出),尝试获取第n行的next-key lock,发现事务2有一个x|gap锁冲突,等待
  13. 事务3释放页面X锁
  14. 事务2获取页面X锁,检查页面是否改动,重新检查第n行数据,发现第n行数据被删除,尝试获取第n行的next-key lock,发现有事务3已经在等待这个锁了,事务2冲突,进入等待
  15. 死锁

表锁加锁流程

lock_table

  1. 检查当前事务是否拥有更强的表锁,有的话直接返回成功,否则继续往下走
  2. 遍历表的锁列表,判断是否有冲突的锁,如果没有转3,有转4
  3. 直接创建一个表锁,放入事务的lock list中,放入table的 lock list中,加锁成功
  4. 如3步骤,创建等待的表锁,加入list,然后进行死锁检测和死锁解决,回滚当前事务或者挂起当前事务

行锁加锁流程

  • lock_rec_lock

主要的参数是 mode(锁类型),block(包含该行的 buffer 数据页),heap_no(具体哪一行)。就可以确定加什么样的锁,以及在哪一行加。

加锁流程主要是lock fast和lock slow,首先进入lock fast进行快速加锁,如果快速加锁失败则进入lock slow开始正常加锁流程,可能有锁冲突检查、死锁检查等流程

lock fast

  1. 获取需要加锁的页面上第一个record lock
  2. 判断获取的锁是不是空,是转3,否转4
  3. 如果需要加的是隐示锁直接返回成功,否则,创建一个创建一个RecLock对象然后创建一个锁返回成功
  4. 判断当前页面上是否只有一个锁,且这个锁是当前事务的,且这个锁模式和需要加的模式一样,且bitmap的大小够用,满足前述条件转5,否则转6
  5. 可以快速加锁,直接设置bitmap进行加锁,返回成功
  6. 快速加锁失败,返回失败并进入lock slow流程

lock slow

  1. 调用lock_rec_has_expl函数判断当前事务是不是有更强的锁,满足转2不满足转3 lock_rec_has_expl函数遍历rec_hash,获取事务编号是当前事务的锁,同时满足以下五个条件就判定为有更强的锁
    1. 不是一个插入意向锁
    2. 不是一个等待中的锁
    3. 根据强弱关系矩阵判断满足更强
    4. 不是lock_rec_not_gap类型,或者要加的锁是lock_rec_not_gap类型或者heap_no是上界
    5. 不是lock_gap类型,或者要加的锁是lock_gap类型或者heap_no是上界

2.有更强的锁,直接返回成功,什么都不需要做

3.如果没有更强的锁,调用lock_rec_other_has_conflicting判断是否有锁冲突需要等待,如果有转4,没有转5。lock_rec_other_has_conflicting函数遍历rec_hash,拿出对应行上的每一个锁,调用 lock_rec_has_to_wait 进行冲突判断

1)如果当前锁和要加的锁是同一个事务的,直接返回,没有冲突

2)根据兼容矩阵判断当前锁和要加的锁是否兼容,如果兼容,直接返回,没有冲突

3)如果要加的是lock_gap或者heap_no是页面上界,且不是lock_insert_intention的话,可以直接返回,没有冲突,因为非插入意向锁的gap锁是不用等待的,都不冲突

4)如果要加的锁不是插入意向锁lock_insert_intention,且当前锁是一个gap锁,直接返回,没有冲突

5)如果要加的锁是gap锁,且当前锁是lock_rec_not_gap锁,直接返回,没有冲突

6)如果当前锁是一个插入意向锁,直接返回没有冲突

7)不满足上述条件,返回冲突

代码语言:javascript
复制
ps:为什么经过2步骤判断锁不兼容还需要往下走5个判断,是因为锁类型lock_mode/lock_type/rec_lock_type三种标记位同时有,如lock_x|lock_gap, lock_s|lock_rec_not_gap 这两个锁虽然lock_mode不兼容,但不冲突
  • 4.调用add_to_waitq,入队一个锁等待
    1. 调用creat,创建lock_t,高优先级事务不放入rec_hash表,非高优先级放入
    2. 如果是高优先级事务,调用jump_queue,如果加锁成功直接返回,jump_queue大概为跳过所有优先级比当前锁低的等待锁,加入等待队列中
    3. 调用deadlock_check进行死锁检测
  • 5.判断是否是隐示锁,是的话直接返回成功,什么都不做,不是的话调用lock_rec_add_to_queue入队一个锁
    1. type_mode|=lock_rec,判断heap_no是否是页面上届,是的话,type_mode不能是lock_rec_not_gap
    2. 遍历rec_hash判断当前行上是否有等待的锁,没有转3,有转4
    3. 如果没有,且当前锁不是一个lock_wait,寻找当前页面上有没有相似的锁(当前事务的锁且锁类型和要加的锁一样),有的话直接设置标记位,没有转4
    4. 创建一个锁lock_t,设置bit位,设置lock_type等信息,添加到rec_hash表中和事务的lock_list中

释放锁流程

lock_rec_dequeue_from_page

  1. 把当前锁从全局hash表中删除
  2. 把当前锁从事务锁list中删除
  3. 调用lock_rec_grant函数,尝试给等待锁加锁 1) 历锁hash表中当前页面上的锁,对于每个等待锁,调用lock_rec_has_to_wait_in_queue函数判断是否还需要等待

lock_rec_has_to_wait_in_queue

  1. 遍历当前锁所在页面的所有非等待锁
  2. 对于每个锁,根据heap_no判断当前锁需要加锁的行是否被锁上
  3. 如果被锁上,判断要加的锁是否需要等待

对于不需要等待的锁,调用lock_grant进行加锁

  1. 移除lock的lock_wait状态
  2. 置空lock的wait_lock
  3. 调用que_thr_end_lock_wait和lock_wait_release_thread_if_suspended唤醒等待中的线程

lock_table_dequeue

  1. 调用lock_table_remove_low
    1. 如果是自增长锁,将锁从事务的autoinc_locks list中移除
    2. 从事务锁列表将锁移除
    3. 从表的锁列表将锁移除
  2. 遍历当前表的表锁列表,判断等待的锁是否能加锁,能的话调用lock_grant函数进行加锁

死锁流程

构造wait-for graph

构造一个有向图,图中的节点代表一个事务,图的一个边A->B代表着A事务等待B事务的一个锁

具体实现是在死锁检测时,从当前锁的事务开始搜索,遍历当前行的所有锁,判断当前事务是否需要等待现有锁释放,是的话,代表有一条边,进行一次入栈操作

死锁检测

有向图判断环,用栈的方式,如果有依赖等待,进行入栈,如果当前事务所有依赖的事务遍历完毕,进行一次出栈

回滚事务选择

如果发现循环等待,选择当前事务和等待的事务其中权重小的一个回滚,具体的权重比较函数是 trx_weight_ge, 如果一个事务修改了不支持事务的表,那么认为它的权重较高,否则认为 undo log 数加持有的锁数之和较大的权重较高。

DeadlockChecker::search()

  1. 将当前要加的等待锁设置为wait_lock,start_lock
  2. 遍历wait_lock锁所在位置上的所有锁
  3. 判断wait_lock锁是否需要等待遍历到的锁lock,不需要等待就跳过,
  4. 如果需要等待,将lock push进栈,同时将lock设置为wait_lock,搜索深度+1,重复上述过程
  5. 如果要等待,判断lock和start_lock是否是一个事务,是的话,发生死锁,选择当前事务和start_lock的事务权重小的回滚
  6. 如果要等待,判断搜索深度是否已经过深,超过阈值,认为发生死锁,直接回滚了start_lock的事务

等待与唤醒 锁的等待以及唤醒实际上是线程的等待和唤醒,调用函数 lock_wait_suspend_thread 挂起当前线程,配合 OS_EVENT 机制,实现唤醒和锁超时等功能

锁分裂、合并、迁移

分裂

索引页面分裂导致的锁分裂

  1. 普通锁,会跟随着记录位置的移动,锁一起移动到新的位置,加锁信息保持不变
  2. gap锁,会给新页面和老页面的上下界最大最小值上根据情况调整gap锁,目的保持gap锁上的区间保持不变

合并

索引页面合并导致的锁合并

  • 合并和分裂基本一致

迁移

插入和删除记录时的GAP锁的迁移

  • 在有GAP锁的间隙里插入记录时会出现GAP锁的迁移

主要是出现在当前事务拥有一个记录的GAP锁,又在这个记录前插入记录时

代码语言:javascript
复制
set global tx_isolation='repeatable-read';

create table t1(c1 int primary key, c2 int unique) engine=innodb;
insert into t1 values(1,1);
begin;
# supremum 记录上加 LOCK_X|LOCK_GAP 锁住(1~)
select * from t1 where c2=2 for update;
# 发现插入(3,3)的间隙存在GAP锁,因此给(3,3)加LOCK_X | LOCK_GAP锁。这样依然锁住了(1~)
insert into t1 values(3,3);
代码语言:javascript
复制
    for (lock = lock_rec_get_first(lock_sys->rec_hash, block, heap_no);
         lock != NULL;
         lock = lock_rec_get_next(heap_no, lock)) {  //遍历当前行的所有锁

        if (!lock_rec_get_insert_intention(lock)
            && (heap_no == PAGE_HEAP_NO_SUPREMUM
            || !lock_rec_get_rec_not_gap(lock))) {  //如果不是插入意向锁 且 heap是上界或者不是一个非GAP锁

            lock_rec_add_to_queue(  //添加一个GAP的且mode和lock一致的锁到下一行
                LOCK_REC | LOCK_GAP | lock_get_mode(lock),
                block, heir_heap_no, lock->index,
                lock->trx, FALSE);
        }
    }

如有记录 1 3 5

  1. 事务A对记录3加GAP锁,阻止1-3的间隙插入
  2. 事务B对记录3加X锁,GAP锁和X锁不冲突,加锁成功,事务B删除记录3,提交
  3. 此时当后台线程刷盘时,发现记录3已经删除,将从此页面将3记录删除,但发现3上还有个GAP锁,就会把这个GAP锁继承给这个记录后面的记录5

Innodb在RR和RC隔离下的加锁实例分析

例子:select * from meng_hinata where id = 10 for update

组合一:id列是主键,RC隔离级别

在主键id=10列加上X锁

组合二:id列是二级唯一索引,RC隔离级别

在唯一索引id=10列上加X锁,在主键索引上对应列加X锁

组合三:id列是二级非唯一索引,RC隔离级别

在二级索引上所有id=10列加上X锁,这些列对应的主键索引列加上X锁

组合四:id列上没有索引,RC隔离级别

在聚簇索引上扫描,所有列上加X锁,此处有个优化,不满足的列在加锁后,判断不满足即可释放锁,违背二阶段加锁

组合五:id列是主键,RR隔离级别

在主键id=10列上加X锁

组合六:id列是二级唯一索引,RR隔离级别

在唯一索引id=10列上加X锁,在主键索引上对应列加X锁

组合七:id列是二级非唯一索引,RR隔离级别

在二级索引上查找id=10列,找到则加上X锁和GAP锁,然后对应的聚簇索引列加上X锁,最后一个不满足的列只会加上GAP锁

组合八:id列上没有索引,RR隔离级别

在聚簇索引上扫描,所有列加上X锁和GAP锁

测试

Innodb默认事务隔离级别为RR

看出默认隔离级别为Repeatable Read,对user_id = 100000745进行count并显示加一个X锁,使用explain看出使用了uid索引即user_id字段的索引

id = 1449731912的数据user_id = 100000745,可以看出在对user_id字段索引加了X锁之后,操纵相对应的主键索引时也会被阻塞,验证了对非主键索引加X锁的同时会对相应主键索引也加锁

同时在对user_id字段索引加了X锁之后,也不能插入user_id相同的新数据,验证了innodb再RR隔离级别下也是防止了幻读的现象,实际上当范围查找时也会再加一个间隙锁来保证不会有幻读

可以看出不使用for update之后变为非当前读,也没有进行加锁,可以进行插入操作。

以上两个图设置隔离级别为RC后,可以看出在user_id = 100000745 进行for update查询后,还是能进行插入相同user_id值的列,说明只加了X锁并没有加间隙锁,同时因为X锁的原因,不能进行删除,验证了innodb引擎在RC隔离级别对于当前度也是会对读到的信息加锁,但没加间隙锁,会出现幻读。

以上两个图,在RC隔离级别下。

首先事务A对无索引的列进行查找并加锁,会扫描全表,注意并没有加表锁,而是对所有行都加了X锁,但是没加间隙锁,事务B还是可以插入。

此时事务A再扫描全表并加X锁,发现被阻塞,提交事务B后继续运行

事务A对全表加X锁后,事务B再次尝试插入,同样成功,但无法删除数据,说明还是没用表锁,对所有行加了X锁。

InnoDB锁同步机制

InnoDB条件变量核心数据结构为os_event_t,类似pthread_cont_t。如果需要创建和销毁则分别使用os_event_create和os_event_free函数。需要等待某个条件变量,先调用os_event_reset,然后使用os_event_wait,如果需要超时等待,使用os_event_wait_time替换os_event_wait即可

InnoDB自旋互斥锁的实现主要在文件 sync0sync.cc 和sync0sync.ic 中,头文件sync0sync.h 定义了核心数据结构ib_mutex_t。使用方法很简单,mutex_create创建锁,mutex_free释放锁,mutex_enter尝试获得锁,如果已经被占用了,则等待。mutex_exit释放锁,同时唤醒所有等待的线程,拿到锁的线程开始执行,其余线程继续等待。

InnoDB读写锁的核心实现在源文件sync0rw.cc 和sync0rw.ic 中,核心数据结构rw_lock_t 定义在sync0rw.h 中。使用方法与InnoDB 自旋互斥锁很类似,只不过读请求和写请求要调用不同的函数。加读锁调用rw_lock_s_lock, 加写锁调用rw_lock_x_lock,释放读锁调用rw_lock_s_unlock, 释放写锁调用rw_lock_x_unlock,创建读写锁调用rw_lock_create,释放读写锁调用rw_lock_free。函数rw_lock_x_lock_nowait和rw_lock_s_lock_nowait表示,当加读写锁失败的时候,直接返回,而不是自旋等待。

----------伟大的分割线-----------

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-04-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PHP饭米粒 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是锁
    • Mysql锁
    • 事务
      • 事务的四个特性条件
        • 事务并发的问题
          • 事务隔离级别
            • 事务加锁方式
            • Innodb的事务隔离
            • InnoDB的MVCC多版本并发控制
            • Innodb事务锁
              • 锁模式
                • 锁类型
                  • 一致性非锁定读
                    • 一致性锁定读
                      • 自增长与锁
                        • 锁升级
                          • 死锁
                          • InnoDB的锁实现
                            • 锁类型
                              • 锁结构
                                • 显示锁和隐示锁
                                  • 锁流程
                                    • 插入加锁流程
                                      • 删除加锁流程
                                        • 表锁加锁流程
                                          • 行锁加锁流程
                                            • 释放锁流程
                                              • 死锁流程
                                                • 锁分裂、合并、迁移
                                                • Innodb在RR和RC隔离下的加锁实例分析
                                                  • 测试
                                                  • InnoDB锁同步机制
                                                  相关产品与服务
                                                  云数据库 SQL Server
                                                  腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
                                                  领券
                                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档