【重温mysql】六、InnoDB 加锁分析

InnoDB 为了保证并发能力,采起行级加锁策略。为了实现事务的隔离级别,InnoDB 中又引入了各类不一样的行级锁机制。不一样的加锁顺序、加锁类型、锁的多少以及影响范围将直接影响到整个事务执行效率与执行时间直接影响 MySQL 的吞吐能力,不恰当的加锁策略甚至有可能产生死锁,所以咱们又必要对整个过程有所了解。html

加锁策略与影响因素

InnoDB 采用了 B+ Tree 的数据结构与汇集索引的数据组织形式,索引在 InnoDB 引擎中占据了很是重要的位置,InnoDB 加锁过程就是对索引进行加锁的一个过程。在分析 InnoDB 加锁以前,咱们须要知道 InnoDB 加锁是和什么有关,这一点很是重要。对于不一样的事务隔离离别、不一样的列 InnoDB 采起的策略与使用的锁的类型都不同,影响加锁的因素有以下两种:mysql

  • 事务隔离级别,对于不一样的事务隔离级别,InnoDB 采起的策略不同。好比对于 select ... from 这类语句而言,因为 InnoDB 采起了一致性读策略,通常是不会加锁的,可是在Serialzable 级别,InnoDB 会对搜索过程当中遇到的二级索引加共享临键锁。对于Read Committed级别不会采起间隙锁的加锁策略。
  • 索引,因为InnoDB 采起了汇集索引的数据组织策略,所以对于主键和二级索引,它们的加锁过程是不一样的。对于主键索引只需对主键上进行加锁便可,而对于二级索引加锁后还需对其指向数据的主键进行加锁。
  • 加锁语句,InnoDB在不一样事务隔离级别下,对于不一样的加锁语句,采起的策略不一样。如对于update ... from ... 语句在Read Repeatable级别下使用了排他临键锁,而在Read Committed级别下使用的是排他行锁

基本加锁原则

对于 InnoDB 而言,虽然加锁的类别繁多,加锁形式也灵活多样,但也遵循了一些原则:sql

  • 对于select ... from ... 语句,使用快照读,通常状况下不加锁,仅在Serializable级别会加共享读锁
  • 对于select ... from ... lock in share mode语句使用当前读,加共享读锁(S锁)
  • 对于 select ... from ... for update语句,为当前读,加排他写锁(X锁)
  • 常见 DML语句(insert、delete、update),使用当前读,加排他写锁(X锁)
  • 常见 DDL语句(alter table,create table ...)等,加的是表级锁

接下来咱们将按照不一样的场景逐个不一样语句的加锁过程进行分析。以下为使用到的表格:segmentfault

CREATE TABLE `t_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `no` char(18) NOT NULL DEFAULT '' COMMENT '身份证',
  `name` varchar(50) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` int(4) NOT NULL DEFAULT '0' COMMENT '年龄',
  PRIMARY KEY (`id`),
  UNIQUE KEY `no` (`no`),
  KEY `name` (`name`)
) ENGINE=InnoDB COMMENT='用户表';
复制代码

默认插入数据以下:bash

id no name age
1 0001 张三 20
3 0003 李四 25
5 0005 王五 50
7 0007 王五 23
9 0009 赵六 28

Read Uncommitted 级别

Read Uncommitted 级别是事务隔离的最低级别,在此隔离级别下会存在脏读的现象,会影响到数据的正确性,所以咱们在平常开发过程当中不多使用该隔离级别。在此隔离级别下更新语句采起的是普通的加行锁的机制,Read Committed的加锁过程与Read Uncommitted一致。因为Read Committed使用范围较Read Uncommitted更广,在Read Committed级别下详细分析。数据结构

Read Committed 级别

Read Committed级别采起了一致性读策略,解决了事务的脏读问题,咱们如下简称为RC级别。在此级别下更新语句加锁与Read Uncommitted一致,可能存在的锁有行锁意向锁。加锁过程采起了Semi-consistent read优化策略,对于扫描过的数据如若不匹配,加锁后会当即释放。并发

使用主键

假设咱们须要在上述t_user表格中,删除ID=7的王五这一条记录,语句为:性能

delete from t_user where id = 7;
复制代码

