MySQL - 深入理解锁机制和实战场景

MySQL 为了解决事务并发、数据安全的问题,提供并使用了锁机制,下面先看一张图,了解一下 MySQL 有哪些锁:
在这里插入图片描述
接下来,一起深入学习 MySQL 的锁机制。

1. 锁的分类

1.1 行级锁

行级锁应用在 InnoDB 存储引擎中,每次锁住一行数据。

  • 优点:锁定粒度小,发生锁冲突的概率最低,并发度最高。
  • 缺点:开销大,加锁慢;会出现死锁。

1.2 页级锁

页级锁应用在 DBD 存储引擎中,每次锁住一页数据 - 16KB左右。

  • 特点:开销和加锁时间介于表级和行级之间;会出现死锁,锁定力度介于表锁和行锁之间,并发度一般。

1.3 表级锁

表级锁应用在 MyISAM、InnoDB、BDB 等存储引擎中,每次操作锁住整张表。

  • 优点:开销小,加锁快;不会出现死锁。
  • 缺点:锁定粒度大,发生锁冲突的概率最高,并发度最低。

在这里插入图片描述

2. 行级锁的分类

接下来进行行级锁的详解,行级锁主要分为以下7类:共享/排他锁、意向锁、记录锁、间隙锁、临建锁、插入意向锁、自增锁。

2.1 共享锁、排他锁

共享锁(S锁):共享锁(Share Locks,简记为S锁)又被称为读锁,其他事务可以并发读取数据,但任何事务都不能获取数据上的排他锁(只能加共享锁,不能加排他锁),直到已释放所有共享锁。

若事务 T 对数据对象 A 加上 S锁,则事务 T 只能读 A;其他事务只能再对 A 加 S锁,而不能加 X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

select ... lock in share mode;

在这里插入图片描述

排他锁(X锁):排它锁((Exclusive lock,简记为X锁))又称为写锁,不能允许读,也不能允许写,排他锁不能与其他锁一起使用。

若事务T对数据对象 A 加上 X锁,则只允许 T 读取和修改 A,其它任何事务都不能再对 A 加任何类型的锁,直到 T 释放 A 上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。

select ... for update;

在这里插入图片描述

MySQL 中,updatedeleteinsertalter 这些写的操作默认都会加上排他锁。select 默认不会加任何锁类型。一旦写数据的任务没有完成,数据是不能被其他任务读取的,这对并发操作有较大的影响。

2.2 意向锁

InnoDB 为了支持多粒度的锁,即允许行级锁和表级锁共存,而引入意向锁。意向锁是指未来的某个时刻,事务可能要加共享/排他锁,先提前声明一个意向。这样如果有人尝试对全表进行修改,就不需要判断表中的数据是否被加锁了,只需要通过等待意向互斥锁被释放就行了。

意向共享锁(IS):事务想要在获得表中某些记录的共享锁,需要在表上先加意向共享锁。

意向互斥锁(IX):事务想要在获得表中某些记录的互斥锁,需要在表上先加意向互斥锁。

意向锁其实不会阻塞全表扫描之外的任何请求,它们的主要目的是为了表示是否有人请求锁定表中的某一行数据。

2.3 记录锁(RS)

单个行记录上的锁。记录锁总是会锁住索引记录,如果 InnoDB 存储引擎表

在建立的时候没有设置任何一个索引,那么InnoDB存储引擎会使用隐式的主键来进行锁定。

2.4 间隙锁(GR)

间隙锁锁住记录中的间隔,即范围查询的记录。

select * From user where id between 1 and 10 for update;

这个脚本会锁住 1 到 10 的数据,以防止其他事务修改该区间的记录;

间隙锁的主要目的,就是为了防止其他事务在间隔中插入数据,以导致“不可重复读”。如果把事务的隔离级别降级为读提交(Read Committed, RC),间隙锁则会自动失效。

2.5 临建锁(next-key Locks)

临建锁是记录锁和间隙锁的组合,锁的范围既包含记录又包含索引区间。默认情况下,InnoDB使用临建锁来锁定记录。但当查询的索引含有唯一属性的时候,临建锁会进行优化,将其降级为记录锁,即仅锁住索引本身,不是范围。

临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为 RC,临键锁则也会失效。

2.6 插入意向锁(insert intention locks)

对已有数据行的修改和删除,必须加互斥锁,对于数据的插入,加插入意向锁。是专门针对于 insert 操作的。

2.7 自增锁(auto-inc locks)

是一种特殊的表级别的锁,专门针对事务插入 auto-increment 类型的列。最简单的情况,如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值。

3. 悲观锁和乐观锁

3.1 悲观锁

悲观的假定大概率会发生并发更新冲突,访问、处理数据前就加排他锁,在整个数据处理过程中锁定数据,事务提交或回滚后才释放锁。

  • 优点:

悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。

  • 缺点:

(1)在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;

(2)在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数

案例:

  1. 假定 MySQL 有客户端A 与客户端B 同时开启事务

  2. 在 A 里面进行查询数据:select * from tab where id = 1 for update;

  3. 由于 B端 还没有提交事务,A端 则会在执行这条 SQL 语句时锁住(卡住不动),B端 结束了事务,A端 才会继续执行 SQL。

3.2 乐观锁

乐观的假定大概率不会发生并发更新冲突,访问、处理数据的过程中不加锁,只在更新数据时根据版本号时间戳判断是否有冲突,有则处理,无责提交事务。

如果系统并发量非常大,悲观锁会带来非常大的性能问题,选择使用乐观锁,现在大部分应用属于乐观锁
在这里插入图片描述
下单案例:

  1. 查询商品信息
select (quantity,version) from products where id=1;
  1. 根据商品信息生成订单
insert into orders ... 
insert into items ...
  1. 修改商品库存
update products set quantity = quantity - 1,version = version +  1 where id = 1 and version = #{version};

除了自己手动实现乐观锁之外,许多数据库访问框架也封装了乐观锁的实现,比如 hibernate 框架。MyBatis 框架大家可以使用 OptimisticLocker 插件来扩展。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

没对象的指针

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值