【MySQL】锁详解——从结构分类到适用场景

这篇具有很好参考价值的文章主要介绍了【MySQL】锁详解——从结构分类到适用场景。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

我们要学习锁首先要了解下我们想了解的锁到底是什么🤔

而在MySQL中给某个数据加锁的本质其实就是在内存中创建一个锁结构与之关联,而这个锁结构就是我们常提到的MySQL的锁🔒

那么接下来的问题就是,这个锁结构长啥样呢?

锁的内部结构(InnoDB)

一张图详解锁结构( ̄∇ ̄)/

【MySQL】锁详解——从结构分类到适用场景

 为了节约资源,并非每个锁都有一个单独的锁结构与之对应,符合如下条件的记录就会放在同一个锁结构中

  • 在同一个事务/页面中进行的加锁操作

  • 加锁的类型一样

  • 等待状态一样

锁的分类

  • 按操作方式

    • 读锁/共享锁/S(Share Lock)

    • 写锁/排他锁/X(Exclusive Lock)

  • 按锁粒度(Lock Granularity)

    • 全局锁

    • 表级锁(Table Lock)

      • 表级别的共享锁和排他锁

      • 意向锁(Intention Lock)

      • 自增锁(AUTO-INC)

      • 元数据锁(MDL)

    • 页级锁

    • 行级锁

      • 记录锁(Record Locks)

      • 间隙锁(Gap Locks)

      • 临键锁(Next-Key Locks)

      • 插入意向锁(Insert Intention Locks)

  • 按思维方式/设计思想

    • 乐观锁(Optimistic Concurrency Control)

    • 悲观锁(Pessimistic Concurrency Control)

  • 按加锁方式

    • 隐式锁

    • 显式锁

共享锁 vs 排他锁

并发事务中"读-读"的情况一般不会引起什么问题,一般需要解决的就是对于"写-写"和"读-写"/"写-读"引起一些问题,主要有两种解决方式:加锁,或者需MVCC

既要允许"读-读"情况不受影响,又要使"写-写"、"读-写"或"写-读"情况中的操作相互阻塞,所以MySQL实现一个由两种类型的锁组成的锁系统来解决

这两种类型的锁通常被称为共享锁(Shared Lock,S Lock)和排他锁(Exclusive Lock,X Lock),也叫读锁(readlock)和写锁(write lock )

  • 共享锁/S(读锁)

    • 针对同一份数据,多个事务的读操作可以同时进行而不会互相影响,相互不阻阻塞

  • 排他锁/X(写锁)

    • 当前写操作没有完成前,它会阻断其他写锁和读锁

    • 这样就能确保在给定的时间里,只有一个事务能执行写入,并防止其他用户读取正在写入的同一资源

一句话概括就是,不同事务中,只有都是共享锁才互不影响不会阻塞,但凡有一个事务有排他锁,都会阻塞,对于InnoDB引擎来说,共享锁和排他锁可以加在表上,也可以加在行上

如何加锁

  • 加共享锁

    • SQL语句 + "LOCK IN SHARE MODE" / "FOR SHARE";

  • 加排他锁

    • SQL语句 + "FOR UPDATE";

需要注意的是:

  • 读操作(SELECT)可以加共享锁,也可以加排他锁

  • 写操作(DELETE、UPDATE、INSERT)只能加排他锁

    • INSERT

      • MySQL通过"隐式锁"来维护

    • DELETE

      • B+树上定位 -> 获取X锁 -> 执行delete mark操作

    • UPDATE

      • 修改键值:DELETE操作 + INSERT操作

      • 不修改键值:

        1. 不修改存储空间:B+树上定位 -> 获取X锁 -> 修改

        2. 修改存储空间:B+树上定位 -> 获取X锁 -> 删除原纪录(移入垃圾链表) -> 插入新纪录(INSERT操作)

补充:MySQL 8.0 新特性

  • nowait

    • 查询的数据已经加锁,立刻返回报错

  • skip locked

    • 立刻返回未被锁定的行

