mysql中的锁机制(代码片段)

真正的飞鱼 真正的飞鱼     2022-12-03     671

关键词:

介绍锁机制

技术是为了解决问题而生的,锁被用来实现隔离性,保证并发事务的正确性。

两段锁 & 一次封锁

两段锁

数据库遵循的是两段锁协议,将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁)

  • 加锁阶段:在加锁阶段只能进行加锁操作。

    • 如果事务要读取对象,必须先获得共享锁。可以有多个事务同时获得一个对象的共享锁
    • 如果事务要修改对象,必须先获得独占锁。只能有一个事务获得对象的独占锁。如果某个事务已经获得了对象的独占锁,则其他尝试获取锁(包括共享锁、独占锁)的事务必须等待,直到加锁成功才能继续执行
  • 解锁阶段:在解锁阶段只能进行解锁操作。

事务要读取对象 ,必须先获得共享锁,这样防止幻读。事务要修改对象,必须先获得独占锁,这样防止脏写。

两段锁可以这样来实现:事务开始后就处于加锁阶段,一直到执行 rollback 或 commit 之前都是加锁阶段。rollback 和 commit 使事务进入解锁阶段,即在 rollback 或 commit 时释放持有的锁。


一次封锁

一次封锁法遵守两段锁协议。

一次封锁要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行。

一次封锁存在的问题:

  • 封锁时间被延长,并发度被降低:一次就将以后要用到的全部数据加锁,势必延长了封锁的时间,从而降低了系统的并发度。
  • 不适合用在数据库:一次封锁不适合用在数据库中,因为在事务开始阶段,数据库并不知道会用到哪些数据。

一次封锁的好处:不会出现死锁。


为什么要使用两段锁呢?用完直接释放锁不行吗?

不行,用完直接释放会使事务的隔离性受到影响。具体介绍可以看下面的文章。Mysql锁:灵魂七拷问 (youzan.com)

两段锁 的优劣局限

两段锁的优点 / 作用:

  • 解决事务并发问题:防止脏写、脏读 ......
  • 实现可串行化隔离:将两段锁与谓词锁结合使用,可以防止所有形式的写倾斜以及其他竞争条件,实现可串行化隔离
  • 性能和实际串行相比:相比于实际串行来说,使用两段锁时,多个事务可以并发读取同一个对象
  • 性能和一次封锁来比:相比于一次封锁,两段锁的锁定时间更短,事务并发性比一次封锁要好

两段锁的缺点:

  • 性能:使用两段锁,事务吞吐量和查询响应时间相比于其他弱隔离级别下降非常多。部分原因在于锁的获取和释放本身的开销,但更重要的是其降低了事务的并发性。
  • 访问延迟具有非常大的不确定性:假如一个事务需要等待另一个事务释放锁,另一个事务释放锁的时机是不确定的,因此等待它释放锁的耗时是不确定的。
  • 死锁更加频繁:由于两段锁的加锁模式,死锁可能变得更为频繁。因而导致另一个性能问题,即如果事务由于死锁而被强行中止,应用层就必须从头重试,假如死锁过于频繁,则性能和效率必然大打折扣。

数据库系统会自动检测死锁情况,并强行中止其中的一个事务以打破僵局

由于使用了这么多的锁机制,所以很容易出现死锁现象 ,例如事务 A 可能在等待事务 B 释放它持有的锁, 而事务 B 在等待事务 A 释放它持有的锁。数据库系统会自动检测事务之间的死锁情况,并强行中止其中的一个事务以打破僵局,这样另一个可以继续向前执行 。而被中止的事务需要由应用层来重试。

MySQL 提供的锁

根据加锁的范围,MySQL 里面的锁大致可以分成全局锁、表级锁和行级锁三类。

全局锁

全局锁就是对整个数据库实例加锁。

给数据库实例加全局锁的命令:flush tables with read lock;(FTWRL)

释放锁的命令:unlock tables;(表级锁、行级锁释放也是这个命令)

加上全局锁之后,整个数据库处于只读状态,其他线程的以下语句会被阻塞:

  • 数据更新语句(数据的增删改 insert、delete、update)
  • 数据定义语句(DDL、包括建表、修改表结构等)
  • 更新类事务的提交语句(更新类事务就是使用了类似 select * from t1 for update; 带 for update 的查询的事务)

