MySQL在默认事务下各SQL语句使用的锁分析

172 阅读4分钟
**数据库使用锁是为了支持更好的并发,提供数据的完整性和一致性。InnoDB是一个支持行锁的存储引擎,锁的类型有:共享锁(S)、排他锁(X)、意向共享(IS)、意向排他(IX)。为了提供更好的并发,InnoDB提供了非锁定读:不需要等待访问行上的锁释放,读取行的一个快照。该方法是通过InnoDB的一个特性:MVCC来实现的**
**InnoDB有三种行锁的算法:** **1,Record Lock:单个行记录上的锁。** **2,Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。**

3,Next-Key Lock:1+2,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。

如果采用MySQL默认的事务隔离级别(可以重复读:repeatable read

可重复读取(Repeatable Read):防止(避免)不可重复读取和脏读,但是有时可能出现幻读数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。

  • 脏读:一个事务还未提交,另外一个事务访问此事务修改的数据,并使用,读取了事务中间状态数据。
  • 幻读:一个事务读取2次,得到的记录条数不一致,由于2次读取之间另外一个事务对数据进行了增删。
  • 不可重复读:一个事务读取同一条记录2次,得到的结果不一致,由于在2次读取之间另外一个事务对此行数据进行了修改。

一、正常查询

在4个事务隔离级别中,除了在串行化(Serializable)时会加共享锁,其他的都不加锁,即快照读。

二、加了锁的查询

加锁select主要是指:

  • select ... for update

  • select ... in share mode

1.当使用唯一索引来搜索唯一行的语句时,使用记录锁(record lock)。如:

select * from t where id = 10  for update; # id是唯一索引列

2.其他情况,包括id列没有索引或者是非唯一索引又或者是搜索条件里有多个查询条件(使每个列都有唯一索引),则使用间隙锁与临键锁。

临键锁是间隙锁和记录锁的组合。

三、update和delete

1. 当使用唯一索引列确定的唯一数据行上进行的update/delete,也使用记录锁:

update t set number=10 where id=1;

2. 其他情况,包括id列没有索引或者是非唯一索引又或者是搜索条件里有多个查询条件(使每个列都有唯一索引),加排他临键锁(exclusive next-key lock)。

注意:如果update的是聚集索引记录,则对应的二级索引记录也会被隐式加锁,这是由InnoDB索引的实现机制决定的:二级过引的叶子上存的是聚集索引的主键值,当检索二级索引时,会二次扫描聚集索引。

四、insert

insert会用排它锁封锁被插入的索引记录,然后在插入区间加插入意向锁(insert intention lock)。

总结:

读已提交和可重复读级别下加锁情况

正常查询(不加update)的话,都是快照读!

如果select的时候加了for update 就会用行锁

唯一索引 唯一行 就是行锁

没有索引 或者普通索引,或者有多个条件,那么就是用间隙锁

update和delete的时候就是加行锁 (唯一索引)

没有索引 或者普通索引,或者有多个条件,那么就是用间隙锁

insert会用排它锁封锁被插入的索引记录,然后在插入区间加插入意向锁(insert intention lock)。 也就是只锁访问的资源,不会锁全表

只要不是插入相同的index record,多个事务向同一个gap插入记录是不会冲突的。虽然插入意向锁之间不会互相阻塞,但是插入意向锁与涉及本区间的行锁们可是不兼容的,会互相阻塞,这也是造成死锁的一大主因。

读已提交

查询用主键和用唯一索引都是只锁单独一条记录,以及对应的聚集索引上的项加锁 也就是行锁

普通索引的话 会对满足条件的对应记录加上锁

如果没有走索引,也只会对满足条件的对应记录上锁

可重复读

查询用主键和用唯一索引都是只锁单独一条记录,以及对应的聚集索引上的项加锁 也就是行锁

普通索引的话 会对旁边的也加锁,是gap锁

如果没有走索引,会对表中所有记录以及间隙加锁