表级锁:
对整张表加锁。开销小,加锁快,不会出现死锁;锁粒度大,发生锁冲突的概率高,并发度低。
行级锁:
对某行记录加锁。开销大,加锁慢,会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度高。
排它锁(Exclusive)
,又称为X 锁,写锁。共享锁(Shared)
,又称为S 锁,读锁。X
和S
锁之间有以下的关系: SS可以兼容的,XS、SX、XX之间是互斥的
一个事务对数据对象 O 加了 S 锁,可以对 O 进行读取操作但不能进行更新操作。加锁期间其它事务能对O 加 S 锁但不能加 X 锁。
一个事务对数据对象 O 加了 X 锁,就可以对 O 进行读取和更新。加锁期间其它事务不能对 O 加任何锁。
显示加锁:select ... lock in share mode
强制获取共享锁,select ... for update
获取排它锁。
例如:
先创建一张表结构如下:
这里事务一 for update
获取了该行记录的排它锁,此时其他事务既不能读也不能写。此时如果事务二想要获取排它锁或共享锁时,也获取不到。
当然,如果此时事务二想要获取其他行的共享锁或者排它锁时是可以实现的。
这里我们需要注意的是,在InnoDB中行锁是加在索引上的,如果我们的过滤条件中没有索引,那么就默认加的是表锁。
InnoDB存储引擎支持事务处理,表支持行级锁定,并发能力更好。
举一个例子,还是上面的 user
表,我们将 name
一列创建上一个普通索引。
这里我们可以看到,如果给name一列添加索引时,那么InnoDB就会使用行级锁。
在串行化隔离级别中解决了脏读、不可重复读和幻读的问题,因为了在串行化中纯粹使用了共享锁和排它锁。
这说明了SS锁是可以共享的,SX锁是互斥的。
如果事务1通过主键索引对一行记录加锁,那么事务2如果通过辅助索引也锁定了和事务1一样的行,那么事务2将不能加锁。
行锁是给索引加的锁,而不是给数据加的锁。
串行化隔离级别是如何解决幻读问题的?
幻读问题
范围查询
本质上是通过 间隙锁(gap lock)
解决的。
我们先来看现象:
下面我们来解决一下原因:
所以,当事务一向间隙中插入数据时,就会阻塞到间隙锁上面。
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB 会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做 “间隙(GAP)”
,InnoDB 也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁。举例来说, 假如 user 表中只有 101 条记录, 其 userid 的值分别是 1,2,…,100,101, 下面的 SQL:
select * from user where userid > 100 for update;
是一个范围条件的检索,InnoDB 不仅会对符合条件的 userid 值为 101 的记录加锁,也会对userid 大于 101(但是这些记录并不存在)的"间隙"加锁,防止其它事务在表的末尾增加数据。
InnoDB使用间隙锁的目的,为了防止幻读,以满足串行化隔离级别的要求,对于上面的例子,要是不使用间隙锁,如果其他事务插入了 userid 大于 100 的任何记录,那么本事务如果再次执行上述语句,就会发生幻读。
间隙锁的加锁范围
现象一:
原因:
现象二:
我们可以看到与预期的现象不同, 这是什么原因呢?
其实这是因为事务在查询时,如果发现索引查询和整表查询所得到的行数相差不多时,MySQL就优化查询方式,将索引查询优化为整表查询,这会导致在加锁时直接加的是 表级锁
, 对整张表加锁会导致插入任何数据都会阻塞。
所以,对于间隙锁的加锁范围,我们应该视情况而定。
等值查询
现象:
原因:
因此,串行化隔离在面对等值查询时,也可以通过间隙锁来很好的避免幻读问题。
首先我们来看一个问题:如果我们想要获取一张表的表锁时,首先得知道这张表有没有被其他事务获取过锁?因为这张表中可能有其他事务的行级锁!
当我们想要获取一张表的共享锁S或者排它锁X时,最起码得确定,这张表又没有被其他事务获取过X锁!
假如这张表的数据非常大(一千万行数据),那么我们应该如何从中得知它有没有被其他事务获取过行锁X锁呢?
这里我们就可以使用意向锁来解决这里的效率问题了。
说明一下:
因此,要解决上面所说的效率问题,当我们要获取表的X锁时,不需要再检查表中的哪些行锁(X或者S)占用,只需要快速检查IX和IS锁即可。
在绝大部分情况下都应该使用行锁,因为事务和行锁往往是选择InnoDB的理由,但个别情况下也使用表级锁:
MyISAM 表锁是 deadlock free 的, 这是因为 MyISAM 总是一次获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。但在 InnoDB 中,除单个 SQL 组成的事务外,锁是逐步获得的,即锁的粒度比较小,这就决定了在 InnoDB 中发生死锁是可能的,如下:
当然,mysql server 检测到死锁发生时,会自动进行事务的回滚操作。从而避免死锁的发生。
死锁问题一般都是我们自己的应用造成的,和多线程编程的死锁情况相似,大部分都是由于我们多个线程在获取多个锁资源的时候,获取的顺序不同而导致的死锁问题。因此我们应用在对数据库的多个表做更新的时候,不同的代码段,应对这些表按相同的顺序进行更新操作,以防止锁冲突导致死锁问题。