InnoDB 为了保证并发能力,采起行级加锁策略。为了实现事务的隔离级别,InnoDB 中又引入了各类不一样的行级锁机制。不一样的加锁顺序、加锁类型、锁的多少以及影响范围将直接影响到整个事务执行效率与执行时间直接影响 MySQL 的吞吐能力,不恰当的加锁策略甚至有可能产生死锁,所以咱们又必要对整个过程有所了解。html
InnoDB 采用了 B+ Tree 的数据结构与汇集索引的数据组织形式,索引在 InnoDB 引擎中占据了很是重要的位置,InnoDB 加锁过程就是对索引进行加锁的一个过程。在分析 InnoDB 加锁以前,咱们须要知道 InnoDB 加锁是和什么有关,这一点很是重要。对于不一样的事务隔离离别、不一样的列 InnoDB 采起的策略与使用的锁的类型都不同,影响加锁的因素有以下两种:mysql
select ... from
这类语句而言,因为 InnoDB 采起了一致性读策略,通常是不会加锁的,可是在Serialzable
级别,InnoDB 会对搜索过程当中遇到的二级索引加共享临键锁
。对于Read Committed
级别不会采起间隙锁
的加锁策略。update ... from ...
语句在Read Repeatable
级别下使用了排他临键锁
,而在Read Committed
级别下使用的是排他行锁
。对于 InnoDB 而言,虽然加锁的类别繁多,加锁形式也灵活多样,但也遵循了一些原则:sql
select ... from ...
语句,使用快照读,通常状况下不加锁,仅在Serializable
级别会加共享读锁select ... from ... lock in share mode
语句使用当前读,加共享读锁(S锁)select ... from ... for update
语句,为当前读,加排他写锁(X锁)接下来咱们将按照不一样的场景逐个不一样语句的加锁过程进行分析。以下为使用到的表格: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 Committed
的加锁过程与Read Uncommitted
一致。因为Read Committed
使用范围较Read Uncommitted
更广,在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
级别引入了间隙锁等一系列机制,来防止其余事务的插入操做,如下简称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
级别是事务隔离的最高级别,在此级别下全部的请求会进行串行化处理。在InnoDB
中该级别下的 更新语句加锁过程与Read Repeatable
下一致。