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

「一文搞懂」MySQL数据库锁之全局锁|表锁|行锁

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

本章内容

数据库锁分类

数据库锁按粒度划分,分为:全局锁、表锁和行锁。

其中:

  • 全局锁、表锁为Server层锁。
  • 行锁为引擎层锁。

全局锁

全局锁定义

全局锁是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句都将被阻塞。其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。

通过全局锁进行数据库备份时:

  • 如果在主库上备份,则备份期间都不能执行更新,业务基本上就得停摆。
  • 如果在从库上备份,则备份期间从库不能执行主库同步过来的binlog,会导致主从延迟。

全局锁加解锁

全局锁加解锁:

  • 加锁:执行命令flush tables with read lock(FTWRL)。
  • 解锁:执行命令unlock tables或者加锁客户端断开连接。

FTWRL使用场景

官方自带的逻辑备份工具是mysqldump。当mysqldump使用参数--single-transaction时,导数据之前会启动一个事务,来确保拿到一致性视图。由于MVCC的支持,该逻辑备份过程中数据可以正常更新。

通过使用参数single-transaction进行逻辑备份的方法只适用于所有表使用事务引擎的库。如果有的表使用了不支持事务的引擎,那么备份就只能通过FTWRL方法。

另外,通过set global readonly=true的方式也可以使数据库进入只读状态,不建议使用该方式的原因:

  • 1)在有些系统中,readonly的值会被用来做其他逻辑(如:用来判断一个库是主库还是备库)。因此,修改global变量的方式影响面更大,不建议使用。
  • 2)在异常处理机制上有差异:
    • 通过FTWRL命令进行逻辑备份,如果客户端发生异常断开,则MySQL会自动释放这个全局锁,整个库回到可以正常更新的状态。
    • 通过设置readonly的值(set global readonly=true)进行逻辑备份,如果客户端发生异常断开,则数据库就会一直保持readonly状态,会导致整个库长时间处于不可写状态,风险较高。

表锁

MySQL中表锁有两种:表锁和元数据锁(meta data lock,MDL)。

表锁

表锁加解锁:

  • 加锁:lock tables … read/write。
  • 解锁:执行命令unlock tables或者加锁客户端断开连接。

注意:lock tables语法除了会限制其他线程的读写外,也限制了当前线程的操作。

示例:假如在线程A中执行语句lock tables t1 read, t2 write,则其他线程写t1、读写t2的语句都会被阻塞。同时,线程A在执行unlock tables释放表锁之前,也只能执行读t1、读写t2的操作。不允许写t1,也不能访问其他表。

在没有出现更细粒度的锁之前,表锁是最常用的处理并发的方式。而InnoDB引擎支持行锁,一般不使用lock tables命令来控制并发,因为锁住整个表的影响面太大。

元数据锁(MDL)

元数据锁

元数据锁的作用是保证读写的正确性。它不需要显式使用,在访问一个表时会自动添加元数据锁。

如果一个查询正在遍历一个表中的数据,执行期间另一个线程对这个表结构做变更(如:删除一列),那么查询线程获取的结果就会与表结构不匹配。因此,在MySQL5.5版本中引入了MDL,当对一个表做增删改查操作时,添加MDL读锁;当对一个表做结构变更操作时,添加MDL写锁。其中:

  • 读锁之间不互斥,因此多个线程可以同时对一张表进行增删改查。
  • 读写锁之间、写锁之间互斥,用来保证表结构变更操作的安全性。因此,如果有两个线程同时对一个表添加字段,其中一个线程要等另一个线程执行完成后才能执行。

注意:事务中的MDL锁,在语句开始执行时申请,但是语句执行结束后并不会马上释放,而会等到整个事务提交后才释放。

向表中安全的添加字段

向表中添加字段时,如果要添加字段的表中存在正在执行的长事务,就会一直占用MDL锁,需要先暂停DDL或者kill掉该长事务。在MySQL中,通过查看information_schema库的innodb_trx表可以查找当前执行中的事务。

向热点表中添加字段时,可以在alter table语句中设定等待时间,如果在指定的等待时间内未获得MDL写锁,则先放弃添加字段操作,之后再通过重试该命令向热点表中添加字段。

