史上最全MySQL各种锁详解

news2024/11/28 19:03:18

锁详解

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

MySQL锁可以按模式分类为:乐观锁与悲观锁。按粒度分可以分为全局锁、表级锁、页级锁、行级锁。按属性可以分为:共享锁、排它锁。按状态分为:意向共享锁、意向排它锁。按算法分为:间隙锁、临键锁、记录锁。

锁分类

  • 从性能上分为乐观锁(用版本对比来实现)和悲观锁
  • 从对数据库操作的类型分,分为读锁和写锁(都属于悲观锁)

读锁(共享锁,S锁(Shared)):针对同一份数据,多个读操作可以同时进行而不会互相影响

写锁(排它锁,X锁(eXclusive)):当前写操作没有完成前,它会阻断其他写锁和读锁

  • 从对数据操作的粒度分,分为表锁和行锁

表锁

每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;一般用在整表数据迁移的场景。

  • 手动增加表锁

lock table 表名称 read(write),表名称2 read(write);

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

  • 查看表上加过的锁

show open tables;

  • 删除表锁

unlock tables;

行锁

每次操作锁住一行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高,但是加锁慢、开销大,容易发生死锁现象。

InnoDB与MYISAM的最大不同有两点:

  • InnoDB支持事务(TRANSACTION)
  • InnoDB支持行级锁

InnoDB在执行查询语句SELECT时(非串行隔离级别),不会加锁。但是update、insert、delete操作会加行锁。

简而言之,就是读锁会阻塞写,但是不会阻塞读。而写锁则会把读和写都阻塞

实现方式

在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。在UPDATE、DELETE操作时,MySQL不仅锁定WHERE条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key locking。

可以看到由于session1迟迟未提交事务,session2在等待session1释放锁时出现了超过锁定超时的警告了。

无索引行锁会升级为表锁(RR级别会升级为表锁,RC级别不会升级为表锁)

锁主要是加在索引上,如果对非索引字段更新,行锁可能会变表锁。

InnoDB的行锁是针对索引加的锁,不是针对记录加的锁。并且该索引不能失效,否则都会从行锁升级为表锁

锁定某一行还可以用lock in share mode(共享锁) 和for update(排它锁),例如:select * from test_innodb_lock where a = 2 for update; 这样其他session只能读这行数据,修改则会被阻塞,直到锁定行的session提交

卧槽,又来一个 Java 神器!!

间隙锁(Gap Lock)

间隙锁,锁的就是两个值之间的空隙。间隙锁基于非唯一索引,它锁定一段范围内的索引记录。使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。

Mysql默认级别是repeatable-read,有办法解决幻读问题吗?间隙锁在某些情况下可以解决幻读问题。

假设account表里数据如下:

那么间隙就有 id 为 (3,10),(10,20),(20,正无穷) 这三个区间,

在Session_1下面执行 update account set name = ‘张三’ where id > 8 and id <18;,则其他Session没法在这个范围所包含的所有行记录(包括间隙行记录)以及行记录所在的间隙里插入或修改任何数据,即id在(3,20]区间都无法修改数据,注意最后那个20也是包含在内的。

间隙锁是在可重复读隔离级别下才会生效。

事务A:

事务B:

事务A:

事务B:

临键锁(Next-key Locks)

临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间,是一个左开右闭区间。临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。

每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。

Next-Key Locks是行锁与间隙锁的组合。像上面那个例子里的这个(3,20]的整个区间可以叫做临键锁。

卧槽,又来一个 Java 神器!!

记录锁

记录锁也叫行锁,例如:

select * from emp where empno = 1 for update;

它会在 id=1 的记录上加上记录锁,以阻止其他事务插入,更新,删除 id=1 这一行。

乐观锁

在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法;乐观锁( Optimistic Locking ) 是相对悲观锁而言,乐观锁是假设认为即使在并发环境中,外界对数据的操作一般是不会造成冲突,所以并不会去加锁(所以乐观锁不是一把锁),而是在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回冲突信息,让用户决定如何去做下一步,比如说重试,直至成功为止;数据库的乐观锁,并不是利用数据库本身的锁去实现的,可能是利用某种实现逻辑去实现做到乐观锁的思想。

