Mysql进阶之锁全解,最全锁知识,超级细节!

1.锁

1.1概述

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源( CPU 、 RAM、 I/O )的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
MySQL 中的锁,按照锁的粒度分,分为以下三类:
  • 全局锁:锁定数据库中的所有表。
  • 表级锁:每次操作锁住整张表。
  • 行级锁:每次操作锁住对应的行数据。

2.全局锁

2.1 介绍

全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的 DML 的写语句, DDL 语句,已经更新操作的事务提交语句都将被阻塞。
其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
为什么全库逻辑备份,就需要加全就锁呢?
A. 我们一起先来分析一下不加全局锁,可能存在的问题。
假设在数据库中存在这样三张表 : tb_stock 库存表, tb_order 订单表, tb_orderlog 订单日
志表。
  • 在进行数据备份时,先备份了tb_stock库存表。
  • 然后接下来,在业务系统中,执行了下单操作,扣减库存,生成订单(更新tb_stock表,插入 tb_order表)。
  • 然后再执行备份 tb_order表的逻辑。
  • 业务中执行插入订单日志操作。
  • 最后,又备份了tb_orderlog表。
此时备份出来的数据,是存在问题的。因为备份出来的数据, tb_stock 表与 tb_order 表的数据不一
( 有最新操作的订单信息 , 但是库存数没减 )
那如何来规避这种问题呢 ? 此时就可以借助于 MySQL 的全局锁来解决。
B. 再来分析一下加了全局锁后的情况
对数据库进行进行逻辑备份之前,先对整个数据库加上全局锁,一旦加了全局锁之后,其他的 DDL 、DML全部都处于阻塞状态,但是可以执行 DQL 语句,也就是处于只读状态,而数据备份就是查询操作。那么数据在进行逻辑备份的过程中,数据库中的数据就是不会发生变化的,这样就保证了数据的一致性和完整性。

2.2 语法

A. 加全局锁

flush tables with read lock ;

 B. 数据备份

mysqldump -uroot –p1234 itcast > itcast.sql
数据备份的相关指令 , 在后面 MySQL 管理章节 , 这部分是属于数据库主从复制相关知识,后面 还会出新的博客进行详细讲解 .
C. 释放锁
unlock tables ;

2.3 特点

数据库中加全局锁,是一个比较重的操作,存在以下问题:
  • 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
  • 如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。
InnoDB 引擎中,我们可以在备份时加上参数 --single-transaction 参数来完成不加锁的一致
性数据备份。
mysqldump --single-transaction -uroot –p123456 itcast > itcast.sql

对了,忘了介绍命令里面有个 > 符号,这个符号是linux的一个重定向符号,用于将命令的输出重定向到文件,而不是默认显示在终端上。

这部分可以后续关注我的linux部分详细讲解!

3. 表级锁

3.1 介绍

