MySQL InnoDB存储引擎-锁篇

锁机制用于管理对共享资源的并发访问。InnodDB存储引擎中,会在数据库内部的多个地方使用锁,从而允许多种不同资源的并发访问。

比如:操作缓冲池的LRU列表,删除、添加、移动LRU列表的元素,为了保证一致性,必须有锁的介入。数据库系统使用锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性。

InnoDB存储引擎锁的实现提供一致性的非锁定读、行级锁支持。行级锁没有额外的开销,并可以同时得到并发性和一致性。

锁的类型

行级锁

InnoDB存储引擎实现了两种如下的行级锁:

  • 共享锁(S Lock),允许事务读一行数据
  • 排他锁(X Lock),允许事务删除或更新一行数据

设T为事务,如果一个T1事务得到了行r上的一个共享锁,此时T2在去申请共享锁则会立即成功,并不会阻塞,即共享锁之间是兼容的。此时再来一个T3事务,想要修改行r的数据,则需要等待T1、T2事务都释放共享锁才能成功。这其实就是我们熟知的读写锁

我们也知道,数据库由多个表组成,表又由多个行组成,同样的,InnoDB存储引擎在表、行上分别也支持不同粒度的锁。即可以对表加锁1的同时,对其中的某行加锁2.

意向锁

InnoDB支持一种额外的锁方式,用来实现不同粒度的加锁。其将锁定的对象又分为多个粒度(数据库、表、页),对细粒度的对象上锁,需要先对粗粒度的对象上锁(预加小锁,先上大锁)。

    

如图,如果需要对底层某条记录上锁,分别需要对数据库A、表x、页y上意向锁IX,最后再对记录上X锁。

  • 意向共享锁(IS Lock),事务想要获得一张表中某几行记录的共享锁
  • 意向排他锁(IX Lock),事务想要获得一张表中某几行记录的排他锁

这里的意向排他锁同上面的排他锁性质并不一样,如果T1对表1加了IX锁,然后T2再对表1加IX锁,也是能成功的。真正的锁申请被排他性锁阻塞,只会发生在行级锁上。

命令SHOW ENGINE INNODB STATUS可以用来查看当前的锁正阻塞在哪里的数据上。

一致性非锁定读

InnoDB存储引擎通过多行版本控制的方案,来读取当前执行时间数据库中行的数据,称为一致性的非锁定读。

如果读取的行正在被delete或者update,此时读取操作并不会因此去等待行数的排他锁释放,而是去读取行的一个快照数据(修改前的数据的备份);

    

快照数据是通过undo段来读取的,undo段是事务用来回滚数据而做的某种备份。这里正好用来提供非锁定读。

值得一提的是,仅在事务隔离级别为REAN COMMITTED和REPEATABLE READ(InnoDB默认隔离级别)下,InnoDB存储引擎会使用一致性非锁定读。

在REAN COMMITTED事务隔离级别下,一致性非锁定读会读取被锁定行的最新的一份快照数据,而在REPEATABLE READ事务隔离级别下,一致性非锁定读会读取事务开始时的行数据版本。即仅在REPEATABLE READ隔离级别及以上,同个事务内对同行数据的读取才会始终是一致的(也就是重复读的字面意思喽)。

一致性锁定读

某些情况下,用户需要显示地对数据库读取操作加排他锁,以保证业务逻辑上的一致性,这就要求数据库要支持显示加锁的语句,即使是只读操作。

InnoDB存储引擎支持两种一致性的锁定读操作:

  • selset ... for update
  • select ... lock in share mode

selset ... for update对读取的行记录加一个X锁,其它事务对该行加的任何锁都会被阻塞。select ... lock in share mode对读取的记录加一个S锁,其它事务可以对同一行数据加S锁,但如果是X锁,则会阻塞。值得注意的是,这里的锁是在事务提交后释放的。

锁的算法

  • Record Lock:单个行记录上的锁
  • Gap Lock:间隙锁,锁定一个范围,但不包含记录本身
  • Next-Key Lock:Gap Lock + Record Lock,锁定一个范围,包含记录本身(为解决幻读问题而被设计的)

InnoDB对于行的查询都是采用的Next-Key Lock算法。加入一个索引有10、13、20这三个值,那么该索引可能被Next-Key Lock锁住的区间(左开右闭)有:

(-∞,10]

(10,13]

(13,20]

(20,+∞)

如果事务T1已经通过Next-Key锁定了范围:

(10,13]、(13,20]

此时再插入记录15,则锁定的范围会变成:

(10,13]、(13,15]、(15,20]

当查询的索引含有唯一属性时,InnoDB存储引擎又会对其优化,将其降级为Record Lock,仅锁住索引本身。

举个栗子,如下创建一个表:

create table z(a int, b int, primary key(a), key(b));
insert into z select 1,2;
insert into z select 3,4;
insert into z select 5,6;

表z的列b是辅助索引,即列表b的值没有唯一属性,若在会话A中执行下面的语句:

selsct * from z where b = 4 for update

很明显,这是SQL语句通过辅助索引列b进行查询,因此对该列使用Next-Key算法加锁,另外因为还有个聚集索引,需要对其加锁Record Lock(具有唯一属性的加Record Lock);

故列a上的锁有:5

列b上的锁有:(2,4], (4,5]

此时,如果在新会话B中执行如下SQL,都会被阻塞:

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

第一个语句阻塞是因为列a上已经有一个X锁了,第二、三条语句阻塞,则是因为3、5分别在列b的两个Next-Lock锁的范围内。

而如果在新的会话C中执行如下的SQL语句,则不会阻塞:

insert into z select 4,2;
insert into z select 6,7;

从这个例子也可以看出,Next-Key的作用是为了阻止多个事务将记录插到同一个范围内,而这会导致幻读(phantom problem:是指在同一事务下,连续执行两次同样的SQL语句可能导致不同的结果,第二次的SQL语句可能返回之前不存在的行)问题的产生。例如在上面的例子中,会话A已经锁定了b=3的记录,若此时没有Next-Key Lock锁住(2,4],(4,6]范围,那么用户可以在b=4行的下一个位置,再插入一条同样列b=4的记录,那么会话A中的用户再次执行同样查询时将会返回两条b=3的记录了,这将导致幻读问题的产生,也就意味着违反了事务的隔离性。

上述的Next-Key Lock算法是在REPEATABLE READ隔离级别下使用的,若将事务隔离级别设置为READ COMMITED,则会自动关闭Next-Key算法,转而使用Record Lock,因为这种隔离级别下,不存在幻读问题。

锁问题

脏读

READ UNCOMMITED事务隔离级别下,在会话A中,在事务没有提交的前提下,会话B中两次select操作取得了不同的结果,并且会话A的事务并没有提交,即产生了脏读。

       

生产环境中一般都是默认REPEATABLE READ事务隔离级别,所以一般没有脏读现象。脏读隔离在一些比较特殊的情况下才会用到,比如sql备份环境中的slave结点,且该节点的查询并不需要特别精确的返回值,此时设置为READ UNCOMMITED隔离级别,有助于性能提升。

不可重复读

在READ COMMITED隔离级别下,存在不可重复读的问题,不过一般该问题都可以接受,因为读到的是事务已经提交的数据,本身并不会有很大的问题。如前一节所述,InnoDB存储引擎在READ REPEATABLE隔离级别中,采用Next-Key Lock算法避免不可重复读问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fireplusplus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值