关于后端:Mysq系列锁

5次阅读

共计 5682 个字符,预计需要花费 15 分钟才能阅读完成。

Mysq 系列 - 锁

前言

面试过程中,对于锁知识点的考查,也是经常出现在面试过程中,常见面试题大略有这么几个,小伙伴们试下是否是否答复进去吧

  • Mysql 数据库的锁机制,锁分类
  • Mysql 数据库的粒度
  • 乐观锁和乐观锁是怎么实现
  • 什么是间隙锁
  • 给小表增加字段须要留神什么货色

Mysql 设计锁这个玩意目标是解决多线程对共享资源的拜访,在 Mysq 体系中咱们晓得解决并发访问共享资源的办法个别是 加锁 事务,也是 InnoDB 和 MySiam 最重要的不同点,明天咱们一起钻研下锁相干的常识。

锁分类

全局锁

概念

​ 全局锁就是对整个数据库实例加锁,让整个数据库只读。一但加上全局锁之后,之后其余线程的一下语句就会被阻塞: 数据更新语句(数据正删改),数据定义语句(建表,修好表的后果),更新事务的提交语句都会阻塞在那等着吧,等我完事你们再来。

应用场景

全局锁的应用场景次要用来做全库的逻辑备份,如果不加全局锁,就会造成备份数据的一致性呈现问题。因为备份零碎失去的数据库不是在一个逻辑工夫点上,视图逻辑不统一,小伙伴请看上面这个例子

  • 备份零碎备份余额表 -> 用户原零碎购买插入订单表 -> 备份零碎备份订单表 // 对应状况是 余额没扣,订单表多出一条数据
  • 备份零碎备份订单表 -> 用户原零碎购买插入订单表扣除余额 -> 备份零碎备份余额表 // 对应状况是 余额扣了, 订单没数据,线上零碎这样搞岂不要滚犊子
如何加全局锁
  1. set global readonly=true,数据库处于只读状态,更新数据你就进不来
  2. Mysql 官网自带的逻辑备份数据mysqldump,应用参数 -single -transaction 时候,会在开始时候启动一个事务,拿到一致性视图(事务下一篇咱们就开始了,小伙伴这里只需有个概念,一致性视图就是事务在我之前的认,在我之后的我就不认,你在我之后更新删除没啥用,我不认你)。数据库在备份的时候,举荐这个
  3. FTWRL(Flush table with read lock) Mysql 提供的一个命令,性能就是让整个数据库处于只读状态,小伙伴必定问了,这不是和 set global readonly=true 一个意思,enen,还是有点不一样,不一样点次要有以下几点

    1. 客户端产生异样的话,FTWRL 会主动开释全局锁。
    2. set global readonly=true 数据库读写拆散的时候,主库写从库读,咱们在一些逻辑操作的时候可能会用这个判断是不是从库。

表级锁

概念

​ Mysql 表级别的锁会将整个表锁住,锁粒度比拟大,表级别锁次要分为两种,一种是表锁,一种是元数据锁(meta data lock,MDL)

分类
表锁

如何加表锁

lock tables tb1 read/write;
// do something
unlock tables;//lock tables unlock tables 会被动以后会话中的所有表锁

这个有个留神的点须要说下,lock tables 除了会限度别的线程读写之外,也会限度本线程的一些操作,啥意思?上个例子

  • 线程 A 执行 lock table t1 read,t2 write这个语句,则其余线程写 t1 表,读写 t2 的都会被阻塞
  • 线程 A 执行 unlock tables 之前,也只能读 t1,读写 t2 的操作,这个时候你线程 A 写 t1 也不行,天然不能拜访其余表
MDL(元数据锁)
  • 当对一个表进行增删改查操作的时候,会加 MDL 读锁;当对表构造变更操作的时候,会加 MDL 写锁
  • MDL 的读写锁,写锁之间是互相互斥,两个线程同时给一个表增加字段,前面的要期待后面一个执行完才开始执行
  • MDL 元数据锁数据提交才会开释,应用 元数据锁的时候留神在表变更的时候会阻塞表的相干查问以及更新语句(ps 面试时候能够唠唠的点是如何平安给小表增加字段这个例子)

业务场景:表数据量尽管小,却是一个热点表,拜访频率特地高,而且该表的拜访是在一个大事务中。加字段的时候始终在期待获取 MDL 写锁。这个期待也影响了后续表拜访对 MDL 读锁的获取,导致前面的查问也都被梗塞了。更惨的是,客户端有重试机制,查问梗塞超过超时工夫会再起一个 session 进行申请,导致数据库的线程池很快就爆满了,间接挂掉。