行锁

MySQL行锁是在引擎层由各个引擎自己实现,并不是所有的引擎都支持行锁(如:MyISAM引擎不支持行锁)。不支持行锁意味着并发控制只能使用表锁,同一张表上任何时刻只能执行一个更新操作,这就会影响到业务并发度。InnoDB引擎支持行锁,这是MyISAM被InnoDB替代的重要原因之一。

行锁是针对数据表中行记录的锁。如:事务A更新一行记录,此时,如果事务B也要更新同一行记录,则事务B需要等待事务A操作完成后才能更新。

两阶段锁协议

在InnoDB事务中,行锁是在需要时才添加,但不是立刻释放,而是要等到事务结束才会释放,这就是两阶段锁协议。

根据两阶段锁协议,如果事务中需要锁住多行记录,则需要将最可能造成锁冲突或最可能影响并发度的锁尽量往后放。

假设需要实现一个电影票在线交易业务,顾客A在影院B购买电影票。

业务操作流程:

  • 1)从顾客A的账户余额中扣除电影票价。
  • 2)向影院B的账户中增加购买成功的电影票价。
  • 3)记录一条交易日志。

以上操作需要在一个事物中更新两条记录(顾客A的账户和影院B的账户)、插入一条记录(交易日志)。此时,如果有另外一位顾客C要在影院B购买电影票,由于顾客A和顾客C需要更新同一个账户金额(即:修改同一行数据),那么这两个事务中语句2就会冲突。根据两阶段锁协议,所有操作的行锁在事务提交时才释放,因此,需要将语句2放置在最后,尽量减少事务之间的锁等待,提升了并发度。

死锁和死锁检测

当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待其他线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁。

如图所示:

图中,事务A在等待事务B释放id=2的行锁,而事务B在等待事务A释放id=1的行锁。事务A和事务B在互相等待对方的资源释放,就会进入死锁状态。

当出现死锁以后,有两种解锁策略:

  • 1)直接进入等待,直到超时。超时时间可以通过参数innodb_lock_wait_timeout进行设置,默认为50s。
  • 2)发起死锁检测,发现死锁后主动回滚死锁链条中的一个事务,让其他事务得以继续执行。将参数innodb_deadlock_detect设置为on,表示开启死锁检测。

对于正常业务来说,如果采用第一种策略,让死锁进入等待直到超时,会存在如下问题:

  • 设置超时时间太长(如:50s),会影响正常业务处理。
  • 设置超时时间太短(如:1s),会误伤正常业务处理(即:正常业务等待也会当作死锁处理)。

因此,正常情况下一般采用第二种策略(即:主动死锁检测)。当出现死锁时,主动死锁检测能够快速发现死锁并进行处理。但是主动死锁检测会消耗CPU资源。

示例:假设有1000个并发线程要同时更新同一行,每个新来的线程被堵住时,都需要判断是否由于自己的加入导致了死锁,这一过程的时间复杂度为O(n)。因此,1000个并发线程进行死锁检测时,会消耗大量CPU资源,严重影响数据库性能。这种热点行更新导致的性能问题有两种解决方案:

  • 1)如果能确定某个业务一定不会出现死锁,可以临时关闭死锁检测。这种操作存在一定的风险,关掉死锁检测可能会导致业务处理超时,对业务造成影响。
  • 2)控制并发度。并发控制一般在数据库服务端处理,如果数据库有接入相关中间件,可以考虑在中间件中实现;否则需要修改MySQL源码,基本思路:对于相同行的更新,在进入引擎之前排队,这样在InnoDB内部就无需进行大量的死锁检测工作。

【阅读推荐】

更多精彩内容,如:

  • Redis系列
  • 数据结构与算法系列
  • Nacos系列
  • MySQL系列
  • JVM系列
  • Kafka系列

请移步【南秋同学】个人主页进行查阅。内容持续更新中......

【作者简介】

一枚热爱技术和生活的老贝比,专注于Java领域,关注【南秋同学】带你一起学习成长~


相关推荐

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...