我们知道mysql在曾经。存储引擎默认是MyISAM。可是随着对事务和并发的要求越来越高,便引入了InnoDB引擎。它具有支持事务安全等一系列特性。
InnoDB锁模式
InnoDB实现了两种类型的行锁。
共享锁(S):同意一个事务去读一行,阻止其它事务获得同样的数据集的排他锁。
排他锁(X):同意获得排他锁的事务更新数据,可是组织其它事务获得同样数据集的共享锁和排他锁。
能够这么理解:
共享锁就是我读的时候,你能够读,可是不能写。排他锁就是我写的时候。你不能读也不能写。事实上就是MyISAM的读锁和写锁,可是针对的对象不同了而已。
除此之外InnoDB还有两个表锁:
意向共享锁(IS):表示事务准备给数据行增加共享锁。也就是说一个数据行加共享锁前必须先取得该表的IS锁
意向排他锁(IX):类似上面,表示事务准备给数据行增加排他锁,说明事务在一个数据行加排他锁前必须先取得该表的IX锁。
InnoDB行锁模式兼容列表:
注意:
当一个事务请求的锁模式与当前的锁兼容。InnoDB就将请求的锁授予该事务;反之假设请求不兼容,则该事务就等待锁释放。
意向锁是InnoDB自己主动加的。不须要用户干预。
对于insert、update、delete,InnoDB会自己主动给涉及的数据加排他锁(X);对于一般的Select语句,InnoDB不会加不论什么锁,事务能够通过下面语句给显示加共享锁或排他锁。
共享锁:select * from table_name where .....lock in share mode
排他锁:select * from table_name where .....for update
增加共享锁的样例:
利用select ....for update增加排他锁
锁的实现方式:
InnoDB行锁是通过给索引项加锁实现的。假设没有索引,InnoDB会通过隐藏的聚簇索引来对记录加锁。
也就是说:假设不通过索引条件检索数据。那么InnoDB将对表中全部数据加锁。实际效果跟表锁一样。
行锁分为三种情形:
Record lock :对索引项加锁。即锁定一条记录。
Gap lock:对索引项之间的‘间隙’、对第一条记录前的间隙或最后一条记录后的间隙加锁,即锁定一个范围的记录,不包括记录本身
Next-key Lock:锁定一个范围的记录并包括记录本身(上面两者的结合)。
注意:InnoDB默认级别是repeatable-read级别,所以以下说的都是在RR级别中的。
之前一直搞不懂Gap Lock和Next-key Lock的差别,直到在网上看到一句话豁然开朗,希望对各位有帮助。
Next-Key Lock是行锁与间隙锁的组合。这样,当InnoDB扫描索引记录的时候。会首先对选中的索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。
假设一个间隙被事务T1加了锁,其他事务是不能在这个间隙插入记录的。
干巴巴的说没意思,我们来看看详细实例:
如果我们有一张表:
+----+------+
| id | age |
+----+------+
| 1 | 3 |
| 2 | 6 |
| 3 | 9 |
+----+------+
表结构例如以下:
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `keyname` (`age`)
) ENGINE=InnoDB AUTO_INCREMENT=302 DEFAULT CHARSET=gbk ;
这样我们age段的索引就分为
(negative infinity, 3],
(3,6],
(6,9],
(9,positive infinity);
我们来看一下几种情况:
1、当事务A运行下面语句:
mysql> select * from fenye where age=6for update ;
不仅使用行锁锁住了对应的数据行。同一时候也在两边的区间,(5,6]和(6,9] 都增加了gap锁。
这样事务B就无法在这个两个区间insert进新数据,可是事务B能够在两个区间外的区间插入数据。
2、当事务A运行
select * from fenye where age=7 for update ;
那么就会给(6,9]这个区间加锁,别的事务无法在此区间插入或更新数据。
3、假设查询的数据不再范围内,
比方事务A运行 select * from fenye where age=100 for update ;
那么加锁区间就是(9,positive infinity)。
小结:
行锁防止别的事务改动或删除,GAP锁防止别的事务新增。行锁和GAP锁结合形成的的Next-Key锁共同攻克了RR级别在写数据时的幻读问题。
何时在InnoDB中使用表锁:
InnoDB在绝大部分情况会使用行级锁,由于事务和行锁往往是我们选择InnoDB的原因,可是有些情况我们也考虑使用表级锁。
1、当事务须要更新大部分数据时。表又比較大。假设使用默认的行锁,不仅效率低。并且还easy造成其它事务长时间等待和锁冲突。
2、事务比較复杂。非常可能引起死锁导致回滚。
死锁:
我们说过MyISAM中是不会产生死锁的,由于MyISAM总是一次性获得所需的所有锁,要么所有满足。要么所有等待。
而在InnoDB中。锁是逐步获得的。就造成了死锁的可能。
在上面的样例中我们可以看到。当两个事务都须要获得对方持有的锁才可以继续完毕事务,导致两方都在等待。产生死锁。
发生死锁后,InnoDB一般都能够检測到,并使一个事务释放锁回退。还有一个获取锁完毕事务。
避免死锁:
有多种方法能够避免死锁,这里仅仅介绍常见的三种:
1、假设不同程序会并发存取多个表,尽量约定以同样的顺序訪问表。能够大大减少死锁机会。
2、在同一个事务中。尽可能做到一次锁定所须要的全部资源,降低死锁产生概率;
3、对于很easy产生死锁的业务部分,能够尝试使用升级锁定颗粒度,通过表级锁定来降低死锁产生的概率;
參考:
深入浅出Mysql数据库开发、优化与管理维护.第二版