mysql 锁机制和事务隔离级别(innoDB存储引擎)

一、需要场景

在最大程度利用数据库的并发访问时候,需要确保每个用户能以一致的方式读取和修改数据。本质上:锁机制是用于管理对共享资源的并发访问。

二、锁的种类对比

  lock latch
对象 事务 线程
保护 数据库内容 内存数据结构
持续时间 整个事务过程 临界资源
模式 行锁、表锁、意向锁 读写锁、互斥量
死锁 通过waits-for graph、time out 等机制进行死锁检测和处理 无死锁检测与处理机制,仅通过应用程序加锁的顺序保证无死锁的情况发生
存在于 lock manager的哈希表中 每个数据结构的对象中

三、锁的相关定义

  • 行级锁    (共享锁和排他锁)

只有对同一行记录,共享锁是兼容的,其他彼此都是不兼容的(需要依次获得该行上的锁)。

  • 表级锁   (意向共享锁和意向排他锁)

意向锁:将锁定的对象分多个层次,希望在更细粒度加锁(即行级)。如果对细粒度上锁,则首先需要对粗粒度的对象上锁,

目的:主要是为了在一个事务中揭示下一行将被请求的锁类型。

  • 一致性非锁定读

innodb存储引擎通过多版本的控制的方式,来读取当前执行时间数据库中行的数据,如果读取的行正在执行delete或update操作,这时读操作不会因此去等待行上的排他锁(x锁)的释放,相反,innodb存储引擎会去读取行的一个快照数据。(具体实现是通过uodo来实现的,后面事务单独讲)。

对于快照版本,不同的事务隔离级别定义不同,

read committed (读已提交)表示被锁定行最新一份快照数据,

举例:如果查询后,有修改并提交,再次查询,是最新的修改后的数据。

repeatable read(可重复读)表示读取事务开始时的行数据版本。mysqlinnodeb的默认配置

举例:查询后,会记录最开始行的版本数据,已经锁定,无论后期数据修改,都不会变。

  • 一致性锁定读

显式的对数据库读取操作进行加锁保证数据逻辑的一致性。是对数据行本身进行加锁,满足排他锁和共享锁的兼容特性。

a、select  ... for update     b、 select ... lock in share mode

四、锁的算法

    1、record lock(单个行记录上的锁):锁定索引记录

如果表在简历的时候没有设置任何一个索引,那么会使用隐世的主键来进行锁定。

    2、gap lock:间隙锁:锁定一个范围,但不包含记录本身

作用:为了阻止多个事务降记录插入到同一范围内,从而产生幻读。

   3、next-key lock:临键锁:锁定一个范围,并且锁定记录本身  (对于行的查询都是采用该算法)

采用该锁,主要目的是为了解决幻读问题,

例如一个索引有10,13,20这三个值,那给索引可能被next-key locking的区间为 (-无穷,10],(10,13],(13,20],(20,+无穷]。

a、当查询的索引含有唯一属性时,next-key lock进行优化,将其降级为 record lock,即锁住索引本身,而不是范围。

栗子:

create table t ( a int primary key);
inset into t select 1;
inset into t select 2;
inset into t select 5;

按照如下步骤进行:

mysql 锁机制和事务隔离级别(innoDB存储引擎)_第1张图片

表t共有1,2,5三个值,在上面,回话a中首先对a = 5 进行x锁定,,由于采用next-key lock 锁,所以锁定的范围为(2,5],由于a是主键且唯一,锁优化降级为record lock,仅锁定5的这个值,,这样会在b会话插入值4的时候不会阻塞,可以立即插入并返回。这样提高了应用的并发性。

b、当存在辅助索引,则仍然是next-key lock,而且会对辅助索引的下一个键值加上gap lock(空隙锁),

栗子:

create table z ( a int ,b int, primary key(a),key(b));
inset into z select 1,1;
inset into z select 3,1;
inset into z select 5,3;
inset into z select 7,6;
inset into z select 10,8;

表z中的b是辅助索引,若在会话a中执行:select * from z where b =3 for update,这时候sql通过b进行查询,因此使用next-key locking技术加锁,因为有两个索引,需要分别锁定,。对于列a=5的索引加record lock,对于辅助索引,加next-key lock,锁定的范围为(1,3),特别注意的是,存储引擎还会对辅助索引下一个键值加上gap lock,即还有一个辅助索引范围为(3,6)的锁,因此,在新的会话b中运行下面的语句,都会被阻塞:

select * from z where a = 5 lock in share mode;
insert into z select 4,2;
insert into z select 6,5;

第一个sql是因为在a=5上加x锁,第二个是辅助索引值2在锁定范围(1,3)。第三个sql是因为5在间隙锁(3,6)内。

下面的语句,则会立即执行:

insert into z select 8,6;
insert into z select 2,0;

五、锁问题

1、脏读:不同事务下,当前事务可以读取到另一个事务未提交的数据。

示例:

time 会话a 会话b
1 set session transaction isolation level read uncommitted;  
2   set session transaction isolation level read uncommitted;
3   begin;
4   select * from z where b =  1;   (两条数据)
5 insert into z select 9,1;  
6   select * from z where b =  1;   (三条数据)

在replication(复制)环境中的slave节点,不需要做精准查询,可以使用这个。

2、不可重复读(幻读):在一个事务内多次读取统一数据集合,在这个事务还没有结束时,另一个事务也访问该统一数据集合,做dml操作,导致第一个事务两次读取的数据不一样。

示例

time 会话a 会话b
1 set session transaction isolation level read committed;  
2   set session transaction isolation level read committed;
3 begin; begin;
4 select * from t;  
5   insert into t select 3;
6   commit;
7 select * from t;   (多出了 “3” 这条数据)  

在mysql的innodb中,使用next-key lock算法避免了不可重复读。这时候mysql默认设置的事务隔离级别为“read repeatable”;

不可重复读下,还存在着修改数据提交后被另一个事务读到的情况。

3、丢失更新:一个事务的更新操作会被另一个事务的更新操作所覆盖,导致数据的不一致。

示例:

time 会话a 会话b
1 begin; begin;
2 select * from z where a = 10 ;    
3   select * from z where a = 10 ;
4 .... .........
5

update z set b = 1 where a = 10;

commit;

 
6  

update z set b = 100 where a = 10;

commit;

7 select b  from z where a = 10  

分别查询出结果后,分别处理业务逻辑更新操作,会导致最后的结果时最后一个事务提交后的结果,

避免方法:在查询时候显示的使用for update,锁住该行。

六、阻塞问题

定义:因为锁之间的兼容性关系,在某些时刻,一个事务中的锁需要等待另一个事务的锁释放它所占的资源,这就是阻塞。

注:默认情况下,innodb不会回滚超时引起的错误异常,其实对大部分情况都不会回滚,会话如果执行成功一部分,则最后不会回滚,必须注意。

 

参考资料:《mysql技术内幕++innodb存储引擎》

你可能感兴趣的:(数据库)