百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

MySQL锁机制 mysql锁机制怎么加锁

bigegpt 2024-10-21 03:45 2 浏览

无论什么时候,只要存在多个连接在同一时刻修改数据,都会涉及到并发控制的问题。MySQL实现了两个层面的并发控制:服务层和引擎层。

锁分类

按照使用场景分类

共享锁:共享锁(shared lock)也称为读锁(read lock)。共享锁是共享的,或者说是相互不阻塞的。多个连接在同一时刻可以同时读取同一个资源,而不相互干扰。

排他锁:排他锁(exclusive lock)也称为写锁(write lock)。写锁是排他的,也就是一个写锁会阻塞其他的写锁和读锁。

按照加锁思想分类

悲观锁:对数据被外界修改保持悲观的态度,在整个数据处理过程中,数据都处于锁定状态。

乐观锁:它认为数据一般情况下不会造成冲突,在数据更新的时候才会对数据的冲突与否进行校验。

按照锁粒度分类

全局锁:对整个数据库实例加锁,它将整个数据库实例处于只读的状态。

表级锁:对整个表进行加锁的方式。MySQL表级锁分为表锁和元数据锁。

行级锁:行锁可以最大程度的支持并发处理,行锁是存储引擎层实现的,而MySQL服务层并没有实现行锁。InnoDB存储引擎行级锁类型:Record Lock、Gap Lock、Next-key Lock。

读写锁

读锁

共享锁:共享锁(shared lock)也称为读锁(read lock)。共享锁是共享的,或者说是相互不阻塞的。多个连接在同一时刻可以同时读取同一个资源,而不相互干扰。

加锁命令

select ...... lock in share mode;

测试

测试时,设置事务手动提交:set autocommit = 0,后续如果没有明确的提示,autocommit都是0。

测试时,大家开启两个窗口,建立两个连接,窗口1和窗口2分别对应事务A和事务B。

  • 窗口1:查询id=6的行数据并添加读锁,正确返回数据。
  • 窗口2:依然查询id=6的行数据并添加读锁,正确返回数据。读读不冲突。
  • 窗口1:对id=6的行执行写操作(update语句),在窗口2的事务提交之前,写操作阻塞,并可能会超时退出。

如果写锁等待时间过长,则会超时退出。

窗口1
mysql> update user set age = 20 where id =6;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

如果在窗口1中的事务在执行写操作等待期间,窗口2的事务也执行同一行数据的写操作,则会导致死锁错误。

窗口2
mysql> update user set age = 30 where id =6;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

写锁

排他锁(exclusive lock)也称为写锁(write lock)。写锁是排他的,也就是一个写锁会阻塞其他的写锁和读锁。

加锁命令

select ...... for update;

测试

  • 窗口1:查询id=6的行数据并添加写锁,正确返回数据。
  • 窗口2:依然查询id=6的行数据并添加写锁,阻塞。写写冲突。
  • 窗口1:对id=6的行执行写操作(update语句),写操作未阻塞。
  • 窗口1事务提交之后,窗口2的查询语句返回结果。

悲观锁和乐观锁

不论是乐观锁还是悲观锁都是人们定义的一种概念,并不是一种锁实现,它是一种思想。乐观锁比较适用于读多写少的场景,悲观锁适用于写多读少的场景。

悲观锁

当我们对数据库的某一条数据进行修改操作时,为了避免同时有其他人对同一行数据进行修改,通过对数据进行加锁的方式以防止并发问题。这种借助了数据库的锁机制,在修改数据之前先锁定再修改的方式称为悲观锁(Pessimistic Lock)。

悲观锁具有强烈的独占性和排他性,在整个数据的写操作过程,都将数据处于锁定状态。悲观锁的实现往往需要数据库提供的锁机制。

悲观锁的实现:

  • 数据库的锁机制如行锁、表锁、读锁和写锁都是在操作之前先加锁的操作,都属于悲观锁。
  • Java中学习的synchronized关键字也是悲观锁。