数据库的乐观并发控制要解决的是数据库并发场景下的写-写冲突,指在用无锁的方式去解决

CAS思想

数据库乐观锁的具体实现几乎就跟Java中乐观锁采用的CAS算法思想是一致,所以我们可以从CAS算法中学习到数据库乐观锁的设计:

CAS指令全称为Compare and Swap,它是系统的指令集,整个CAS操作是一个原子操作,是不可分割的。从具体的描述上,我们可以这么看CAS操作:

CAS指令需要3个操作数,分别是内存位置V,旧的预期值A,和新值B。CAS指令执行时,当我们读取的内置位置V的现值等于旧预期值A时,处理器才会将新值B去更新内置位置V的值。否则它就不执行更新,但无论是否更新V的值,都会返回V的旧值。

我们通俗的放到代码层次上去理解i = 2; i++,就是说:

  • 首先线程1从内存位置V中读取到了值,保存并作为旧预期值A. (v = 2 ,a = 2)

  • 然后在因为i要进行++操作,系统会比较内存位置V的现值跟旧预期值A进行比较,既V =? A。

  • 如果相等,B = i++ = 3 ,新值B就会对内存位置V进行更新,所以内存位置V的值就变成了B的值,3

  • 如果不相等,则说明有其他的线程修改过了内存位置V的值,比如线程2在线程1修改i的值前就更新了i的值。,所以线程1会更新变量i失败。但线程不会挂起,而是返回失败状态,等待调用线程决定是否重试或其他操作。(通常会重试直到成功)

  • 数据库层的乐观锁实现也类似代码层面的实现

实现方式

通常乐观锁的实现有两种,但它们的内在都是CAS思想的设计:

方式一: 使用数据版本(version)实现
这是乐观锁最常用的一种实现方式。什么是数据版本呢?就是在表中增添一个字段作为该记录的版本标识,比如叫version,每次对该记录的写操作都会让 version+ 1。
所以当我们读取了数据(包括version),做出更新,要提交的时候,就会拿取得的version去跟数据库中的version比较是否一致,如果一致则代表这个时间段,并没有其他的线程的也修改过这个数据,给予更新,同时version + 1;如果不一致,则代表在这个时间段,该记录以及被其他线程修改过了, 认为是过期数据,返回冲突信息,让用户决定下一步动作,比如重试(重新读取最新数据,再过更新)

update table set num = num + 1 , version = version + 1 where version = #{version} and id = #{id}

方式二: 使用时间戳(timestamp)实现

表中增加一个字段,名称无所谓,比如叫update_time, 字段类型使用时间戳(timestamp)
原理和方式一一致,也是在更新提交的时检查当前数据库中数据的时间戳和自己更新前取到的时间戳是否一致,如果一致则代表此刻没有冲突,可以提交更新,同时时间戳更新为当前时间,否则就是该时间段有其他线程也更新提交过,返回冲突信息,等待用户下一步动作。

update table set num = num + 1 ,update_time = unix_timestamp(now()) where id = #{id} and update_time = #{updateTime}

但是我们要注意的是,要实现乐观锁的思想的同时,我们必须要要保证CAS多个操作的原子性,即获取数据库数据的版本,拿数据库的数据版本与之前拿到的版本的比较,以及更新数据等这几个操作的执行必须是连贯执行,具有复合操作的原子性;所以如果是数据库的SQL,那么我们就要保证多个SQL操作处于同一个事务中。

悲观锁

在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法; 悲观锁指的是采用一种持悲观消极的态度,默认数据被外界访问时,必然会产生冲突,所以在数据处理的整个过程中都采用加锁的状态,保证同一时间,只有一个线程可以访问到数据,实现数据的排他性;通常,数据库的悲观锁是利用数据库本身提供的锁机制去实现的.

