【MySQL】锁之InnoDB

纸上得来终觉浅,绝知此事要躬行。mysql

InnoDB行锁

开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的几率最低,并发度也最高。sql

InnoDB 实现了如下两种类型的行锁。并发

  • 共享锁(S):又称为读锁,简称S锁,共享锁就是多个事务对于同一数据能够共享一把锁,都能访问到数据,可是只能读不能修改。
  • 排他锁(X):又称为写锁,简称X锁,排他锁就是不能与其余锁并存,如一个事务获取了一个数据行的排他锁,其余事务就不能再获取该行的其余锁(包括共享锁和排他锁),可是获取排他锁的事务是能够对数据就行读取和修改。

能够经过如下语句显示给记录集加共享锁或排他锁 。性能

共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE

排他锁(X) :SELECT * FROM table_name WHERE ... FOR UPDATE

对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);
对于普通SELECT语句,InnoDB不会加任何锁;测试

行锁案例

在测试前仍然准备相关测试表和数据,注意表的存储引擎为InnoDB:优化

create table test_innodb_lock(
	id int(11),
	name varchar(16),
	sex varchar(1)
)engine = innodb default charset=utf8;

insert into test_innodb_lock values(11,'100','1');
insert into test_innodb_lock values(3,'3','1');
insert into test_innodb_lock values(4,'400','0');
insert into test_innodb_lock values(5,'500','1');
insert into test_innodb_lock values(6,'600','0');
insert into test_innodb_lock values(7,'700','0');
insert into test_innodb_lock values(8,'800','1');
insert into test_innodb_lock values(9,'900','1');
insert into test_innodb_lock values(1,'200','0');

create index idx_test_innodb_lock_id on test_innodb_lock(id);
create index idx_test_innodb_lock_name on test_innodb_lock(name);

建立完成,咱们看一下表结构和表数据,方便后面操做查看:设计

mysql> desc test_innodb_lock;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | YES  | MUL | NULL    |       |
| name  | varchar(16) | YES  | MUL | NULL    |       |
| sex   | varchar(1)  | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)


mysql> select * from test_innodb_lock;
+------+------+------+
| id   | name | sex  |
+------+------+------+
|   11 | 100  | 1    |
|    3 | 3    | 1    |
|    4 | 400  | 0    |
|    5 | 500  | 1    |
|    6 | 600  | 0    |
|    7 | 700  | 0    |
|    8 | 800  | 1    |
|    9 | 900  | 1    |
|    1 | 200  | 0    |
+------+------+------+
9 rows in set (0.00 sec)

行锁基本演示

Session-1 Session-2
关闭自动提交功能 关闭自动提交功能
能够正常的查询出所有的数据 能够正常的查询出所有的数据
查询 id 为 3 的数据 查询 id 为 3 的数据
更新 id 为 3 的数据,可是不提交 更新id为3 的数据, 处于等待状态
经过 commit,提交事务 解除阻塞,更新正常进行
以上, 操做的都是同一行的数据,接下来,演示不一样行的数据
更新 id 为 3 数据,正常的获取到行锁 , 执行更新 ; 因为与Session-1 操做不是同一行,获取当前行锁,执行更新;

无索引行锁升级为表锁

若是不经过索引条件检索数据,那么InnoDB将对表中的全部记录加锁,实际效果跟表锁同样。3d

查看当前表的索引 :code

show  index  from test_innodb_lock ;

接下来咱们看一个例子,当索引失效的时候,行锁升级为表锁的过程:blog

以上两个客户端对一行数据进行修改时,因为执行更新时,name字段原本为varchar类型, 咱们是做为int类型使用,存在类型转换,索引失效,最终行锁变为表锁,致使Session-2更新数据处于等待状态。

间隙锁的危害

当咱们用范围条件,而不是使用相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据进行加锁; 对于键值在条件范围内但并不存在的记录,叫作 "间隙(GAP)" , InnoDB也会对这个 "间隙" 加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)

下面是一个间隙锁的案例:

Session-1 Session-2
关闭事务的自动提交 关闭事务的自动提交
执行更新语句 执行更新语句, 但处于阻塞状态
提交事务 解除阻塞,执行更新成功
执行提交操做

因为Session-1的插入操做是一个范围,咱们表中存在id不连续的状况,致使在更新时不只会加排它锁,还会在当前访问增长间隙锁,致使Session-2在插入元素时处于等待状态。

InnoDB 行锁争用状况

查看行锁竞争状况:

show  status like 'innodb_row_lock%';

  • Innodb_row_lock_current_waits: 当前正在等待锁定的数量
  • Innodb_row_lock_time: 从系统启动到如今锁定总时间长度
  • Innodb_row_lock_time_avg:每次等待所花平均时长
  • Innodb_row_lock_time_max:从系统启动到如今等待最长的一次所花的时间
  • Innodb_row_lock_waits: 系统启动后到如今总共等待的次数

总结

InnoDB存储引擎因为实现了行级锁定,虽然在锁定机制的实现方面带来了性能损耗可能比表锁会更高一些,可是在总体并发处理能力较高。可是,InnoDB的行级锁也是一把双刃剑,当咱们使用不当的时候,可能会让InnoDB的总体性能降低。

优化建议:

  • 尽量经过索引来完成数据操做,避免无索引行锁升级为表锁。
  • 合理设计索引,尽可能缩小锁的范围
  • 尽量减小索引条件,及索引范围,避免间隙锁
  • 尽可能控制事务大小,减小锁定资源量和时间长度
  • 尽可以使用低级别事务隔离(可是须要业务层面知足需求)
相关文章
相关标签/搜索