乐观锁

乐观锁是相对于悲观锁的概念,乐观锁是假设数据一般情况下都不会存在并发冲突,在数据进行更新的时候才会对数据的冲突与否进行验证。如果存在冲突,则告诉用户结果,由用户决定下一步该怎么做。

乐观锁是一种宽松的加锁方式,它不需要使用数据库本身的锁机制。

乐观锁的实现:

  • MVCC,数据库多版本控制利用版本号控制数据更新的并发问题,是乐观锁的实现。
  • CAS,比较并交换是Java中乐观锁的实现。

全局锁

全局锁:对整个数据库实例加锁,它将整个数据库实例处于只读的状态。

加锁命令 (FTWRL)

 Flush tables with read lock;

应用场景

全局锁常用来对整个数据库实例进行逻辑备份。

全局锁加锁期间,业务的数据更新操作(DML)和 表结构的修改操作(DDL)都是会被锁住的。

此时你是不是有个疑问:开发中备份都是直接使用mysqldump,什么时候使用FTWRL呢?

官方自带的逻辑备份工具mysqldump 使用参数-single -transaction的时候,在导出数据的时候就会启动一个事务,来确保拿到一致性视图,在学习事务隔离的时候我们了解到,基于MVCC的一致性视图,这个过程中的数据是可以正常更新的。但是我们要知道事务是引擎层的实现,并不是每个存储引擎都支持事务。我们在开发中大部分的备份使用的是mysqldump,主要是因为我们的存储引擎大部分情况都是使用的默认的引擎InnoDB。

表级锁

表级锁:即是对整张表进行加锁。表的定义包含两个部分:数据和结构,所以表级锁也分为两类:表锁和元空间锁。

表锁

表锁是MySQL中最基本的锁策略,并且也是开销最小的策略。表锁会锁住整张表,在对表进行写操作(插入、删除、更新等)前,需要先获取写锁,它会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他读取的用户才能获取读锁。读锁之前互相不会造成阻塞。

写锁的优先级高于读锁,因此一个写锁的请求可能会被插入到读锁队列前面,但是读锁是不能插入到写锁的前面的。

加解锁命令

-- 对表加读锁
lock tables ...... read;
-- 对表加写锁
lock tables ...... write;
-- 释放锁
unlock tables;

测试

表锁读锁测试

  • 窗口1:对user表加读锁。
  • 窗口2:对user表全表读取,正常返回全表数据。
  • 窗口2:修改user表中id=6的数据,阻塞。读写冲突。
  • 窗口1:修改user表中id=6的数据,报错。
  • 窗口1:释放读锁,窗口2的更新数据执行成功。

表锁写锁测试

  • 窗口1:对user表添加写锁。
  • 窗口2:全表查询user表,阻塞。
  • 窗口1:更新user表中的id=6的数据,更新成功。
  • 窗口1:释放锁,窗口2的全表查询返回最新数据。


元空间锁

MySQL5.5版本中引入了元空间锁(matadata lock),当对一个表做增删改查操作的时候,会添加MDL读锁。当对表结构变更操作的时候,会添加MDL写锁。MDL的作用是防止DDL和DML并发的冲突。

MDL锁是系统默认添加的,不需要显式的添加。

测试

  • 窗口1:查询表中的一条数据,这个时候在执行查询语句,会添加MDL读锁。
  • 窗口2:添加字段,此时执行的是alter语句,会添加MDL写锁。这个时候窗口1的读锁没有释放,所以alter语句会阻塞。
  • 窗口3:查询表中的一条数据,由于窗口2导致的阻塞,在窗口3申请MDL读锁的时候也会造成阻塞。
  • 窗口1:提交事务,窗口3获取了MDL读锁,返回查询结果。
  • 窗口3:提交事务释放了读锁,窗口2获取写锁,添加字段成功。


行级锁