全局锁的典型使用场景是,做全库逻辑备份。也就是把整库每个表存成 .sql 类型的文件。


全局锁的作用相当于是停止更新操作,拿到一个一致性视图。

MySQL 的在可重复读隔离级别下开启一个事务也可以拿到一个一致性视图,并且后者可以做到不影响更新操作。

官方自带了全量逻辑备份工具 mysqldump。

  • 当 mysqldump 使用参数 –single-transaction 的时候,就会使用基于 MVCC 机制的一致性视图。
  • 当 mysqldump 使用参数 –master-data 的时候,就会使用基于全局读锁的一致性视图。

表级锁

表级锁就是对表加锁。

MySQL 里面表级别的锁有三种:

  • 表锁;
  • 元数据锁(meta data lock,MDL);
  • 意向锁。

表锁

表锁就是对整个数据表加锁。

给数据表加表锁的命令:lock table 表名 read / write;

释放表锁的命令和释放全局锁的命令一样,都是:unlock tables;。如果不手动释放表锁,在客户端断开的时候会自动释放表锁。


需要注意的是,lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。

举个例子,如果在线程 A 中执行 lock tables t1 read, t2 write; 这个语句,则其他线程写 t1、读写 t2 的语句都会被阻塞。同时,线程 A 在执行 unlock tables 之前,也只能执行读 t1、读写 t2 的操作。连写 t1 都不允许,自然也不能访问其他表。

意向锁

表锁分为:共享锁、独占锁。

  • 如果我们想对整个数据表加共享锁,首先要确保表中没有记录被加独占锁
  • 如果我们想对整个数据表加独占锁,首先要确保表中没有记录被加共享锁 / 独占锁

那么我们该如何来判断表中是否有记录被加独占锁 / 独占锁呢?我们可以通过遍历所有记录的方式来查看表中有没有被加锁的记录,而遍历的方式太慢了。

意向锁的提出就是为了加表级别的共享锁 和 独占锁时,快速判断表中的记录是否被上锁,以避免用遍历的方式来查看表中有没有被加锁的记录,提供判断速度。


意向锁分为:意向共享锁、意向独占锁:

  • 当事务准备在某条记录上加 共享锁 时,需要先在表级别加一个 意向共享锁;
  • 当事务准备在某条记录上加 独占锁 时,需要先在表级别加一个 意向独占锁。

这样,如果表级别存在 意向共享锁,就意味着表中有被加 共享锁 的记录;如果表级别存在 意向独占锁,就意味着表中有被加 独占锁 的记录。通过意向锁我们就可以快速判断表中是否有记录被加锁。


表锁和意向锁的兼容互斥关系

意向共享锁、意向独占锁是兼容的。表锁和意向锁的兼容互斥关系如下:

  • ✔️代表二者兼容;
  • ❌代表二者互斥。
锁的类型 意向共享锁 意向独占锁
表共享锁 ✔️
表独占锁
  • 如果该表持有意向共享锁,意味着表中有记录持有共享锁,那么表共享锁可以加锁成功;表独占锁加锁失败,阻塞等待
  • 如果该表持有意向独占锁,意味着表中有记录持有独占锁,那么表共享锁、表独占锁加锁失败,阻塞等待

元数据锁

元数据锁(meta data lock,MDL)是 MySQL 5.5 版本引入的。

MDL 不需要显式使用,在访问一个表的时候会被自动加 MDL 锁。

MDL 锁分为:MDL 读锁、MDL 写锁:

  • DML 操作(数据的增删改查:insert、delete、update、select)加 MDL 读锁
  • DDL 操作(对表结构做变更操作)加 MDL 写锁。

MDL 锁的加锁、释放锁的规则:

  • MDL 读锁与 MDL 读锁互不干扰。
  • MDL 写锁与 MDL 写锁、MDL 写锁与 MDL 读锁相互阻塞。用来保证变更表结构操作的安全性。
  • MDL 锁使用两段锁:事务获得锁之后, 一直持有锁直到事务结束(包括提交或中止)。

MDL 锁作用是:防止 DDL 操作和 DML 操作并发,保证变更表结构操作的安全性。


