两万字详解InnoDB的锁

本文将跟大家聊聊InnoDb的锁,以及如何分析和解决死锁问题,希望对大家有帮助哈 。

  1. 为什么需要加锁呢?
  2. InnoDB的七种锁介绍
  3. 一条SQL是如何加锁的
  4. RR隔离级别下的加锁规则
  5. 如何查看事务加锁情况
  6. 死锁案例分析
1. 为什么需要加锁?为什么需要加锁呢?
在日常生活中,如果你心情不好想静静,不想被比别人打扰,你就可以把自己关进房间里,并且反锁 。
同理,对于MySQL数据库来说的话,一般的对象都是一个事务一个事务来说的 。所以,如果一个事务内,正在写某个SQL,我们肯定不想它被别的事务影响到嘛?因此,数据库设计大叔,就给被操作的SQL加上锁 。
专业一点的说法: 如果有多个并发请求存取数据,在数据就可能会产生多个事务同时操作同一行数据 。如果并发操作不加控制,不加锁的话,就可能写入了不正确的数据,或者导致读取了不正确的数据,破坏了数据的一致性 。因此需要考虑加锁 。
1.1 事务并发存在的问题
  • 脏读:一个事务A读取到事务B未提交的数据,就是脏读 。
  • 不可重复读:事务A被事务B干扰到了!在事务A范围内,两个相同的查询,读取同一条记录,却返回了不同的数据,这就是不可重复读 。
  • 幻读:事务A查询一个范围的结果集,另一个并发事务B往这个范围中插入/删除了数据,并静悄悄地提交,然后事务A再次查询相同的范围,两次读取得到的结果集不一样了,这就是幻读 。
1.2 一个加锁和不加锁对比的例子我们知道MySQL数据库有四大隔离级别读已提交(RC)、可重复读(RR)、串行化、读未提交 。如果是读未提交隔离级别,并发情况下,它是不加锁的,因此就会存在脏读、不可重复读、幻读的问题 。
为了更通俗易懂一点,还是给大家举个例子吧,虽然东西挺简单的 。假设现在有表结构:
CREATE TABLE `account` (`id` int(11) NOT NULL,`name` varchar(255) DEFAULT NULL,`balance` int(11) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `un_name_idx` (`name`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert into account(id,name,balance)values (1,'Jay',100);insert into account(id,name,balance)values (2,'Eason',100);insert into account(id,name,balance)values (3,'Lin',100);在READ-UNCOMMITTED(读未提交) 隔离级别下,假设现在有两个事务A、B:
  • 假设现在Jay的余额是100,事务A正在准备查询Jay的余额
  • 这时候,事务B先扣减Jay的余额,扣了10
  • 最后A 读到的是扣减后的余额

两万字详解InnoDB的锁

文章插图
 
手动验证了一把,流程如下:
两万字详解InnoDB的锁

文章插图
 
由上图可以发现,事务A、B交替执行,事务A被事务B干扰到了,因为事务A读取到事务B未提交的数据,这就是脏读 。这是因为在读未提交的隔离级别写操作,并没有对SQL加锁,因此产生了脏读这个问题 。
我们再来看下,在串行化隔离级别下,同样的SQL执行流程,是怎样的?
两万字详解InnoDB的锁

文章插图
 
为啥会阻塞等待超时呢?这是因为串行化隔离级别下,对写的SQL加锁啦 。我们可以看下加了什么锁,命令如下:
SET GLOBAL innodb_status_output=ON; -- 开启输出SET GLOBAL innodb_status_output_locks=ON; -- 开启锁信息输出SHOW ENGINE INNODB STATUS锁相关的输出内容如下:
两万字详解InnoDB的锁

文章插图
 
我们可以看到了lock_mode X locks rec but not gap,它到底是一种什么锁呢?我们一起来学习下InnoDB的七种锁 。
2. InnoDB的七种锁介绍
两万字详解InnoDB的锁

文章插图
 
2.1 共享/排他锁InnoDB呢实现了两种标准的行级锁:共享锁(简称S锁)、排他锁(简称X锁) 。
  • 共享锁:简称为S锁,在事务要读取一条记录时,需要先获取该记录的S锁 。
  • 排他锁:简称X锁,在事务需要改动一条记录时,需要先获取该记录的X锁 。
如果事务T1持有行R的S锁,那么另一个事务T2请求访问这条记录时,会做如下处理:


推荐阅读