MySQL的行锁是在引擎层由各个存储引擎实现的。并不是所有的存储引擎都支持行锁,比如MyISAM引擎是不支持行锁的。不支持行锁意味着并发控制的时候只能使用表锁,这也意味着同一个时刻同一个表只有一个更新执行,严重影响了并发。InnoDB是支持行锁的,这也是InnoDB能替代MyISM的重要原因之一。

两阶段锁协议

在InnoDB事务,行锁是需要的时候加上的,但并不是不需要了就立刻释放的,而是需要等到事务结束后才会释放锁。这个就是两阶段锁协议。

InnoDB是采用的两阶段锁协议。在事务执行的过程中,随时都可以锁定,锁只有在执行commit或者rollback的时候才会释放,并且所有的锁是在同一个时刻释放的。


事务A执行了两条update语句之后,事务B也执行update语句,但是事务B阻塞直到事务A提交事务。

行锁

我们创建一张简单表t,其中id为主键,a为索引,插入6条数据。

CREATE TABLE `t1` (
  `id` int(11) NOT NULL,
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into t1 values(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);

间隙锁(Gap Lock)

间隙锁,锁的是两个值的间隙。

对于表t1,6条数据就产生了7个间隙,如下图所示:


我们看前面学习的写锁的例子:

begin;
select * from t1 where b = 5 for update;
commit;
  • 加了写锁的select查询是当前读,读取的是最新的数据值。
  • b字段不是表的索引字段,它会扫描全表将满足条件的行都加上写锁,也会给满足条件的行两边的间隙加上了锁。

间隙锁之间并不会冲突,与间隙锁存在冲突关系的是往这个间隙中插入数据的操作。

next-key lock

行锁和间隙锁的合称为next-key lock每个next-key lock都是一个前开后闭的区间。这里不要混淆了,间隙锁是一个开区间,间隙锁加上行锁生成的next-key lock是一个前开后闭的区间。

间隙锁和next-key lock的引入帮助我们解决了幻读的问题。

间隙锁是可重复隔离级别下才生效的,如果我们把隔离级别设置为读提交,那就没有间隙锁了。

测试

前面我们在学习其他锁的时候,为了省事把autocommit设置为了0,现在我们需要把autocommit设置为1;

  • 窗口1:使用显式事务,通过for update加写锁,对满足条件的行和间隙加锁。
  • 窗口2:更新id为0的行,此行没有加锁更新成功。
  • 窗口3:插入数据,满足了(0,5]的next-key lock,阻塞等待。


总结

锁按照锁粒度分为:全局锁、表锁和行锁。

行锁是引擎层的实现,我们文中描述的行锁都是基于InnoDB存储引擎的实现。

InnoDB的行锁采用的两阶段锁协议,锁在需要的时候添加,只有在事务commit或者rollback时才会一次性释放所有锁。

可重复度隔离级别下存在间隙锁,如果设置为其他的隔离级别下就没有间隙锁了。间隙锁即是对行数据的两边间隙进行加锁,间隙锁加上行锁合称为next-key lock,它是一个前开后闭的区间。间隙锁解决了幻读的问题。

大家在学习锁的时候还需多动手实践。

我是勾勾,一直在努力的程序媛!

在看、点赞、转发和关注是最好的支持!

我们下篇文章见!

参考资料:

《高性能MySQL》

《MySQL实战45讲-丁奇》

相关推荐

Java 泛型大揭秘:类型参数、通配符与最佳实践

引言在编程世界中,代码的可重用性和可维护性是至关重要的。为了实现这些目标,Java5引入了一种名为泛型(Generics)的强大功能。本文将详细介绍Java泛型的概念、优势和局限性,以及如何在...

K8s 的标签与选择器:流畅运维的秘诀

在Kubernetes的世界里,**标签(Label)和选择器(Selector)**并不是最炫酷的技术,但却是贯穿整个集群管理与运维流程的核心机制。正是它们让复杂的资源调度、查询、自动化运维变得...

哈希Hash算法:原理、应用(哈希算法 知乎)

原作者:Linux教程,原文地址:「链接」什么是哈希算法?哈希算法(HashAlgorithm),又称为散列算法或杂凑算法,是一种将任意长度的数据输入转换为固定长度输出值的数学函数。其输出结果通常被...

C#学习:基于LLM的简历评估程序(c# 简历)

前言在pocketflow的例子中看到了一个基于LLM的简历评估程序的例子,感觉还挺好玩的,为了练习一下C#,我最近使用C#重写了一个。准备不同的简历:image-20250528183949844查...

55顺位,砍41+14+3!季后赛也成得分王,难道他也是一名球星?

雷霆队最不可思议的新星:一个55号秀的疯狂逆袭!你是不是也觉得NBA最底层的55号秀,就只能当饮水机管理员?今年的55号秀阿龙·威金斯恐怕要打破你的认知了!常规赛阶段,这位二轮秀就像开了窍的天才,直接...

5分钟读懂C#字典对象(c# 字典获取值)

什么是字典对象在C#中,使用Dictionary类来管理由键值对组成的集合,这类集合被称为字典。字典最大的特点就是能够根据键来快速查找集合中的值,其键的定义不能重复,具有唯一性,相当于数组索引值,字典...

c#窗体传值(c# 跨窗体传递数据)

在WinForm编程中我们经常需要进行俩个窗体间的传值。下面我给出了两种方法,来实现传值一、在输入数据的界面中定义一个属性,供接受数据的窗体使用1、子窗体usingSystem;usingSyst...

C#入门篇章—委托(c#委托的理解)

C#委托1.委托的定义和使用委托的作用:如果要把方法作为函数来进行传递的话,就要用到委托。委托是一个类型,这个类型可以赋值一个方法的引用。C#的委托通过delegate关键字来声明。声明委托的...

C#.NET in、out、ref详解(c#.net framework)

简介在C#中,in、ref和out是用于修改方法参数传递方式的关键字,它们决定了参数是按值传递还是按引用传递,以及参数是否必须在传递前初始化。基本语义对比修饰符传递方式可读写性必须初始化调用...

C#广义表(广义表headtail)

在C#中,广义表(GeneralizedList)是一种特殊的数据结构,它是线性表的推广。广义表可以包含单个元素(称为原子),也可以包含另一个广义表(称为子表)。以下是一个简单的C#广义表示例代...

「C#.NET 拾遗补漏」04:你必须知道的反射

阅读本文大概需要3分钟。通常,反射用于动态获取对象的类型、属性和方法等信息。今天带你玩转反射,来汇总一下反射的各种常见操作,捡漏看看有没有你不知道的。获取类型的成员Type类的GetMembe...

C#启动外部程序的问题(c#怎么启动)

IT&OT的深度融合是智能制造的基石。本公众号将聚焦于PLC编程与上位机开发。除理论知识外,也会结合我们团队在开发过程中遇到的具体问题介绍一些项目经验。在使用C#开发上位机时,有时会需要启动外部的一些...

全网最狠C#面试拷问:这20道题没答出来,别说你懂.NET!

在竞争激烈的C#开发岗位求职过程中,面试是必经的一道关卡。而一场高质量的面试,不仅能筛选出真正掌握C#和.NET技术精髓的人才,也能让求职者对自身技术水平有更清晰的认知。今天,就为大家精心准备了20道...

C#匿名方法(c#匿名方法与匿名类)

C#中的匿名方法是一种没有名称只有主体的方法,它提供了一种传递代码块作为委托参数的技术。以下是关于C#匿名方法的一些重要特点和用法:特点省略参数列表:使用匿名方法可省略参数列表,这意味着匿名方法...

C# Windows窗体(.Net Framework)知识总结

Windows窗体可大致分为Form窗体和MDI窗体,Form窗体没什么好细说的,知识点总结都在思维导图里面了,下文将围绕MDI窗体来讲述。MDI(MultipleDocumentInterfac...