数据库的悲观并发控制可以解决读-写冲突和写-写冲突,指在用加锁的方式去解决。

实现方式

数据库的悲观锁就是利用数据库本身提供的锁去实现的(select…for update是MySQL提供的实现悲观锁的方式)

  • 外界要访问某条数据,那它就要首先向数据库申请该数据的锁
  • 如果获得成功,那它就可以操作该数据,在它操作期间,其他客户端就无法再操作该数据了
  • 如果获得失败,则代表同一时间已有其他客户端获得了该锁,那就必须等待其他客户端释放锁

共享锁

共享锁,又称之为读锁,简称S锁,当事务A对数据加上读锁后,其他事务只能对该数据加读锁,不能做任何修改操作,也就是不能添加写锁。只有当事务A上的读锁被释放后,其他事务才能对其添加写锁。

共享锁主要是为了支持并发的读取数据而出现的,读取数据时,不允许其他事务对当前数据进行修改操作,从而避免”不可重读”的问题的出现。

实现方式
select …lock in share mode

session1持有共享锁,未提交事务。session2的查询不受影响,但是update操作会被一直阻塞,直到超时。

排它锁

排它锁,又称之为写锁,简称X锁,当事务对数据加上写锁后,其他事务既不能对该数据添加读写,也不能对该数据添加写锁,写锁与其他锁都是互斥的。只有当前数据写锁被释放后,其他事务才能对其添加写锁或者是读锁。

MySQL InnoDB引擎默认update,delete,insert都会自动给涉及到的数据加上排它锁,select语句默认不会加任何锁类型。

session1 不提交事务,session2不能加锁。

兼容性加锁方式
S锁:共享锁加了S锁的记录,允许其他事务再加S锁,不允许其他事务再加X锁select…lock in share mode
X锁:排他锁加了X锁的记录,不允许其他事务再加S锁或者X锁select…for update

意向共享锁和意向排它锁

意向锁是表锁,为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存。

意向共享锁(IS锁):事务在请求S锁前,要先获得IS锁
意向排他锁(IX锁):事务在请求X锁前,要先获得IX锁

为什么意向锁是表级锁呢?

当我们需要加一个排他锁时,需要根据意向锁去判断表中有没有数据行被锁定(行锁);

(1)如果意向锁是行锁,则需要遍历每一行数据去确认;

(2)如果意向锁是表锁,则只需要判断一次即可知道有没数据行被锁定,提升性能。

下图表示意向锁和共享锁、排他锁的兼容关系。
1.当事务A对某个数据范围(行或表)上了“某锁”后,另一个事务B是否能在这个数据范围上“某锁”。
2.意向锁相互兼容,因为IX、IS只是表明申请更低层次级别元素(比如 page、记录)的X、S操作。
3.表级S锁和X、IX锁不兼容:因为上了表级S锁后,不允许其他事务再加X锁。
4.表级X锁和 IS、IX、S、X不兼容:因为上了表级X锁后,会修改数据,所以即使是行级排他锁,因为表级锁定的行肯定包括行级锁定的行,所以表级X和IX、X都不兼容。

注意:上了行级X锁后,行级X锁不会因为有别的事务上了IX而堵塞,一个mysql是允许多个行级X锁同时存在的,只要他们不是针对相同的数据行。

行锁分析

通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况

show status like 'innodb_row_lock%';              

对各个状态量的说明如下:

Innodb_row_lock_current_waits: 当前正在等待锁定的数量

Innodb_row_lock_time: 从系统启动到现在锁定总时间长度

Innodb_row_lock_time_avg: 每次等待所花平均时间

Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花时间

Innodb_row_lock_waits: 系统启动后到现在总共等待的次数

对于这5个状态变量,比较重要的主要是:

Innodb_row_lock_time_avg (等待平均时长)

Innodb_row_lock_waits (等待总次数)

Innodb_row_lock_time(等待总时长)

尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手制定优化计划。

查看INFORMATION_SCHEMA系统库锁相关数据表

