数据库是一个多用户使用的共享资源。当多个用户并发的存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加以控制就可能会读取和存储不正确的数据,破坏数据的一致性。
锁就是用于管理对公共资源的并发控制。
锁的出现,就是用于解决不同事物对共享资源并发访问所引起的脏读、不可重复读和幻读
MySQL中InnoDB锁类型,大体可分为: 共享锁 (Shard Lock) 、排它锁(Exclusive Lock)、意向共享锁(Intention Shared Lock)、意向排它锁(Intention Exclusive Lock)、自增锁(Auto-INC LOCK)、临键锁(Next-key Lock)、间隙所(Gap Lock)、记录锁(Record Lock)这8种。(其中记录锁、间隙锁、临键锁都是行锁的具体实现)。
接下来锁相关介绍,均以事务关闭状态,手工开启为前提来进行demo演示。
orders 表数据:
1 共享锁(S锁)
又称为读锁。简称S锁。行级锁,未命中索引将会退化变为表锁。
共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。添加共享锁方式如下:
// 在查询语句末尾,需要通过LOCK IN SHARE MODE的方式添加共享锁
select * from users where id = 1 LOCK IN SHARE MODE;
说明:
2 排它锁(X锁)
又称为写锁,简称X锁。
如果一个事务获取了一个数据行的排它锁,那么其它事务就不能再获取该行的锁(共享锁、排它锁),只有获取了该排它锁的事务是可以对这一数据行进行读取和修改。(其它事务可以读取数据,数据来自快照,即Mysql的MVCC机制)。
添加排它锁的方式如下:
//删除、修改、插入,默认会加上X锁
delete /update / insert
//查询语句,需要通过 FOR UPDATE 的方式添加排他锁
select * from users where id = 1 FOR UPDAT
3 意向锁
意向锁的由来,我们来分析下面这个示例:
多个事务:A、B、C三个事务为例:
3.1 意向共享锁(IS锁)
表锁。表示事务准备给数据行加入共享锁时,即一个数据行加共享锁前必须先取得该表的IS锁。
3.2 意向排它锁(IX锁)
表锁。表示事务准备数据行加入排它锁时,即一个数据行加排它锁前必须先取得该表的IX锁。
Tips:意向锁相当于Java中的flag,就是一个标记,在加锁前会判断该表是否有锁。
例子来通俗易懂的理解:
事务A锁住了表中的一行,让这一行只能读,不能写。
之后事务B申请整张表的写锁。
如果事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。
那么数据库应该怎么判断这个冲突呢?主要有两种办法:
a.其中一个办法是判断表中的每一行是否已经被行锁锁住;
b.另外一个方法判断表是否已经被其它事务用表锁锁住;
注意方法a,这样的判断方法效率实在不高,因为需要遍历整个表。
于是在这样的场景下,意向锁出现了。
在意向锁存在的情况下,事务A必须先申请表的意向互斥锁,成功后再申请这一行的行锁。
而在意向锁的情况下,上面的事务B判断就发现表中有意向互斥锁,说明表中有些行被行锁锁住了。因此,事务B申请表的写锁会被阻塞;
4 自增锁(AUTO-INC LOCK)
自增锁,是针对自增列自增长的一个特殊的表级别锁。
比如说我们建表时,通常会指定某一列为id自增长列,该列便会使用到这个自增锁。
自增锁有一个默认值,是从1开始的。通常情况下,我们在数据库表中会发现自增id并不连续,这就是自增锁在搞事情。
当我们插入数据时,自增锁会+1,但是此时如果事务执行了rollback等其他操作,导致数据并没有插入成功,此时zizengsuoid并不会随之回退,会永久丢失,从而导致的自增id列不连续。
/** 当我们进行插入操作的时候,该操作会根据这个自增长的计数器的值+1赋予自增长的列,
这个操作我们称之为auto-inc Locking,也就是自增长锁,这种锁其实采用的是特殊的表
锁机制,如果insert操作出现在一个事务中,这个锁是在insert操作完成之后立即释放,
而不是等待事务提交。**/
自增锁是一种特殊的表级别锁(table-level lock),专门针对事务插入AUTO_INCREMENT类型的列。最简单的情况,如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值。
那么为什么表级别的锁,我们还能够在会话1中的事务没有结束的时候,在另外一个会话2上成功执行insert呢?不应该直接锁表么?它的本质其实是在参数innodb_autoinc_lock_mode上,这个参数设置为1的时候,相当于将这种auto_inc lock弱化为了一个更轻量级的互斥自增长机制去实现,官方称之为mutex。
5 临建锁(next-key lock)&间隙锁(gap-key lock)和记录锁(record lock)
Mysql中锁大体可分为表级锁和行级锁。
在Mysql的常用引擎中InnoDB支持行锁;而MyISAM只能使用表锁。
Mysql的表锁
表锁由Mysql Server实现,一般在执行DDL语句时会对整张表进行加锁,比如说ALTER TABLE等操作。
表锁使用的是一次性锁技术,也就是说,在会话开始的地方使用lock命令将后续需要用到的表都加上锁,在表释放前,只能访问这些加锁的表,不能访问其它表,直到最后通过unlock tables释放所有表锁。
Mysql的行锁
行锁中按照粒度划分为:
注意:行锁加锁是在索引上加锁的。
5.1 临键锁(Next-key lock) (InnoD引擎默认行锁算法)
临键锁(Next-key Lock), 作为InnoDB引擎默认的行锁算法,本质就是用来解决幻读的问题。当SQL执行按照索引进行数据查询时,查询条件为范围查找(between and > <等)并由数据命中时,此时SQL语句加上的锁为Next-key Lock,锁住的是索引的记录+下一个区间(左开右闭)
即使用临建锁的条件:
我们使用 SQL 进行查询。针对 orders 表中的数据,InnoDB 引擎会将数据进行如下区间划分:并锁住当前数据所在区间+下一个区间。
此时,我们对 id = 4 进行查询,由于不在锁住区间内,并不会阻塞;id = 7,10 进行查询,插入id = 8,9 的数据。发现都会被阻塞。当我们插入 id = 11 时,可以成功插入,说明上述查询锁住的区间为 (4,7],(7,10]
那么,临键锁,为什么要锁住下一个区间呢?
这和索引底层 B+Tree 有关系。MySQL 索引底层选择 B+Tree。B+Tree 数据都保存在叶子节点,并且具有顺序性,从左到右依次增大。临键锁锁住相邻区间,此时 insert 插入数据时,我们并不能够将数据成功的插入到该区间,就能够满足每次查询的数据一致,从而解决幻读问题。
5.2 间隙锁(Gap Lock)
间隙锁(Gap Lock)。当SQL执行按照索引进行数据查询时,查询条件的数据不存在,此时SQL语句加上的锁即为Gap Lock,锁住的是数据不存在的区间。(左开右开)。
即使用间隙锁条件:
使用:同临建锁类似,当查询无数据命中时,临建锁(Next-key lock)便会退变成为Gap锁。Gap锁锁住的是数据不存在的区间(左开右开)。
由于锁住(7,10)这个区间,此时我们无法插入id=8,9的数据,但是我们可以插入id=11的数据,说明上述 查询使用的是Gap锁,锁住的区间为(7,10)。
因为只有可重复的隔离级别下,InnoDB引擎才能够解决数据幻读的问题。临键锁(Next-key) lock=间隙锁(Gap Lock)+记录锁(Record Lock).所以Gap锁只在可重复隔离级别下存在。
5.3 记录锁(Record Lock)
记录锁(Record Lock)。当SQL执行按照唯一性(Primary key,Unique key)索引 进行数据查询时,查询条件等值匹配且查询的数据存在,此时SQL语句加上的锁即为Record Lock,锁住的是具体的索引项。
即使用记录锁条件:
由于锁住 id = 7 这个索引,此时我们可以查询 id = 4 的数据,但是无法查询 id = 7 的数据。说明:锁住的区间为 id = 7 这个索引。
6 事务并发问题解决方案
6.1 +X锁(排它锁) 解决脏读问题
6.2 +S锁(共享锁),解决不可重复读问题
6.3 +Next key锁,解决幻读问题
7 如何避免出现事务死锁?
总结:
select * from users where id = 1 LOCK IN SHARE MODE; //共享锁,该行只能读不能修改
delete /update/insert 以及select * from users where id=1 FOR UPDATE //加入排它锁,这一行不允许和别的锁共存。
select * from orders where id>5 and id<9 for update; next-key lock;//命中数据;命中索引;范围查询;锁住(4,7] (7,10]
select * from orders where id>8 and id<10 for update; //查询记录不存在,命中索引,范围查询,锁住(7,10)
select * from order where id=7 for update; //唯一性索引查询(主键索引、唯一性索引) 精确等值查询;数据命中;
InnoDB中的行锁实现依赖与索引,一旦某个加锁操作没有使用到索引,那么该所就会退化成为表锁;