如何给小表增加字段

  • 没加之前,解决点长事务(始终持有读锁),增加字段会始终期待
  • 表变更语句 alter table 设置等待时间(没有拿到锁的时候主动开释)
查问表级锁争用

​ 这里有两个参数能够查看到表级锁的竞争状况,咱们执行上面这个命令,会呈现这两个参数

show STATUS like '%table%'
  • Table_locks_immediate:可能立刻获取表级锁的次数
  • Table_locks_waited:不能立刻获取表级锁而须要期待的次数(这个值越大,阐明表级别锁竞争越大)

行级锁

行锁就是针对数据表中行记录的锁。这很好了解,比方事务 A 更新了一行,而这时候事务 B 也要更新同一行,则必须等事务 A 的操作实现后能力进行。Mysql 行级锁是由各个引擎实现的,并不是所以的引擎都反对行级锁,比方 MyISAM 引擎不反对行级锁,咱们解决并发的时候只能应用表级锁,这是 MySIAM 被 InnoDB 取代的起因之一。InnoDB 反对行锁,对于 InnoDB 行锁须要记住以下几点

  1. InnoDB 的行锁是针对索引加的锁,不是针对记录加的锁,如果索引生效状况话,行锁会降级为表锁
  2. InnoDB 事务中,行锁是须要的时候才加上,但并不是不须要了就立即开释,须要等到事务执行完结之后才会开释,也就是咱们常说的两阶段锁协定

缩小行锁对性能下面的影响

两阶段锁协定一个利用:如何缩小锁等待时间:将影响并发度的操作尽量往后放,假如你负责实现一个电影票在线交易业务,顾客 A 要在影院 B 购买电影票。咱们简化一点,这个业务须要波及到以下操作:

  1. 从顾客 A 账户余额中扣除电影票价;
  2. 给影院 B 的账户余额减少这张电影票价;
  3. 记录一条交易日志。
  4. 两阶段锁协定是事务提交的时候才会开释锁,咱们能够将 3,1,2 这三个操作放在一个事务中。可能出抵触的是给影院账户余额,这个操作,所以依照 3,1,2 的程序大大减少锁等待时间,进步并发

咱们创立一个表, 表中 id 不设置索引,咱们通过两个窗口查问数据,咱们会发现,行级锁会变成表锁