表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在 MyISAM 、InnoDB、 BDB 等存储引擎中。
对于表级锁,主要分为以下三类:
  • 表锁
  • 元数据锁(meta data lockMDL
  • 意向锁

3.2 表锁

3.2.1 介绍

对于表锁,分为两类:
  • 表共享读锁(read lock
  • 表独占写锁(write lock

3.2.2 语法

  • 加锁:lock tables 表名... read/write
  • 释放锁:unlock tables / 客户端断开连接 。

3.2.3 特点

A. 读锁

左侧为客户端一,对指定表加了读锁,不会影响右侧客户端二的读,但是会阻塞右侧客户端的写。

 B. 写锁 

左侧为客户端一,对指定表加了写锁,会阻塞右侧客户端的读和写。

结论 : 读锁不会阻塞其他客户端的读,但是会阻塞写。写锁既会阻塞其他客户端的读,又会阻塞其他客户端的写。

 也就是常言道:读读共享,读写互斥,写写互斥。

3.3 元数据锁

3.3.1 介绍

meta data lock , 元数据锁,简写 MDL
MDL 加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。 MDL 锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免 DML DDL 冲突,保证读写的正确性。
这里的元数据,大家可以简单理解为就是一张表的表结构。 也就是说,某一张表涉及到未提交的事务时,是不能够修改这张表的表结构的。
MySQL5.5 中引入了 MDL ,当对一张表进行增删改查的时候,加 MDL 读锁 ( 共享 ) ;当对表结构进行变更操作的时候,加MDL 写锁 ( 排他 )
常见的SQL 操作时,所添加的元数据锁:

 

这里兼容互斥的意思就是,兼容是不影响两个事务的操作,不阻塞,互斥就是影响两个事务的操作,后来者阻塞。下面给个实例进行演示一下兼容互斥的情况:

演示:
当执行 SELECT INSERT UPDATE DELETE 等语句时,添加的是元数据共享锁( SHARED_READ / SHARED_WRITE)之间是兼容的。
当执行 SELECT 语句时,添加的是元数据共享锁( SHARED_READ ),会阻塞元数据排他锁
EXCLUSIVE ),之间是互斥的。

3.3.2 语法

 我们可以通过下面的SQL,来查看数据库中的元数据锁的情况:

select object_type,object_schema,object_name,lock_type,lock_duration from
performance_schema.metadata_locks ;

 我们在操作过程中,可以通过上述的SQL语句,来查看元数据锁的加锁情况。

这里面的字段分别是:

object_type :锁的级类,这里是表级锁,因为MDL锁是表级锁的一种嘛。

objext_schema:作用的数据库,我上锁上的是的db01这个数据库。

objext_name:作用的表,db01这个表。

lock_type :锁的类型,这里是共享写锁。

lock_duration:锁的作用时间,这里是一个事务的周期。

3.3.3 作用

这就是元数据锁MDL的作用,我在操作数据的时候,给表自动加一个MDL锁,这时不可以修改表结构,这是mysql5.5版本之后的特性。保证了表结构的一致性。 

3.4 意向锁

3.4.1 介绍与作用

为了避免 DML 在执行时,加的行锁与表锁的冲突,在 InnoDB 中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
假如没有意向锁,客户端一对表加了行锁后,客户端二如何给表加表锁呢,来通过示意图简单分析一下:
首先客户端一,开启一个事务,然后执行 DML 操作,在执行 DML 语句时,会对涉及到的行加行锁。
(行锁接下来会详细讲解,这里了解一下就好)
当客户端二,想对这张表加表锁时,会检查当前表是否有对应的行锁,如果没有,则添加表锁,此时就会从第一行数据,检查到最后一行数据,效率较低。
有了意向锁之后 :
客户端一,在执行DML操作时,会对涉及的行加行锁,同时也会对该表加上意向锁。
而其他客户端,在对这张表加表锁的时候,会根据该表上所加的意向锁来判定是否可以成功加表锁,而不用逐行判断行锁情况了。

  • 分类意向共享锁(IS): 由语句select ... lock in share mode添加 。 与表锁共享锁 (read)兼容,与表锁排他锁(write)互斥。
  • 意向排他锁(IX): insertupdatedeleteselect...for update添加 。与表锁共 享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥。

意向锁属于表级锁,但是他是在加行锁的时候自动加意向锁,用来判断该表要在加表锁的时候,是否有行锁的存在。

一旦事务提交了,意向共享锁、意向排他锁,都会自动释放。

 可以通过以下SQL,查看意向锁及行锁的加锁情况:

select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;

 兼容冲突就不再详细演示,还是那句话,只有读读共享,其余互斥。

接下来重点讲解一下行级锁。

4. 行级锁

4.1 介绍

行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中。
InnoDB 的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:
  • 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行updatedelete。在 RCRR隔离级别下都支持。

  • 间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。

  • 临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙GapRR隔离级别下支持。

4.2 行锁

4.2.1介绍

InnoDB 实现了以下两种类型的行锁:
4.2.1.1 共享锁(Shared Lock, S Lock)
  • 共享锁 允许多个事务同时读取相同的数据,但不允许修改数据。当某个事务对数据加上共享锁时,其他事务可以继续对该数据加共享锁,但不能加排它锁。因此,多个事务可以并发读取数据,而不互相阻塞。

    共享锁的特点:

  • 共享锁用于读操作,允许多个事务并发地读取数据,但不允许任何事务修改该数据。
  • 如果一个事务持有共享锁,其他事务也可以获得共享锁(读取数据),但不能获得排它锁(写数据)。
  • 共享锁可以确保数据在多个读操作之间保持一致性。
4.2.1.2 排它锁(Exclusive Lock, X Lock)

排它锁(也叫写锁)用于写操作,即修改数据。当一个事务对数据加上排它锁时,其他事务不能再获得该数据的任何类型的锁(包括共享锁和排它锁)。排它锁确保只有当前事务能够修改该数据,避免其他事务的读或写操作导致数据不一致。

排它锁的特点:

  • 排它锁用于写操作,当一个事务对数据加上排它锁时,其他事务不能再对该数据加任何锁,保证事务能够安全地修改数据。
  • 一个事务持有排它锁时,其他事务既不能读(因为无法加共享锁),也不能写(因为无法加排它锁)。

排它锁的使用场景:

  • 排它锁在事务需要对某条记录进行更新、插入、删除时使用,确保在修改过程中没有其他事务能够读取或修改同一数据,从而保证数据的一致性和正确性。

这两种我应该提前介绍,我放在了这里介绍。有点晚。

前面提到过,读读共享,其他互斥。 

常见的SQL语句,在执行时,所加的行锁如下:

4.2.2 演示

默认情况下, InnoDB REPEATABLE READ 事务隔离级别运行, InnoDB 使用 next-key 锁进行搜
索和索引扫描,以防止幻读。
针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
InnoDB 的行锁是针对于索引加的锁,不通过索引条件检索数据,那么 InnoDB将对表中的所有记
录加锁,此时 就会升级为表锁
可以通过以下 SQL ,查看意向锁及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;
A. 普通的 select 语句,执行时,不会加锁。

B. select...lock in share mode,加共享锁,共享锁与共享锁之间兼容。  

lock_mode:是行级锁的类型,这里是 S,REC_NOT_GAP,就是说是共享行锁,在前面的行级锁介绍中,有英文的名字介绍,其实就是显示的共享锁的简写英文,S是 S lock 就是共享锁,GAP就是 GAP lock 就是排它锁,REC_NOT_GAP就是record not GAP 就是行锁不是间隙锁,只是单独锁的这一行的数据,不加间隙锁。

lock_type:之前讲过,是锁的类级别:这里是record翻译过来就是行级锁。

其他的都在表锁的时候讲过,这里有一个特殊的没见过的lock_data:这里就是指,我行级锁锁的是哪一行或者哪一间隙(间隙后续会有讲解)。

 共享锁与排他锁之间互斥

客户端一获取的是 id 1 这行的共享锁,客户端二是可以获取 id 3 这行的排它锁的,因为不是同一行数据。 而如果客户端二想获取id 1 这行的排他锁,会处于阻塞状态,以为共享锁与排他锁之间互斥。
C. 排它锁与排他锁之间互斥
当客户端一,执行 update 语句,会为 id 1 的记录加排他锁; 客户端二,如果也执行 update 语句更
id 1 的数据,也要为 id 1 的数据加排他锁,但是客户端二会处于阻塞状态,因为排他锁之间是互斥的。 直到客户端一,把事务提交了,才会把这一行的行锁释放,此时客户端二,解除阻塞。

D. 无索引行锁升级为表锁  

 我们在两个客户端中执行如下操作:

在客户端一中,开启事务,并执行 update 语句,更新 name Lily 的数据,也就是 id 19 的记录 。
然后在客户端二中更新 id 3 的记录,却不能直接执行,会处于阻塞状态,为什么呢?
原因就是因为此时,客户端一,根据 name 字段进行更新时, name 字段是没有索引的,如果没有索引,此时行锁会升级为表锁( 因为行锁是对索引项加的锁,而 name 没有索引 )
接下来,我们再针对 name 字段建立索引,索引建立之后,再次做一个测试:

此时我们可以看到,客户端一,开启事务,然后依然是根据 name 进行更新。而客户端二,在更新 id 3 的数据时,更新成功,并未进入阻塞状态。 这样就说明,我们根据索引字段进行更新操作,就可以避免行锁升级为表锁的情况。

4.3 间隙锁&临键锁

默认情况下, InnoDB REPEATABLE READ 事务隔离级别运行, InnoDB 使用 next-key 锁进行搜
索和索引扫描,以防止幻读。
  • 索引上的等值查询(唯一索引),给不存在的记录加锁时, 优化为间隙锁 。
  • 索引上的等值查询(非唯一普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁。
  • 索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。

间隙锁和临键锁(next-key)在行级锁的介绍部分有介绍,忘了可以回顾。

这里还要叮嘱一句,在RR和串行化隔离级别下,只要使用行级锁,在特定条件下间隙锁和临键锁自动添加,默认是临键锁,根据上述的条件,退化成间隙锁。

接下来我来详细演示介绍这几句话的具体作用。

注意:间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。(就是说可以重入,在锁的层面属于重入锁)。

 4.3.1 演示

A. 索引上的等值查询 ( 唯一索引 ) ,给不存在的记录加锁时 , 优化为间隙锁 。

这里,行级锁的间隙锁lock_data显示的是8,就证明我间隙锁锁的是8之前的间隙,8之前的间隙都不允许被插入数据。

B. 索引上的等值查询 ( 非唯一普通索引 ) ,向右遍历时最后一个值不满足查询需求时,(临键锁) next-key lock 退化为间隙锁。
介绍分析一下:
我们知道 InnoDB B+ 树索引,叶子节点是有序的双向链表。 假如,我们要根据这个二级索引查询值为18 的数据,并加上共享锁,我们是只锁定 18 这一行就可以了吗? 并不是,因为是非唯一索引,这个结构中可能有多个18 的存在,所以,在加锁时会继续往后找,找到一个不满足条件的值(当前案例中也就是29 )。此时会对 18 加临键锁,并对 29 之前的间隙加锁。

 C. 索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。

到这里可能会有人很多混乱,什么混乱呢,就是S,X,REC_NOT_GAP,GAP到底都是什么啊,我来详细讲解:

S :就是共享锁的英文简称,同时,我前面提到过,在满足条件的情况下,加行级锁,默认和临键锁同时添加,单独的一个S也就代表了共享锁和临键锁,只有在S,REC_NOT_GAP同时出现时,才是单独的行锁。

X:排它锁的英文简称,也和S同理

GAP:间隙锁的英文简称 

REC_NOT_GAP:不是间隙锁。

有没有发现,行锁+间隙锁=临键锁,对没错!

接下来继续讲解:

查询的条件为 id>=19 ,并添加共享锁。 此时我们可以根据数据库表中现有的数据,将数据分为三个部分:
[19]
(19,25]
(25,+∞]
所以数据库数据在加锁是,就是将 19 加了行锁, 25 的临键锁(包含 25 25 之前的间隙),正无穷的临键锁( 正无穷及之前的间隙 )
到这里锁的相关知识,就讲完了,是不是看起来很复杂,其实很简单,我来总结一下:

总结

锁的级别分类有:全局锁、表级锁、行级锁。

锁的颗粒度分类:

                表级锁有:表锁、元数据锁、意向锁。

                行级锁有:行锁、临键锁、间隙锁。(临键锁=间隙锁+行锁)

锁的大类分类:共享读锁、排它写锁。

1.1. 全局锁
  • 作用:全局锁会锁定整个数据库。这种锁通常用于全局操作,例如备份数据库时使用的 FTWRL(Flush Tables with Read Lock)命令。
  • 优点:可以确保数据库在备份期间不被修改,数据一致性得到保证。
  • 缺点:并发性极差,锁定期间所有事务(包括读写操作)都会被阻塞,导致数据库停滞。
  • 应用场景:数据库的备份或迁移操作,保证数据一致性时。
1.2. 表级锁
  • 表级锁的粒度较大,锁住整张表。
    • 表锁:锁定整张表。
    • 元数据锁:InnoDB 中用来防止表结构的修改,确保在执行 ALTER TABLE 等操作时,表的结构不会被并发修改。
    • 意向锁:用来标记事务即将在表内对某些行加锁,允许多个事务同时对同一个表加行锁。
  • 优点:管理简单,适用于较大范围的操作,避免冲突。
  • 缺点:并发性能差,表锁会导致整个表不能被其他事务修改,容易造成锁竞争。
  • 应用场景:用于批量数据操作、大量删除、插入时,或在没有高并发需求的情况下。
1.3. 行级锁
  • 行级锁的粒度最细,只锁定某一行数据,因此允许更多的并发操作。行级锁主要有:
    • 行锁:锁定单行数据,允许其他事务并发访问其他行。
    • 临键锁:锁定当前行及其前后的间隙,防止幻读(Next-Key Lock)。
    • 间隙锁:只锁定行与行之间的间隙,防止在间隙中插入新行。
  • 优点:并发性能好,锁定粒度小,事务可以并行操作不同行的数据。
  • 缺点:锁的开销较大,且容易引发死锁。
  • 应用场景:适用于高并发的环境,尤其是对特定行进行修改时,如 OLTP(在线事务处理)系统。
1.4. 共享锁(读锁)
  • 作用:允许多个事务同时读取数据,但不允许修改。持有共享锁的事务可以读取数据,但不能对其进行修改。
  • 优点:允许多个事务并发读取数据,提升并发性能。
  • 缺点:无法进行修改,数据只能在事务提交之后被更新。
  • 应用场景:适用于只读操作的事务,或事务隔离级别较高的场景,如 SELECT ... LOCK IN SHARE MODE
1.5. 排它锁(写锁)
  • 作用:排它锁只允许一个事务修改数据,其他事务不能读或写被锁定的数据。持有排它锁的事务可以读取和修改数据。
  • 优点:保证数据的一致性,修改数据时不被其他事务干扰。
  • 缺点:并发性能较差,因为同时只能有一个事务对数据进行操作。
  • 应用场景:更新、删除或插入数据时,尤其是事务隔离级别较高的环境。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值