-- 查看事务 
select * from INFORMATION_SCHEMA.INNODB_TRX; 
-- 查看锁 
select * from INFORMATION_SCHEMA.INNODB_LOCKS; 
-- 查看锁等待 
select * from INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
-- 释放锁,trx_mysql_thread_id可以从INNODB_TRX表里查看到 
kill trx_mysql_thread_id 
-- 查看锁等待详细信息 
show engine innodb status\G;       

死锁

set tx_isolation=‘repeatable-read’;

Session_1执行:select * from account where id=1 for update;

Session_2执行:select * from account where id=2 for update;

Session_1执行:select * from account where id=2 for update;

Session_2执行:select * from account where id=1 for update;

查看近期死锁日志信息:show engine innodb status\G;

大多数情况mysql可以自动检测死锁并回滚产生死锁的那个事务,但是有些情况mysql没法自动检测死锁

锁优化建议

  • 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
  • 合理设计索引,尽量缩小锁的范围
  • 尽可能减少检索条件范围,避免间隙锁
  • 尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行
  • 尽可能低级别事务隔离

总结

Innodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一下,但是在整体并发处理能力方面要远远优于MYISAM的表级锁定的。当系统并发量高的时候,Innodb的整体性能和MYISAM相比就会有比较明显的优势了。

但是,Innodb的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让Innodb的整体性能表现不仅不能比MYISAM高,甚至可能会更差。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1300204.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

20道计算机网络面试题

网络分层 1、说说OSI 七层、TCP/IP 四层的关系和区别&#xff1f; OSI 七层从下往上依次是&#xff1a;物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。一张图给你整明白&#xff1a; TCP/IP 四层从下往上依次是&#xff1a;网络接口层、网络层、传输层、应用…

【工具】JS|浏览器脚本6分钟极速入门 · 开发一个限制自己刷b站的脚本

这张图花里胡哨的是让AI生成的&#xff0c;我觉得怪可爱的&#xff0c;就直接作为封面了。 这篇文章中会开发一个JS脚本&#xff0c;这是一个用来限制b站网页版功能的脚本&#xff0c;避免刷b站的时间过长。功能如下&#xff1a; 除了搜索、视频页、私信页之外的任何页都会被重…

最长连续序列(leetcode 128)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路方法一&#xff1a;排序方法二&#xff1a;哈希表 5.实现示例参考文献 1.问题描述 给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。 请你…

事务--03---分布式系统唯一ID

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 分布式ID一、什么是分布式系统唯一ID2. 二、分布式系统唯一ID的特点 分布式ID-----实现方案1、使用UUID生成分布式ID2、基于数据库自增ID3、Redis生成ID4、号段模式…

电机:无刷直流电机的原理

一、什么是无刷直流电机 无刷直流电机&#xff0c;英文名称 Brushless DC Motor&#xff0c;简称BLDC&#xff1b; 无刷直流电机的定子是线圈组&#xff0c;而转子是磁铁组&#xff0c;所以不需要用刷子把电流引到定子上&#xff0c;这就是无刷的来历。 电机运动的原理都是依…

[山东大学操作系统课程设计]实验四+实验五

0.写在前面&#xff1a; 为什么这次把两个实验放在一起写了&#xff0c;因为实验五的要求就是在实验四的基础上完成实现的。但是我得实现说明&#xff0c;我的实验四虽然完成了要求&#xff0c;但是无法在我自己的实验四的基础上完成实验五&#xff0c;这是一个很大的问题&…

智能优化算法应用:基于被囊群算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于被囊群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于被囊群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.被囊群算法4.实验参数设定5.算法结果6.参考文…

linux权限管理以及shell

1.shell 1.1什么是shell? shell即外壳&#xff0c;是运行在linux系统上的一个脚本语言&#xff0c;包裹在linux内核的外面。我们常说的linux操作系统实际上是linux内核。我们使用的所有指令都是一个个程序&#xff0c;而shell指令就是一个将我们用户的操作翻译给linux内核的程…

layui日历插件

