以下都是在innodb存储引擎中
1:innodb中的锁
1.1:锁的类型
行级锁
- 共享锁(s lock):允许事务读一行数据
- 排他锁(x lock):允许事务删除或更新一行数据
行级锁的兼容情况:
对于同一行来说,只有两个共享锁之间是兼容的。这种情况称为锁兼容,即是当一个事务获得了行r的共享锁之后,另一个事务还可以获得该行的共享锁。其他情况均不兼容。
x | S | |
---|---|---|
x | 不兼容 | 不兼容 |
s | 兼容 | 兼容 |
不兼容的例子:当一个事务获得某一行的共享锁之后,另一个事务要修改该行的数据并需要获得排他锁,这个时候必须要等待前一事务完成后释放该行上的共享锁,这个事务才能获得该行的排他锁,这种情况称为锁不兼容。
表级锁(意向锁)
为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。
- 意向共享锁(is like):事务想要获得一张表中的某些行的共享锁,需先要获得该表的意向共享锁。
- 意向排他锁(is lock):事务想要获得一张表中的某些行的排他锁,需要先获得该表的意向排他锁。
1.2:一致性非锁定读
什么是一致性非锁定读
一致性非锁定读是存储引擎通过多版本控制的方式来读取当前执行时间数据库中的数据
mvcc参考文章
简单来说就是比如某一事物对某行正在进行删除或者更新操作,并且对该行加了排他锁。如果这时另一个事务想要读取该行的数据,那么可以不用等待之前的事物结束释放锁。相反的,innodb会去读取该行的一个快照数据返回。
不用考虑锁兼容的,即是该行被加锁了,还是可以读
快照数据是指该行之前版本的数据。(如何实现的,看了事务写上)。应该是在事物提交后才会更新一个版本的快照数据,而不是在事物中修改了就生成。
这样一致性的非锁定读提高了数据库的并发性。在innodb下,这时默认的读取方式,即读取不会占用和等待表上的锁,要注意的是,在不同的事物隔离级别下,读取的方式不同,并不是在所有的事物隔离级别下使用的都是一致性非锁定读,而且即使都是使用一致性非锁定读,但是对于快照数据的第一也不同。
使用该方式的隔离级别:
因为有事默认的模式,所以数据库查询语句默认并不会加锁
事物隔离级别
在事物隔离级别Read committed和repeatable read中,采用的是一致性非锁定读,但是这个量级别中对于快照数据的定义是不一样的。
- 提交读中:读取的是被锁定行的最新一份快照数据
- 可重复读中:读取的数据是读取事物开始时的快照数据版本。(本事务的快照数据的最新版本)
例子:
表:学生表(ID,name,age)
数据:1,小明,18
现在我们开启一个事务A:执行以下操作,查询小明的年龄,这时是返回18.
接着,我们再开启一个事务B。在这个事务中,我们更新小明的年龄为19,但是事务没有被提交。这时也就是对该行加了一个排他锁。
我们现在考虑的事务隔离级别是提交读和可重复读,现在我们继续回到事务A中,我们再次查询姓名为小明的学生的年龄。这两个事务隔离下,都会返回18.因为B事务并没有提交,数据库中该行只有一个版本的数据,该版本当前即是最新版本数据,也是A事务开始时的版本数据,所以这两种事务隔离级别下都是返回18 。
之后我们提交B事务。这时候该行数据就会更新一个版本了。
这时我们再返回到A事务中进行查询,同样是查询名字为小明的同学的年龄,这时再提交读的事物隔离级别下,因为是读取最新版本的数据,所以会返回19;而在可重复读的事物隔离级别下,因为读取的快照数据应该是事务开始时的版本数据,所以还是返回19
1.3:一致性锁定读
默认配置下,即提交读的事物隔离级别下,查询使用一致性非锁定读。
但是子啊某些情况下,我们想要显示的对查询进行加锁,这就要数据库支持加锁语句,即使时对于select的只读操作。
innidb存储引擎支持两种对查询语句的一致性锁定读(9显示加锁)操作:
- select …for update:对于读取的记录行加一个排他锁。其他事务就不能再加锁了。
- select …lock in share mode:对于读取的行记录加一个共享锁。其他事务还可以加共享锁,排他锁就不锁兼容了。
这两个语句必须在事务中,事务结束了锁也就释放了。
而且就算使用该语句给行加了一个排他锁,页丝毫不影响一致性非锁定读。
1.4:自增长与锁
在mysql5.1.22之前。
每个含有自增长的列的数据库都会有一个自增长计数器。插入操作会依据这个计数器的值再加一然后赋值给自增长的列。这个实现方式称为AUTO-INC lock。采用一种特殊的表锁机制,为了提高插入的性能,并不是事务提交后才释放锁,而是在自增长列插入的sql语句完成后就释放锁,以提高并发插入的能力。但是还是有许多不足。必须等待前一个插入的完成才能插入下一个(虽然不用等待事务完成),而且如果一个事务中插入数据非常大量,另一个事务中的插入会被阻塞。
在mysql5.1.22之后。
提供了一种轻量级互斥量的自增长实现机制,这种机制大大了提高自增长的性能。
1.5:外键和锁
对于外键值的拆入或者更新,要先检查父表,即select父表。但是对于父表的select操作,不使用一致性非锁定读的方式,因为这样会发生数据不一致的问题,因此此时使用select …lock in share mode方式,即显示对父表加一个锁,是一致性锁定读。
这样如果父表上被加了X锁,那么子表中与父表有关的相应外键操作就会被阻塞。
比如在A事务中对父表中id为3的进行修改,这是附表中该巨鹿被加了一个X锁,还不提交事务。这是在事务B中,我们在指标中,我们对一行记录的外键等于三的要更新,这时会在父表中就那些查询,这时要加s锁,但是发现以及已经有一个x锁了,B事务这里就被阻塞了,只有A事务提交了,B事务才能继续执行下去。
如果对于父表的查询使用一致性非锁定读,就不会发生阻塞,但是会发生数据不一致的问题,收益外键这里,对于父表的查询,使用的是一致性锁定读。
2:行锁的三种算法
InnoDB通过给索引项加锁来实现行锁,如果没有索引,则通过隐藏的聚簇索引来对记录加锁。如果操作不通过索引条件检索数据,InnoDB 则对表中的所有记录加锁,实际效果就和表锁一样。
InnoDB存储引擎有三种行锁算法,其分别是:
- record lock:单个行记录上的锁。提交读使用这种算法
- Gap Lock:间隙锁,锁定一个范围,但不会包含记录本身
- Next-Key Lock:Gap Lock+Record Lock,锁定一个范围,并且包含记录本身。可重复读下使用这种算法。
行锁锁住的是索引而不是记录!
next-key lock也是innodb默认的算法。
当查询的索引含有唯一属性时,innodb存储引擎会对next-key lock进行降级,降级到record lock。(所以主键索引肯定是使用的record lock)
也就是说对于主键索引,最终使用的时record lock算法对索引记录加锁。
对于辅助索引来说,默认加上next-key lock后并不会降级(除非这个辅助索引唯一吧)。特别要注意的是,innodb存储引擎还会对辅助索引下一个键值加上gao lock。例如当一个辅助索引b 有值1 3 6,那么在使用b=3进行查询的时候,显示加锁next-key lock,即对辅助索引范围为(1,3】加锁了,但是还会对(3,6)这个范围也加一个gap lock。而且还要锁住这些辅助索引对应的主键索引,如果b=3对应的书签即主键索引为5,那么还要对主键索引为5进行加锁,这时候加的锁就是record lock了。
总结:
查询条件使用到索引时:
- 对主键索引加锁:record lock算法
- 对非主键索引加锁:先对非主键索引加锁(next-key lock + Gap lock),然后要对对应的主键索引加锁(record lock)
查询条件没有使用到索引,就会锁住整张表
3:解决幻读(phantom problem)
为什么可重复读级别没有解决幻读
幻读: 同一事务下,两虚两次执行两次同样的sql语句可能导致不同的结果,第二次sql语句可能返回和之前不一样的值
next-key lock解决了幻读。
与其他数据库不同的是,mysql在课重复读隔离级别下就解决了幻读
其实也就是锁可重复读能够解决幻读,只是我们要在查询的时候取显示的加锁,没有显示的加锁的时候,都是一致性非锁定读的,就可能会产生幻读。但是在默认级别下,快照读通过版本快照页解决了不可重复读的问题!!加锁了的时当前读,没加锁的是快照读。
不可重复读只是幻读的一部分吧
4:锁问题
4.1:脏读
脏数据: 脏数据和脏页是完全不同的概念。脏页是缓冲池中修改的页,但是还没有刷新到磁盘中,即缓冲池中的页数据和磁盘中的不一致。脏数据是指事务对缓冲池中行记录的修改,并且还没有被提交。
脏读: 读到了脏数据。
即是一个事务可以读取到另一个事务中为提交的数据,显然违反了数据库的隔离性。
在未提交读这个事务隔离级别下可能会发生脏读
在另一个事务没有提交的情况下,可以度到其数据的变化
4.2:不可重复读
不可重复读是指在一个事务内多次读取同一个数据返回的结果不一样。
在一个事务中查询一个数据,而当这个事务还没有结束时,历一个事务修改了这个数据,并且提交了,那么这个事务再此查询这个数据时,两次返回的内容就不一样,着就是不可重复读。
不可重复读与脏读的区别: 脏读时读到了未提交的脏数据,而不可重复读是,提交的数据在同一事务前后两次读会发现不一致。
在innodb中,通过版本控制可以解决快照读(非锁定一致性读)的不可重复读问题,而当前读(update,insert)的不可重复读(幻读)问题是靠next_key lock解决的。
4.3:丢失更新
- 事务A将行记录R进行修改,但是事务A没有修改
- 与此同时,事务B将记录R又进行修改,事务B页不提交
- 事务A提交
- 事务B提交
可能你以为这样A的跟新就被B 覆盖了,其实不会
其实这种丢失跟新不会发生,因为任何事务隔离级别下,跟新操作都会加锁,这里A事务进行更新就会对行R加锁,A事务提交前B事务并不会更新,会被阻塞。
还有逻辑意义的一种丢失更新,这种并不是因为数据库的问题:
- 事务A查询数据,给用户user1显示
- 事务B页查询这个数据,给用户user2显示
- user1 修改这个数据,并且提交
- user2修改这个数据,并且提交
显然这个过程中user1 的跟新“丢失”了。
看书不好写274
要解决这个问题就要让事务在这种情况下的操作变成串行化。
在步骤一与步骤二加一个排他锁,这样步骤二事务B的读取就会被阻塞,直到1,3执行完成,即事务A结束,才能执行事务B。
5:阻塞
因为不同锁之间的兼容性关系,在有些时刻一个事务中的锁要等待历一个事务中的锁释放它所占用的资源,这就是阻塞。阻塞并不是一件坏事,其是为了确保事务可以并发且正常的运行。
控制等待时间与超时是否回滚得到参数(默认不回滚)
注意阻塞超时后,事务不会主动提交或者回滚,所以需要确定是回滚还是提交。抛出异常后输入commit 或者rollback。默认是不回滚的。
建议把超时后设置为回滚。
6:死锁
死锁是指两个个或者两个以上的事务在执行过程中,因为争夺锁资源而造成的一种相互等待的现象
使用等待图wait-for gragh来解决死锁,p278
等待图中节点是各个事务,如果A事务等待B事务的某个锁释放,A就指向B。
图中有回路就代表可能发生死锁
7:锁升级
innodb存储引擎中没有锁升级。