CREATE TABLE `user`  (`id` int(11) NOT NULL  COMMENT 'id 值',
  `age` int(11) NULL DEFAULT NULL COMMENT '年龄'
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
insert INTO USER(id,age) value(1,1);
insert INTO USER(id,age) value(2,2);
insert INTO USER(id,age) value(3,3);

session A 查问 id= 1 这一列,因为没有索引,所以行级锁变成表级锁,锁住整个表

Session B 查问 id= 2 的时候,因为 session A 持有标记锁,session B 只能期待,看那个红框,始终转圈圈

接下来一起剖析一下 Mysql 行级锁中的共享锁和排他锁

共享锁 / 排他锁
  • 共享锁又称为读锁,多个事务进行读操作能够同时进行而不会相互影响,然而只能读不能批改
  • 排他锁又称为写锁,一个事务获取一个数据行的排他锁,其余事务不能再获取这一行的其余锁,不论是读锁和写锁,其余事务不能在这一行上加读锁和写锁,只能以后事务对数据读取和批改,然而其余事务能够通过 select 语句去查,因为 select 是不加任何锁的
  • select 语句自身是默认不加任何锁的,如果须要加排他锁,能够应用 select … for update 语句,加共享能够通过 select … lock in share mode
  • Mysql 中的更新语句 (update/delete/insert) 会主动加上排他锁
死锁和死锁检测

并发零碎中不同线程呈现循环资源依赖,波及的线程都在期待别的线程开释资源时,就会导致这几个线程都进入有限期待的状态,称为死锁

<img src=”https://4k-images.oss-cn-beijing.aliyuncs.com/m2img/2019/05/202202281529139.png” alt=”image-20220228152920152″ style=”zoom: 25%;” />

这时候,事务 A 在期待事务 B 开释 id=2 的行锁,而事务 B 在期待事务 A 开释 id=1 的行锁。事务 A 和事务 B 在相互期待对方的资源开释,就是进入了死锁状态。当呈现死锁当前,有两种 策略:

  • 一种策略是,间接进入期待,直到超时。这个超时工夫能够通过参数 innodb_lock_wait_timeout 来设置(超时工夫设置)。
  • 一种策略是,发动死锁检测(减少零碎),发现死锁后,被动回滚死锁链条中的某一个事务,让其余事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,示意开启这个逻辑。
间隙锁

间隙锁是 MySQL在 RR 可反复读隔离级别下用来修复幻读才引入的一种锁,间隙锁也只有在 RR 可反复读隔离级别下才会存在,如果是在 RC 读已提交隔离级别下,是没有间隙锁的存在的。另外,咱们也晓得,幻读这种景象也只有在以后读的时候才会产生,在一致性快照读的状况下是没有幻读景象的。

1 CREATE TABLE `t` (`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5), (10,10,10),(15,15,15),(20,20,20),(25,25,25);

间隙锁,锁的就是两个值之间的空隙。比方文章结尾的表 t,初始化插入了 6 个记 录,这就产生了 7 个间隙。当你执行 select * from t where d=5 for update 的时候,就不止是给数据库中已有的 6 个记录加上了行锁,还同时加了 7 个间隙锁。这样就确保了无奈再插入新的记录。

当咱们程序范畴查问的申请共享和排他锁的时候,InnoDB 会给符合条件的已有记录的索引项增加锁,对于键值对在范畴内然而不存在的记录,InnoDB 会给这个间隙增加锁,也就是咱们常说的间隙锁。(PS 间隙锁解决 Mysql 幻读问题)

<img src=”https://cdn.nlark.com/yuque/0/2021/png/2616318/1611918381086-a88b5710-5eff-4a4c-9909-69e5b6e1c0bd.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_52%2Ctext_ZnJvbSBsaW15bmw%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10″ alt=”img” style=”zoom:33%;” />

如上图,给 id>5 中并不存在的数据加上了间隙锁,当插入 id= 6 的数据时被阻塞了。这是一个坑:「若执行的条件是范畴过大,则 InnoDB 会将整个范畴内所有的索引键值全副锁定,很容易对性能造成影响」

加锁规定

锁的两种机制

锁有两种机制,乐观锁和乐观锁,是两种常见的资源并发锁设计思路,有这两个玩意的次要为了解决数据失落问题和脏读问题

  • 数据失落的情景 A 先更新一条 id= 1 的数据,B 后而后也更新 id= 1 的数据,B 把 A 更新的数据笼罩
  • 脏读:A,B 看到的值都是 10,B 把 10 改成 5,然而 A 看到的值还是 6

乐观锁

​ 每次他人提交拜访数据的时候,认为这次拜访扭转数据的概率很低,不加锁,只有数据提交的时候,数据库抵触的话才会将数据锁住。这样能够防止期待长事务数据库加锁开销,进步零碎的并发性,乐观锁不能解决脏读问题。

实现形式
  1. 基于版本号实现,当进行数据读操作的时候,将提交数据的版本信息和数据库对应记录的以后版本信息进行比照,若是提交的数据版本大于数据库的版本,进行更新,小于的话就认为是过期数据,数据不进行更改。
  2. 基于工夫戳实现,数据库减少工夫戳字段,进去数据更新的时候,比拟是不是和本人开始的时候查的工夫戳是否统一,如果统一代表没有其余数据进行操作,能够进行这次操作

乐观锁

乐观锁工作对数据库的每一次操作都会数据进行扭转,所以在一开始就会将数据锁住,乐观锁往往依附于数据库提供的锁机制,比方行锁,表锁来保证数据的排他性。

Q:这里问小伙伴一个问题,乐观锁加锁形式,你说说乐观锁会加锁嘛

A:乐观锁自身是不加锁的,只是更新的时候通过版本号什么的去比拟数据有没有被其余线程扭转。在理论开发过程中,可能在提交之间应用 select for update 语句做一下查看,而后再用 Update 提交

用动静的观点看加锁

锁优化

  • 准则 1: 加锁的根本单位是 next-key lock。心愿你还记得,next-key lock 是前开后闭区间。
  • 准则 2: 查找过程中拜访到的对象才会加锁。
  • 优化 1: 索引上的等值查问,给惟一索引加锁的时候,next-key lock 进化为行锁。
  • 优化 2: 索引上的等值查问,向右遍历时且最初一个值不满足等值条件的时候,next-key lock 进化为间隙锁。
  • 一个 bug: 惟一索引上的范畴查问会拜访到不满足条件的第一个值为止。

伟人肩膀

https://www.cnblogs.com/boblogsbo/p/5602122.html

https://www.cnblogs.com/jian0110/p/12721924.html

https://time.geekbang.org/column/intro/100020801

https://blog.51cto.com/qinbin/1968612

https://blog.csdn.net/sdyy321/article/details/6183412/

喜爱的小伙伴点击头像关注一下呀

正文完
 0