需要注意的是,如果申请加 MDL 锁失败,那么再此之后的加锁申请都必须等待(公平锁机制,遵循先来先执行原则,先来的没有加锁成功,后来的不能加锁)。因此执行 DDL 操作时要格外注意,如果操作执行时间过长,后面的 DML 操作都将被阻塞较长时间。

行级锁

行级锁就是对记录加锁。

行级锁又分为各种类型,不同类型的行级锁的作用也不同,行级锁分为:

  • Record Lock:行锁,单个行记录的锁
  • Gap Lock:间隙锁,作用于记录与记录之间的空隙,作用仅仅是为了防止满足搜索条件的记录插入空隙(防止插入幻影记录)
  • Next-Key Lock:索引区间锁,本质是一个行锁 和 一个 Gap Lock 的结合体

Gap Lock 说明

只在可重复读或以上隔离级别下的特定操作才会加间隙锁。在 加读写锁的 select、update 和 delete 时,除了基于唯一索引(主键索引也属于唯一索引)的查询之外,基于其他索引查询时都会加间隙锁。

能够加 Gap Lock 的要求:

  • 必须是可重复读或以上隔离级别

  • 如果是 select,则必须以给读到的记录加读写锁的方式

    • 可重复读隔离级别下的 select ... for update、select ... lock in share mode
    • 可串行化隔离级别下的 select ...(加共享锁)以及上面两种手动加共享锁,排他锁的方式
  • 必须是能够走索引的查询,如果是全表扫描的查询那么没有办法加 Gap Lock。


加锁规则:包含了两个“原则”、两个“优化”和一个“bug”。

  • 原则 1:加锁的基本单位是 next-key lock。next-key lock 是前开后闭区间。
  • 原则 2:查找过程中访问到的对象才会加锁。
  • 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
  • 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
  • 一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

共享锁 & 独占锁 说明

表锁、元数据锁、行锁又都分为共享锁和独占锁。

  • 共享锁-共享锁兼容:如果事务要读取对象 ,必须先以共享模式获得锁。可以有多个事务同时获得一个对象的共享锁
  • 共享锁-独占锁、独占锁-独占锁互斥:如果事务要修改对象,必须先以独占模式获取锁。只能有一个事务获得对象的独占锁。如果某个事务已经获得了对象的独占锁,则其他尝试获取锁(包括共享锁、独占锁)的事务必须等待

共享锁 和 独占锁 的兼容互斥关系如下:

  • ✔️代表二者兼容;
  • ❌代表二者互斥。
锁的类型 共享锁 独占锁
共享锁 ✔️
独占锁

为什么要分 共享锁、独占锁:允许多个事务并发读,但只允许一个事务写,既支持并发提高性能,又保证了并发安全。

参考资料

06 | 全局锁和表锁 :给表加个字段怎么有这么多阻碍? (geekbang.org)

07 | 行锁功过:怎么减少行锁对性能的影响? (geekbang.org)

21 | 为什么我只改一行的语句,锁这么多? (geekbang.org)

《数据密集型应用系统设计》第七章:事务

《MySQL 是怎样运行的:从根儿上理解 MySQL》第25章 工作面试老大难-锁

通过各种简单案例,让你彻底搞懂mysql中的锁机制与mvcc(代码片段)

文章目录锁的分类表级锁与行级锁共享锁与排他锁意向锁行级锁实现记录锁通过主键操作单个值通过唯一索引操作单个值间隙锁通过主键操作范围值通过唯一索引操作范围值Next-key锁通过普通索引操作单个值通过普通索引操作范... 查看详情

mysql中的锁(表锁行锁)(代码片段)

 锁是计算机协调多个进程或纯线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有... 查看详情

mysql的锁机制:myisam表锁innodb行锁(代码片段)

MySQL性能强劲,是目前使用最广泛的数据库之一,以 MySQL为学习原型也方便之后掌握其他数据库,下面就给大家全面讲解下MySQL8.0的新特性,从零基础到高阶一站式学习,结合实际案例让大家有所收获!▼M... 查看详情

详解mysql中的锁机制

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决... 查看详情

mysql中的锁(代码片段)

