全局锁

全局锁是针对数据库实例的直接加锁,MySQL 提供了一个加全局锁的方法, Flush tables with read lock 可以使用锁将整个表的增删改操作都锁上其中包括 ddl 语句,只允许全局读操作。

全局锁的典型使用场景是做全库的逻辑备份。

不过现在使用官方自带工具 mysqldump 使用参数 --single-transaction 的时候,导出数据之前就会启动一个事务。来确保拿到一致性视图。这个应该类似于在可重复读隔离级别下启动一个一致性事务。由于 MVCC 的支持,这个过程中数据可以正常更新。

另外提一点不太容易遇到的, --single-transaction 既然可以不用锁表,为什么还需要使用全局锁?原因是 --single-transaction 的时候需要支持一致性读,但是不支持事务的引擎是不支持一致性读的。这个时候就需要 FTWRL 命令了。

还有另外一种方法用来支持设置数据库为只读状态

set global readonly=true

这里 丁奇 不建议这样设置来设置数据库为只读有两个原因

一是,在有些系统中,readonly 的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。因此,修改 global 变量的方式影响面更大,我不建议你使用。

二是,在异常处理机制上有差异。如果执行 FTWRL 命令之后由于客户端发生异常断开,那么 MySQL 会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。

表级锁

MySQL 中表级别锁有两种:一种是普通表锁,一种是元数据锁(metadata lock. MDL)

表锁的语法是 lock tables xxx read/write 同样使用 unlock tables 来释放锁。通过加读锁我们可以限制其他语句进行写入,但是重复加读锁不受影响。但是当我们加写锁的时候,既不可以读也不可以写。同样在使用 unlock tables 之后可以解除锁定。

另外一种表级锁是 MDL 锁(metadata lock) MDL 锁不需要显示的使用,在访问一个表的时候自动就被加上了。 MDL 锁是用来保证读写正确性的,当我们对一个表在做 增删改查操作的时候都会被加上 MDL 读锁。当要进行 ddl 的时候需要加 MDL 写锁。

MDL 读锁与读锁之间不互斥,因此我们可以多个线程进程对一个表进行增删改查。

MDL 读写锁之间互斥,用来保证表结构变更的安全性。因此如果有两个线程同时要给同一个表加字段,其中一个要等另外一个执行完成之后再开始执行。

下面我们来看一个比较有代表性的场景 MDL 读锁写锁互斥导致表无法读写被死锁。

session A: 开始一个事务,然后查询 t 表,这会给 t 表加上 MDL 读锁。(注意该事务被打开后就一直没有结束)

session B: 查询一个 t 表。这里应该是 autoocommit 会自动成功。

session C: 修改表 ddl 会加 MDL 写锁,和 session A 的读锁互斥。这个时候就锁住了表。

session D: 由于 session C 造成了写锁阻塞,所以后面所有的请求都会被锁住。

如果该表查询频繁,而且客户端有重试的机制,那么这个数据库的查询线程会很快被打满。

可能在进行 web 开发的同学会经常遇到类似的情况。比如我在 ipython 里面打开了一个数据库某个表的连接,然后我一直没有 commit 。就可能造成该表在加写锁的时候阻塞后面所有的操作。

这种事情非常常见。

那么我们如何安全的给小表加字段,首先我们应该解决长事务或者脚本事务的问题,因为他们会一直挂读锁不结束。在 MySQL 的 information_schema 中的 innodb_trx 中可以查询到执行中的长事务,但是比较麻烦的是这个看不到很短的事务。但是往往进行 sleep 的短事务也可能因为一直没有 commit 而导致上面的情况出现。

这个时候就需要把对应表的 sleep 进程 kill 掉使其恢复正常。

行级锁

先来看个描述两阶段锁的例子:

事务 A 会持有两条记录的行锁,并且只会在 commit 之后才会释放。

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

知道了这个设定我们应该在长事务中把影响并发度的锁尽量往后放。下面的这一段的介绍比较复杂,我觉得 丁奇 讲得还是比较清楚的所以直接引用原文了。

假设你负责实现一个电影票在线交易业务,顾客 A 要在影院 B 购买电影票。我们简化一点,这个业务需要涉及到以下操作:

1. 从顾客 A 账户余额中扣除电影票价;

2. 给影院 B 的账户余额增加这张电影票价;

3. 记录一条交易日志。

也就是说,要完成这个交易,我们需要 update 两条记录,并 insert 一条记录。当然,为了保证交易的原子性,我们要把这三个操作放在一个事务中。那么,你会怎样安排这三个语句在事务中的顺序呢?

试想如果同时有另外一个顾客 C 要在影院 B 买票,那么这两个事务冲突的部分就是语句 2 了。因为它们要更新同一个影院账户的余额,需要修改同一行数据。

根据两阶段锁协议,不论你怎样安排语句顺序,所有的操作需要的行锁都是在事务提交的时候才释放的。所以,如果你把语句 2 安排在最后,比如按照 3、1、2 这样的顺序,那么影院账户余额这一行的锁时间就最少。这就最大程度地减少了事务之间的锁等待,提升了并发度。

死锁和死锁检测

如果出现下面的不慎操作就会发生死锁。

事务 A 开启事务,并且拿了 id = 1 的行锁。

事务 B 开启事务,拿到 id = 2 的行锁。

事务 A 试图去拿 id = 2 的行锁被 block。

事务 B 试图去拿 id = 1 的行锁被 block。

解决死锁 MySQL 目前有两种策略,第二种策略的参数我没有在 MySQL 5.6 版本中找到,在 MySQL 5.7 中找到。

1. 一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置。这个参数默认是 50s。

2. 另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。

很显然,等待 50 s 失效在现实业务中是不切实际的。肯定会造成高并发的业务大量的阻塞和 500 。所以看上去我们可以依赖第二种办法?

但是第二种办法也有副作用。

死锁检测会对每个新来的被堵住的线程,都判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是 O(n) 的操作。假设有 1000 个并发线程要同时更新同一行,那么死锁检测操作就是 100 万这个量级的。虽然最终检测的结果是没有死锁,但是这期间要消耗大量的 CPU 资源。因此,你就会看到 CPU 利用率很高,但是每秒却执行不了几个事务。

解决这个的方法是

1. 如果我们能确保业务中就是不会存在死锁的逻辑,那么我们可以关闭死锁检测。

2. 我们控制并发度,不让某些业务更新这么快。对客户端的并发控制下来之后,死锁检测的效率是高的,也可以解决这个问题。

Reference:

本读书笔记皆来自发布在极客时间的 林晓斌(丁奇)的 MySQL 实战45讲:

极客时间版权所有: https://time.geekbang.org/ 版权所有:

https://time.geekbang.org/column/article/69862

https://time.geekbang.org/column/article/70215