全局锁 vs 表级锁 vs 页级锁 vs 行级锁

数据库的并发度和锁的粒度息息相关,锁的粒度越小,并发度越高,但也越耗资源(锁的获取、检查、释放等操作),影响系统性能,因而我们需要根据实际的业务场景,在高并发响应与系统性能之间进行平衡

全局锁

对整个数据库实例进行加锁,加锁后整个数据库处于只读状态,命令如下:

Flush tables with read lock

表锁

  • 开销小,但并发性较差

  • 可以避免死锁

不与行级锁冲突的表级锁:意向锁

主要用于在已经加了行级锁后,会在上一层记录中自动加一个意向锁,用于判断当前数据页或者数据表是否被加过锁,意向共享锁和意向排他锁之间是任意组合都是相互兼容的,但是与普通的共享锁/排他锁之间,只有意向共享锁与普通共享锁兼容,其余都互斥

具体使用意向共享锁,还是意向排他锁,要根据行级锁(与行级锁一致)

  • 意向锁之间不互斥

  • 意向锁与行级锁不互斥,与表级锁互斥

  • 意向锁在保证高并发的前提下,实现了行锁与表锁共存,并且满足事务隔离性的要求

自增锁

建表字段中如果包含"AUTO_INCREMENT"关键字,每当插入操作时,就要对自增锁进行竞争,以保证主键唯一单调递增

首先,插入数据的模式有3种:

  1. 简单插入(Simple inserts)

    事先明确插入的数量
  2. 批量插入(Bulk inserts)

    基于现有的表进行插入(事先并明确插入的数量)
  3. 混合模式插入(Mixed-mode inserts)

    插入的数据有的有主键,有的没主键

主要有3种模式

  1. "传统"锁定模式(并发性较差)

    - innodb_autoinc_lock_mode=0
  2. "连续"锁定模式(8.0前默认)

    innodb_autoinc_lock_mode=1
  3. "交错"锁定模式(8.0后默认)

    innodb_autoinc_lock_mode=2

元数据锁(MDL)

可以通过下面👇代码查看当前执行的process信息

show processlist;

如果想查看在等待哪把锁,可以重点关注下State字段

页锁

  • 开销、粒度、并发度 介于表锁和行锁之间

  • 也会出现死锁

  • 每个层级的锁数量是有限制的(锁会占用内存空间),锁空间的大小是有限制的,某个层达到阈值后就会进行锁升级(用更大力度的锁代替多个较小粒度的锁)

    • 比如一张表中出现大量DELETE操作时,会锁表

行锁(InnoDB)

行锁又称记录锁,锁的是某一行数据

MySQL的服务层并没有实现行锁机制,行锁是在存储引擎层实现的

  • 优点

    • 锁粒度小,发生锁冲突概率低,可实现的并发度高

  • 缺点

    • 锁的开销较大,加锁较慢,会出现死锁

记录锁

可参照读锁和写锁理解,只不过粒度是行,只有操作相同的行才有影响

间隙锁(解决幻读问题)

在MySQL的可重复读的隔离级别下,可以解决幻读问题

因为在一开始进行读取操作的时候,我们并不知道会产生幻影数据的主键是多少(还不存在),因而无法使用记录锁事先锁定那一行,间隙锁会阻塞当前记录和其前一条记录之间的插入操作。

对于间隙锁很多文章都有小部分出入,我们就以官网为准

MySQL :: MySQL 8.0 Reference Manual :: 15.7.1 InnoDB Locking

【MySQL】锁详解——从结构分类到适用场景

这段话中提到的比较重要的部分就是说间隙锁是在索引记录之间的间隙上的锁,或者是在第一条索引记录之前或最后一条索引记录之后的间隙上的锁,对于使用唯一索引锁定行以搜索唯一行的语句,不需要间隙锁定(这不包括搜索条件只包括多列唯一索引的某些列的情况;在这种情况下,确实会发生间隙锁定)

例如,如果该列具有唯一索引,则以下语句仅使用id一个索引记录锁定值为 100 的行id,其他会话是否在前面的间隙中插入行并不重要:

SELECT * FROM child WHERE id = 100;

如果id未索引或具有非唯一索引,则该语句会锁定前面的间隙

临键锁

一句话理解~就是记录锁+间隙锁(不愧是我(*≧ω≦)/),如果还是不太明白这些🔒的锁定范围,可以移步博文 

【MySQL】记录锁?间隙锁?临键锁?到底锁了些什么?这一篇帮你捋清楚( ̄∇ ̄)/_AQin1012的博客-CSDN博客

插入意向锁

获取锁失败的事务,开始等待的事务的其锁的类型就是插入意向锁,插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁

乐观锁 vs 悲观锁

悲观锁

悲观锁是通过数据库自身的锁机制来实现,从而保证致据操作的排它性。

悲观锁总是假设最坏的情况,认为每次去查数据的时候都会被别人修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)比收如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁,当其他线程想要访问数据时,都需要阻塞挂起

如果在SELECT语句上加悲观锁,在这条语句执行过程中所有扫描的行都会被锁上,因此在MySQL中使用悲观锁需要确定必须使用了索引,不然会全表扫描,把整张表都锁上

乐观锁

乐观锁以为对同一数据的并发操作不会总发生,不用每次都对数据上锁,但是在更新的时候会判断一下在此期间别人有设有去更新这个数据,也就是不采用数据库自身的锁机制,而是通过程序来实现的。在程序上,我们可以采用版本号机制或者CAS机制实现,乐观锁适用于多读的应用类型,这样可以提高吞吐量

ps:在Java中java.util.concurrent.atomic包下的原子变量类就是使用了乐观锁的CAS实现的

1. 版本号机制

在表中设计一个饭木字段version,第一次读的时候,会获取version字段的取值。然后对数据进行更新或删除操作时,会执行UPDATE..·SET version=version+1 NHERE version=version。此时如果已经有事务对这条数据进行了更改,修改就不会成功。

这种方式类以我们展悉的SN、CVS版本管理系统,当我们修改了代玛进行提交时,首先会检查当前版木号与服务器上的版本号昏一致,如果一致就可以直接提交,如果不一致就需要更新服务器上的最新代码,然后再进行提交。

2. 时间戳机制

时间戰和版本号机制一样,也是在更新提交的时候,将当前数据的时间戳和更新之前取得的时间戳进行较,如果两者一致则更新功,否则是版本冲突,基本是通过给数据行增加一个戳(版本号或者时间戳),从而证明当前拿到的数据是否最新

总结

  • 乐观锁适用于读多的场景;悲观锁适用于写多的场景

  • 乐观锁基于程序实现,不会产生死锁;悲观锁基于MySQL的锁机制实现

显式锁 vs 隐式锁

隐式锁

一个事务在执行INSERT操作时,如果即将插入的侧除已经被其他争务加了间隙锁,那么本次INSERT操作会阻塞,并且当前事务会在该间隙上加一个插入意向锁,否则一股情况下INSERT操作是不会显式加锁的,执行逻辑大致如下:

  1. InnoDB的每条记录中都有一个隐藏的trx_id字段,存于聚簇索引的B+树中

  2. 在操作一条记录前,首先根据记录中的trx_id检查该事务是否是活动的事务(未提交、回滚),如果是活动的事务,就会现将隐式锁转换为显式锁

  3. 检查是否有锁冲突,如果有,创建锁,并设置为waiting的状态,没有就执行步骤5

  4. 等待加锁成功,被唤醒,或者超时

  5. 写数据,并将自己的trx_id写入trx_id字段

显式锁

通过特定语句进行添加的锁,一般称为显式锁("FOR UPDATE"等)

搞定撒个花(。・ω・。)ノ🎉文章来源地址https://www.toymoban.com/news/detail-409414.html