粗略的分,我们可以将MySQL中的锁分成三类,分别是:全局锁表级锁行级锁以上这三种锁都非常好理解,下面我依次给大家介绍下全局锁顾名思义,全局锁就是对整个数据库的实例加锁,在MySQL中有这样一... 查看详情

mysql锁机制(代码片段)

innodb的锁分为共享锁和排它锁。这跟myisam中的读锁和写锁有很多不同,也是大多数人容易混淆的地方。myisam中的两种锁默认都是存储引擎自己加上去的(当然自己手动加也可以),查询时即(加了读锁之后ÿ... 查看详情

mysql中的锁(代码片段)

Mysql中锁的介绍1.Mysql引擎2.锁的定义3.锁的分类(1)按锁的粒度分类(2)按锁的级别分类(3)按加锁的方式分类(4)按操作进行分类(5)按适用方式分类4.死锁的原理及分析(1)MVCC(2)两段锁&... 查看详情

mysql中的锁(代码片段)

Mysql中锁的介绍1.Mysql引擎2.锁的定义3.锁的分类(1)按锁的粒度分类(2)按锁的级别分类(3)按加锁的方式分类(4)按操作进行分类(5)按适用方式分类4.死锁的原理及分析(1)MVCC(2)两段锁&... 查看详情

重新学习mysql数据库7:详解myisam与innodb引擎的锁实现(代码片段)

...支持全文索引(5.6以上支持) Memory:数据是存放在内存中的,默认哈希索引,非常适合存储临时数据,服务器关闭后,数据会丢失掉。  如何选择存储引擎:  MyISAM:应用是以读操作和插入操作为主,只有很少的... 查看详情

mysql锁机制详解(代码片段)

...用到事务,需要保证数据的强一致性,期间也用到了mysql的锁,但当时对mysql的锁机制只是管中窥豹,所以本文打算总结一下mysql的锁机制。  本文主要论述关于mysql锁机制,mysql版本为5.7,引擎为innodb,由于实际中关于innodb锁... 查看详情

mysql中的锁机制

介绍锁机制技术是为了解决问题而生的,锁被用来实现隔离性,保证并发事务的正确性。两段锁&一次封锁两段锁数据库遵循的是两段锁协议,将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁)加锁阶段:在加锁阶... 查看详情

mysql的锁机制,你真的了解吗?进来吧!用图表告诉你(代码片段)

什么是锁?锁的存在是为了数据的一致性,我们都知道mysql在修改数据层面是支持并发修改的,那么在多个线程同时修改一个数据时产生的线程安全问题;什么是线程安全呢?大家想象这样的场景,一个数... 查看详情

mysql的锁机制,你真的了解吗?进来吧!用图表告诉你(代码片段)

什么是锁?锁的存在是为了数据的一致性,我们都知道mysql在修改数据层面是支持并发修改的,那么在多个线程同时修改一个数据时产生的线程安全问题;什么是线程安全呢?大家想象这样的场景,一个数... 查看详情

mysql中的锁机制

介绍锁机制技术是为了解决问题而生的,锁被用来实现隔离性,保证并发事务的正确性。两段锁&一次封锁两段锁数据库遵循的是两段锁协议,将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁࿰... 查看详情

多线程synchronized中的锁优化的机制(偏向锁-;轻量级锁-;重量级锁)(代码片段)

@TOCsynchronized的锁优化的机制这也是属于我们编译器优化,以及说JVM,操作系统,它们的一些优化策略所涉及到一些小细节。基本特点结合上面的锁策略,我们就可以总结出Synchronized具有以下特性(只考虑JDK1.8):加锁工作过程1.偏向... 查看详情

mysql中的锁:表mdl意向锁行锁(代码片段)

元数据锁SHARE_READ/EXCLUSIVE:共享锁:在DQL/DML的时候给表加SHARE_READ/WRITE锁,与排它锁互斥作用:在A事务未提交的情况下,B事务不能修改表结构排他锁:在DDL的时候给表加EXCLUSIVE锁,与共享/排他锁都互斥。作用:修改表结构的时... 查看详情

mysql悲观锁总结和实践(代码片段)

...数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。 使用场景举... 查看详情

读懂mysql中的锁

数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则。对于任何一种数据库来说都需要有相应的锁定机制,所以MySQL自然也不能例外。MySQL数据库... 查看详情