layui日历插件: 在已开源的layui日历插件的基础上的改版&#xff08;原版插件地址&#xff1a;https://gitee.com/smalldragen/lay-calender-mark&#xff09;https://gitee.com/tangmaozizi/layui-calendar-plugin.gitjava后台代码并没有把项目完整结构上传上去&#xff0c;因…

小黑子——springBoot基础

springBoot简单学习 一、SpringBoot简介1.1 springBoot快速入门1.1.1 开发步骤1.1.2 对比1.1.3 官网构建工程1.1.3 SpringBoot工程快速启动 1.2 springBoot概述1.2.1 起步依赖I. 探索父工程II. 探索依赖III. 小结 1.2.2 程序启动1.2.3 切换web服务器-jetty 二、配置文件2.1 配置…

Redis权限管理体系(一):客户端名及用户名

在Redis6之前的版本中&#xff0c;因安全认证的主要方式是使用Redis实例的密码进行基础控制&#xff0c;而无法按照不同的应用来源配置不同账号以及更细粒度的操作权限控制来管理。本文先从client list中的信息入手&#xff0c;逐步了解Redis的客户端名设置、用户设置及权限控制…

simulink MATLABFunction模块中实时函数调用函数的使用

样例 function Predyy matlabceshi(input, Time_s) input1 input; Time_s1 Time_s; Predyy ee(input1) mm(Time_s1); end 上面是主要部分&#xff0c;下面是被调用部分 function A ee(input1) A input1 * 100; end function B mm(Time_s1) B Time_s1 * 100; end 模型…

每日一练【盛最多水的容器】

一、题目描述 11. 盛最多水的容器 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明…

Threejs发光闪烁提示特效

一、导语 发光闪烁特效应该在我们的项目中是经常需要去封装的一个特效吧&#xff0c;一般用于点击选择&#xff0c;选中物体&#xff0c;或者一些特效加持于中心物体&#xff0c;物体碰撞检测后的发光特效等等 二、分析 我们可以合理的使用后处理特效&#xff0c;上步骤&am…

深度学习与计算机视觉技术的融合

深度学习与计算机视觉技术的融合 一、引言 随着人工智能技术的不断发展&#xff0c;深度学习已经成为了计算机视觉领域的重要支柱。计算机视觉技术能够从图像和视频中提取有用的信息&#xff0c;而深度学习则能够通过学习大量的数据来提高计算机视觉技术的性能。本文将探讨深…

简易加减运算器的制作----数字电路设计(含proteus仿真)

简易加减运算器的制作 一、功能要求—基本功能 1、自制0-9按键&#xff0c;在一个LED数码管上稳定地显示当前按下的值。&#xff08;基本功能&#xff09; 2、增加、两个按键&#xff0c;实现0-9两个一位数的加法运算&#xff0c;同时在两位LED上稳定地显示运算结果。&#…

[MySQL--进阶篇]存储引擎的体系结构、简介、特点、选择

前言 ⭐Hello!这里是欧_aita的博客。 ⭐今日语录&#xff1a;不要在乎别人怎么看你&#xff0c;因为他们根本就没有时间&#xff0c;他们只关心他们自己。 ⭐个人主页&#xff1a;欧_aita ψ(._. )>⭐个人专栏&#xff1a; 数据结构与算法 MySQL数据库 存储引擎 前言MySQL体…

class066 一维动态规划【算法】

class066 一维动态规划 算法讲解066【必备】从递归入手一维动态规划 code1 509斐波那契数列 // 斐波那契数 // 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 // 该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。…

049:VUE 引入jquery的方法和配置

第049个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

PWN动态调试

这篇文章就是来教大家学习怎么动态调试的&#xff0c;然后我还写了一篇关于动态调试的文章&#xff0c;不是buu上面的题&#xff0c;就是两道简单的栈溢出问题&#xff0c;那两道题挺有特点的。大家可以去看看。 每日3道PWN之课外2道&#xff08;第2.5天&#xff09;-CSDN博客 …