到了这里,关于【MySQL】锁详解——从结构分类到适用场景的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】线索二叉树(适用场景+图文推导过程+C语言代码实现)

    普通二叉树(如下图): 空间浪费 :存在大量“∧”,该空间未利用。 时间效率 :查找一次结点的前驱、后继就需要遍历一次,时间效率低。         在实际问题中,如果所用的 二叉树需经常遍历或查找结点时需要某种遍历序列中的前驱和后继,那么采用线索二叉链表

    2024年02月04日
    浏览(35)
  • 【flink】Task 故障恢复详解以及各重启策略适用场景说明

    当 Task 发生故障时,Flink 可以重启出错的 Task 以及其他受到影响的 Task ,以使得作业恢复到正常执行状态。 Flink 通过重启策略和故障恢复策略来控制 Task 重启: 重启策略决定是否可以重启以及重启的间隔; 故障恢复策略决定 哪些 Task 需要重启 。   参数 restart-strategy 定义了

    2024年02月04日
    浏览(41)
  • MySQL分区表的正确使用方法,适用场景,建立分区的条件

    什么是MySQL分区? MySQL分区是将一张表分割成独立的子表的技术。每个子表被称为分区,它们有着相同的结构和字段,但存储着不同的数据。这项技术可以提高查询速度,减少日志文件和磁盘空间的使用。 建立分区的条件 要建立MySQL分区,需要满足以下几个条件: 1.所需的

    2024年02月07日
    浏览(44)
  • 一文带你了解MySQL的前世今生,架构,组成部分,特点,适用场景

    MySQL最初是由瑞典公司MySQL AB的Michael Widenius和David Axmark开发的一款开源关系型数据库管理系统。MySQL AB的初衷是创造一个简单、快速、可靠的关系型数据库系统,以解决当时Web应用程序的需要。他们想要创造一个更简单、更强大的数据库系统,以取代当时主流的商业数据库系统

    2024年02月04日
    浏览(37)
  • MySQL索引:结构、语法、分类和优化

    MySQL索引是数据库中非常关键的性能优化手段。它们提供了快速访问数据的方法,同时也可以极大地提高查询效率。本文将深入介绍MySQL索引的结构、语法、分类,以及如何使用 Profile 和 EXPLAIN 来优化查询性能,带有详细的实例演示。 索引结构 MySQL索引基于B-Tree结构实现。这

    2024年02月07日
    浏览(37)
  • Mysql高级3-索引的结构和分类

    1.1 索引的介绍 索引index:是帮助 Mysql  高效获取数据  的  有序的数据结构 ,在数据之外,数据库系统维护着的满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引 1.2 索引的优缺

    2024年02月15日
    浏览(46)
  • mysql 日志分类详解

    对于IT从业人员来说,日志是日常开发和问题排查过程中非常重要的信息,通过日志可以了解到很多有用的信息,很多奇怪的不好定位的问题,往往可以通过分析日志找到答案。 在mysql中,也提供了多种类型的日志,不同类型的日志其作用也不一样,本篇就深入的了解下mysq

    2024年02月05日
    浏览(45)
  • 图解Redis适用场景

    Redis以其速度而闻名。 string,int,list,map。Redis 最常见的用例是缓存对象以加速 Web 应用程序。 此用例中,Redis 将频繁请求的数据存储在内存。允许 Web 服务器快速返回频繁访问的数据。这减轻数据库的负载并提高应用程序RT。 规模扩张时,缓存分布在 Redis 服务器集群中。分

    2024年02月05日
    浏览(42)
  • MySQL进阶篇:索引(概述,结构,分类,语法,SQL性能分析,索引使用,设计原则)

    索引(index)是帮助MysQL 高效获取数据的数据结构 ( 有序 )。 在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。 优缺点: MySQL的索引是在存储

    2024年01月20日
    浏览(48)
  • Node.js 的适用场景

    目录 前言 适用场景 1. 实时应用 用法 代码 理解 代码示例 理解 3. 微服务架构 用法 代码示例 理解 总结 前言 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,它使得 JavaScript 可以脱离浏览器运行在服务器端。Node.js 的出现极大地扩展了 JavaScript 的应用范围,使得开发者

    2024年02月04日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包