【MySQL 读书笔记】全局锁 | 表锁 | 行锁的更多相关文章

  1. MySQL 笔记整理(7) --行锁功能:怎么减少行锁对性能的影响?

    笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> 7) --行锁功能:怎么减少行锁对性能的影响? MySQL的行锁是在引擎层由各个引擎自己实现的.因此,并不是所有的引擎都支持行锁,如 ...

  2. mysql死锁-查询锁表进程-分析锁表原因【转】

    查询锁表进程: 1.查询是否锁表 show OPEN TABLES where In_use > 0;   2.查询进程     show processlist   查询到相对应的进程===然 ...

  3. mysql--->innodb引擎什么时候表锁什么时候行锁?

    mysql innodb引擎什么时候表锁什么时候行锁? InnoDB基于索引的行锁 InnoDB行锁是通过索引上的索引项来实现的,这一点MySQL与Oracle不同,后者是通过在数据中对相应数据行加锁 ...

  4. MySQL中锁详解(行锁、表锁、页锁、悲观锁、乐观锁等)

    悲观锁: 顾名思义,很悲观,就是每次拿数据的时候都认为别的线程会修改数据,所以在每次拿的时候都会给数据上锁.上锁之后,当别的线程想要拿数据时,就会阻塞,直到给数据上锁的线程将事务提交或者回滚.传统的关 ...

  5. MySQL高级知识(十四)——行锁

    前言:前面学习了表锁的相关知识,本篇主要介绍行锁的相关知识.行锁偏向InnoDB存储引擎,开销大,加锁慢,会出现死锁,锁定粒度小,发生锁冲突的概率低,但并发度高. 0.准备 #1.创建相关测试表tb_ ...

  6. MySQL性能优化(七·下)-- 锁机制 之 行锁

    一.行锁概念及特点 1.概念:给单独的一行记录加锁,主要应用于innodb表存储引擎 2.特点:在innodb存储引擎中应用比较多,支持事务.开销大.加锁慢:会出现死锁:锁的粒度小,并发情况下,产生锁 ...

  7. mysql死锁-查询锁表进程-分析锁表原因

    查询锁表进程: 1.查询是否锁表 show OPEN TABLES where In_use > 0;   2.查询进程     show processlist   查询到相对应的进程===然 ...

  8. mysql锁机制之行锁(四)

    前言 顾名思义,行锁就是一锁锁一行或者多行记录,mysql的行锁是基于索引加载的,所以行锁是要加在索引响应的行上,即命中索引,如下图所示: InnoDB 支持多粒度锁(multiple granula ...

  9. mysql 开发进阶篇系列 9 锁问题 (Innodb 行锁实现方式)

    一.概述 Innodb 行锁是通过给索引上的索引项加锁来实现的.这一点与(oracle,sql server)不同后者是通过在数据块中对相应的数据行加锁.这意味着只有通过索引条件检索数据,innodb ...

随机推荐

  1. BaiduSpeechDemo【百度语音SDK集成】(基于v3.0.7.3)

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 本Demo将百度语音SDK(其中一部分功能)和自定义的UI对话框封装到一个module中,便于后续的SDK版本更新以及调用. 本De ...

  2. C#代码安装Windows服务(控制台应用集成Windows服务)

    最近在为一款C/S架构的科研软件开发云计算版,需要用到WCF,考虑到不需要什么界面以及稳定性,无人值守性,准备用Windows Service作为宿主,无奈Windows Service的安装太为繁复 ...

  3. GOF23种设计模式概括

    GOF23种设计模式分为三种: 创建型模式[工厂方法模式]结构型模式[(类)适配器模式]行为型模式[ 解释器模式,模板方法模式]   创建型模式Creational Patterns抽象工厂模式abs ...

  4. Manacher's Algorithm(马拉车算法)

    ## 背景 该算法用于求字符串的最长回文子串长度. ## 参考文章 >[最长回文子串——Manacher 算法](https://segmentfault.com/a/1190000003914 ...

  5. 高淇java300集JAVA面向对象的进阶作业

    一.选择题 1.使用权限修饰符(B)修饰的类的成员变量和成员方法,可以被当前包中所有类访问,也可以被它的子类(同一个包以及不同包中的子类)访问.(选择一项) Apublic B.protected C ...

  6. 【译】.NET Core 3.0 中的新变化

    .NET Core 3.0 是 .NET Core 平台的下一主要版本.本文回顾了 .Net Core 发展历史,并展示了它是如何从基本支持 Web 和数据工作负载的版本 1,发展成为能够运行 Web ...

  7. 解决select2 在modal中搜索框无效的问题

    $.fn.modal.Constructor.prototype.enforceFocus = function() {};

  8. 一看就能学会的H5视频推流方案

    本文由云+社区发表 作者:周超 导语 随着直播平台爆发式增长,直播平台从 PC 端转战移动端,紧跟着直播的潮流,自己学习实现了一套简单的 H5 视频推流的解决方案,下面就给小伙伴们分享一下自己学习过程 ...

  9. 第七课 路径与列表 html5学习2

    1.路径 一.相对路径1.同级路径2.下级路径 /3.上级路径 ../上一级路径 ../../上两级二.绝对路径 2.列表 列表特点;整齐.整洁.有序 一.无序列表语法格式<ul> < ...

  10. 生鲜配送管理系统_升鲜宝V2.0 供应商协同系统设计思想及设计效果展现(一)

    生鲜配送管理系统[升鲜宝]V2.0 供应商协同系统小程序设计思想及操作说明(一)     生鲜供应链企业,最重要的二个方面,其中一个是客户服务(销售订单)    另外一个就是供应商的管控,只有做好了这 ...