因为使用了主键,只需对该条记录加X锁便可,其加锁过程以下: 优化

使用惟一索引

假设咱们经过身份证no这个惟一索引来删除id=7这条数据会如何加锁呢?ui

delete from t_user where no = '0007';
复制代码

因为惟一索引为二级索引,Innodb 首先经过惟一索引对数据进行过滤,对于0007惟一索引加X锁,而后还须要在汇集索引上对主键=7的数据进行加X锁。

使用非惟一索引

假设咱们使用非惟一索引,那么状况又会如何呢?

delete from t_user where name = '王五';
复制代码

因为惟一索引为二级索引,Innodb 首先经过索引对数据进行过滤,对于王五的两条索引加X锁,而后还须要在汇集索引上对主键=5,7 的数据进行加X锁。

未使用任何索引

若是不使用任何索引,状况会是怎样呢?

delete from t_user where age = 23;
复制代码

因为删除语句没有使用任何索引,那么 InnoDB 必须进行全表扫描以肯定哪条数据须要删除。也就是说首先须要对全表的全部数据进行加锁,InnoDB 在RC级别下的加锁过程采起了Semi-consistent read优化策略,对于扫描过的数据如若不匹配,加锁后会当即释放。

插入过程加锁

那么对于插入过程,RC级别又是如何加锁的呢?

insert into t_user(id,no,name,age) values(4,'00004','小灰灰',8);
复制代码

InnoDB事实上只对主键加了X锁。

Read Repeatable 级别

Read Repeatable级别引入了间隙锁等一系列机制,来防止其余事务的插入操做,如下简称RR级别。但与此同时间隙锁的范围也带来了不少额外的开销与问题,其中之一就有因为引入了间隙锁加大了锁的粒度范围,使用不当容易形成死锁。因为RR级别下能够经过参数innodb_locks_unsafe_for_binlog来配置是否开启gap锁,在此咱们讨论的是开启gap锁的状况。

使用主键

假设咱们须要在上述t_user表格中,删除ID=7的王五这一条记录,语句为:

delete from t_user where id = 7;
复制代码

因为使用了主键,能够惟一确认影响的记录,只需对该条记录加X锁便可,其加锁过程与RC级别下的使用主键加锁过程相同。

使用惟一索引

假设咱们经过身份证no这个惟一索引来删除id=7这条数据会如何加锁呢?

delete from t_user where no = '0007';
复制代码

因为惟一索引为二级索引,Innodb 首先经过惟一索引对数据进行过滤,对于0007惟一索引加X锁,而后还须要在汇集索引上对主键=7的数据进行加X锁。

使用非惟一索引

假设咱们使用非惟一索引,那么状况又会如何呢?

delete from t_user where name = '王五';
复制代码

因为使用索引为二级索引,Innodb 首先经过索引对数据进行过滤,因为普通索引不能保证影响数据范围惟一,有可能其余的事务在对两者之间的间隙操做添加新数据,所以还须要对于王五之间的间隙进行加锁,以防有其余事务在事务提交前在此间隙插入数据,最后还须要在汇集索引上对主键=5,7 的数据进行加X锁。

未使用任何索引

那么在RR级别下,若是不使用索引会致使什么状况呢?

delete from t_user where age = 23;
复制代码

如若不使用任何索引,InnoDB只可以经过全表扫描以肯定须要删除的数据,所以首先会须要对全部数据进行加锁,此外因为须要避免其余事务插入,还须要对全部的间隙进行加锁,这对InnoDB性能影响很是显著。

插入过程

RR级别下,插入过程是如何加锁的呢?

insert into t_user(id,no,name,age) values(4,'00004','小灰灰',8);
复制代码

插入过程是不须要增长gap锁的,所以RR级别下的加锁过程与RC级别下的加锁过程差很少。依照官方文档,插入过程隐式的加了插入意向锁,该锁虽然为间隙锁,但大多数时候并不会影响其余行的插入。

Serializable 级别

Serializable 级别是事务隔离的最高级别,在此级别下全部的请求会进行串行化处理。在InnoDB中该级别下的 更新语句加锁过程Read Repeatable下一致

感谢

相关文章
相关标签/搜索