【44期】MySQL行锁和表锁的含义及区别

2023-09-29 10:06:49 浏览数 (1)

一、前言

对于行锁和表锁的意义差异,在面试当中可能出现得频率较高,我们应对MySQL中的锁有一个体系化的了解,更详尽的内容需要自行查找相关资料,本文仅精要总结回答。

MySQL常用的引擎有MyISAM和InnoDB,而InnoDB是mysql预设的引擎。MyISAM不允许行级锁定,然而InnoDB则支持行级锁定和表级锁定。

如何加锁?

MyISAM在执行查询(SELECT)之前,会自动为所有涉及的表添加读锁,在执行更新操作(如UPDATE、DELETE、INSERT等)之前,会自动为涉及的表添加写锁。这个过程不需要用户进行干预,因此通常用户不需要使用LOCK TABLE命令显式为MyISAM表添加锁定。

显式加锁:

上共享锁(读锁)的写法:lock in share mode,例如:

代码语言:javascript复制
select  math from zje where math>60 lock in share mode;

上排它锁(写锁)的写法:for update,例如:

代码语言:javascript复制
select math from zje where math >60 for update;

二、表锁

不会出现死锁,发生锁冲突几率高,并发低。

MyISAM引擎

MyISAM在执行查询语句(select)之前,会自动为涉及的所有表添加读锁,在执行增删改操作之前,会自动为涉及的表添加写锁。

MySQL的表级锁存在两种模式:

  • 表共享读锁
  • 表独占写锁

读锁会阻塞写,写锁会阻塞读和写

  • 对MyISAM表的读操作,不会阻塞其它进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。
  • 对MyISAM表的写操作,会阻塞其它进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作。

MyISAM不适合做写为主表的引擎,因为写锁后,其它线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞

三、行锁

会出现死锁,发生锁冲突几率低,并发高。

在MySQL中,InnoDB引擎提供了行锁的支持。与Oracle不同,MySQL的行锁是基于索引的加载的,也就是说,行锁是添加在索引所对应的行上的。如果对应的SQL语句没有使用索引,那么将会进行全表扫描,这时行锁将无法生效,取而代之的是表锁,此时其他事务将无法对当前表进行更新或插入操作。

代码语言:javascript复制
CREATE TABLE `user` (
  `name` VARCHAR(32) DEFAULT NULL,
  `count` INT(11) DEFAULT NULL,
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8

-- 这里,我们建一个user表,主键为id



-- A通过主键执行插入操作,但事务未提交
update user set count=10 where id=1;
-- B在此时也执行更新操作
update user set count=10 where id=2;
-- 由于是通过主键选中的,为行级锁,A和B操作的不是同一行,B执行的操作是可以执行的



-- A通过name执行插入操作,但事务未提交
update user set count=10 where name='xxx';
-- B在此时也执行更新操作
update user set count=10 where id=2;
-- 由于是通过非主键或索引选中的,升级为为表级锁,-- B则无法对该表进行更新或插入操作,只有当A提交事务后,B才会成功执行

for update

如果在一条select语句后加上for update,则查询到的数据会被加上一条排它锁,其它事务可以读取,但不能进行更新和插入操作

代码语言:javascript复制
-- A用户对id=1的记录进行加锁
select * from user where id=1 for update;

-- B用户无法对该记录进行操作
update user set count=10 where id=1;

-- A用户commit以后则B用户可以对该记录进行操作

行锁的实现需要注意:

  1. 行锁必须有索引才能实现,否则会自动锁全表,那么就不是行锁了。
  2. 两个事务不能锁同一个索引。
  3. insert,delete,update在事务中都会自动默认加上排它锁。

行锁场景:

当A用户进行消费时,服务层首先需要查询该用户的账户余额。如果余额足够,才可以进行后续的扣款操作。为了避免出现B用户在A用户查询后、消费前将A用户账号上的钱转走的情况,需要对该记录进行加锁。这样,在A用户操作该记录时,可以使用"for update"来进行加锁。这样即可确保在A用户进行判断余额是否足够时,不会出现余额已经不足但扣款成功的情况发生。

扩展:间隙锁

当我们使用范围条件而不是相等条件来检索数据,并且请求共享或排他锁时,InnoDB会对符合条件的已有数据记录的索引项进行加锁。对于在条件范围内但实际上不存在的记录,也就是我们所说的"间隙",InnoDB同样会对这个间隙进行加锁。这种锁机制被称为间隙锁。

代码语言:javascript复制
-- 用户A
update user set count=8 where id>2 and id<6

-- 用户B
update user set count=10 where id=5;

假若用户A在执行了上述操作后,事务仍未提交,则用户B将无法对范围在2至6之间的记录进行更新或插入操作,会发生阻塞。直到用户A提交事务后,用户B的更新操作才会被执行。

建议:

  • 尽量使得所有数据查询都经由索引来完成,避免无索引行锁升级为表锁
  • 合理规划索引,尽量缩小锁的范围
  • 尽量减少索引筛选条件,规避间隙锁
  • 尽量限制事务规模,减少锁定资源数量和时间长度

0 人点赞