《MySQL技术内幕四》-InnoDB-各种锁的东东

26 篇文章 0 订阅
18 篇文章 0 订阅

《MySQL技术内幕-InnoDB存储引擎》学习笔记四-各种锁的东东

2019-07-16 ヾ(◍°∇°◍)ノ゙ 不要拦着我 我还能学一会儿

2019-07-16 ヾ(◍°∇°◍)ノ゙ 不要拦

第6章 锁的东东

简单的说说:就是防止数据不一致,同时修改数据的情况存在的锁,然后就是不同锁之间的操作限制关系了。挺复杂的,不过了解一下就好了 (:з」∠) 通常也就是查看一下,也没法做什么操作的样子。


关于锁

锁嘛,分为两种:lock事务所 和 latch闩锁(轻量级锁)。

两种锁的简单查看:

-- latch 
show engine innodb mutex;
-- lock 
show engine innodb status;
-- 而关于具体额字段属性意义,需要的时候请在查询吧!懒得记录了╮(╯_╰)╭ 

InnoDB存储引擎的锁

锁类型与兼容模式

InnoDB实现了以下两种类型的行锁

  • 共享锁(s):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
  • 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。

另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁

  • 意向共享锁(IS):事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
  • 意向排他锁(IX):事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

简单的个人理解:S锁就是读取,基本都随意的;X为删改操作的锁,处理的时候啥都不能在东了。而加I 的表示在表级意向而已,只是说明这个表用在操作,具体什么操作还是要看行级锁的。

而具体的兼容如下:(兼容为可以同时操作不冲突,不用等待锁释放)

当前锁模式/是否兼容/请求锁模式XIXSIS
X冲突冲突冲突冲突
IX冲突兼容冲突兼容
S冲突冲突兼容兼容
IS冲突兼容兼容兼容

在操作数据的时候会给对应的行、表加锁的。如果一个事务请求的锁模式与当前的锁兼容,InnoDB就请求的锁授予该事务;反之,如果两者两者不兼容,该事务就要等待锁释放。

这里有查看锁情况的SQL:

-- 就是这三个 具体字段啥意思 自个查询吧 
select * from information_schema.innodb_trx
select * from information_schema.innodb_locks
select * from information_schema.innodb_lock_waits
-- 这3个东东的数据是实时的,所以一般情况下是空的
--【更有可能的是,没有information_schema库的查看权限,新增的用户默认是没有此权限的】
读取情况的锁使用

InnoDB的读取数据的时候,会给读取的数据加上锁,而如果读取到正在修改的数据时,就会冲突啥的,所以为了并发效率啥的,就有了一致性非锁定读这种东东,而对应的一致性锁定读 看看就好。

一致性非锁定读:这个是InnoDB默认的读数据模式,就是在读取数据的时候,如果出现数据已经被别的事务修改的情况,则会去读取快照数据(使用undo中的回滚数据获取),已保证读取不用等待。

在不同事务隔离情况下读取还有不同,可以了解一下:

默认的InnoDB的事务隔离是REPEATABLE READ,这种情况下读取的的是事务开始是的快照;

而READ COMMITTED 事务情况下,读取的是最新的快照(算上还在事务中,但已提交的快照);

select @@tx_isolation
-- 查看事务类型
set session tx_isolation = 'XXXXXXXX'

一致性锁定读:这东东压根感觉不会使用的,在读取数据的时候手动添加数据的X/S锁,保证数据读取的时候没有事务在修改数据,这种是会阻塞其他事务的。

操作的话也记一下好了╮(╯_╰)╭:

select ... for update		 --这个加X锁
select ... lock in share mode --这个加S锁
-- 使用的时候,当事务提交,锁就被释放了。
-- 所以使用的时候务必加上BEGIN,START TRANSACTION 或者 SET AUTOCOMMIT = 0 ;

这种一致性锁定读,会用到的地方是在外键使用的时候,数据插入时回会去SELECT父表,为保证数据一致,这时会给父表加上S锁,这时如果有处理附表数据是的X锁事务就会被阻塞了。

感觉也就这里会用到一致性锁定读了,自己平常做的WEB方面的业务都用不到的。。ε=(´ο`*)))唉 。。

锁的算法(略)

简单锁说一下,有三种:(简单了解一下就可以了)

  • Record Lock:单个行记录上的锁,【这时最好理解的锁了】
  • Gap Lock : 间隙锁,锁定一个范围,但不包含记录本身
  • Next-Key Lock:Gap Lock +Record Lock的模式,锁定一个范围,且包含记录本身。对于行查询,这个是默认的锁定算法。
锁的问题

这应该是InnoDB执行并发事务所带来的一些的问题。 相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持可以支持更多的用户。但并发事务处理也会带来一些问题,如下;

幻读【Phantom Problem】

在一个事务下,连续执行两次相同的SQL语句可能导致不同的结果,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

解决的方式就是前边说的,锁的算法Next-Key Lock。比如一个事务中查询:select * from table where id >x for update 这个时候会给区间id ->(x,+∞)的数据全部加上X锁。这个范围的数据都不允许插入的,避免幻读。

在正常我们的业务的事务里面,好像都没有谁用到了 一致性锁定读 这种的写法。(ˇˍˇ) 想~

脏读【Dirty Read】

一个事务读取到了,其他事务中已对数据做的修改,但是没有提交的数据,也叫脏数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这就是所谓的脏读。

这种的问题只会出现在事务为Read uncommitted的情况,通常是不可能会遇到的。正常默认数据库的事务级别都比这个高的。╮(╯_╰)╭ 略过了。。。

不可重复读【Non-Repeatable Reads】

一个事务在读取某些数据已经发生了改变、或某些记录已经被删除了!这种现象叫做“不可重复读”。

这东东跟幻读很像,一个是处理的时候别的事务插入数据,这个是别的事务修改数据。

更新丢失【Lost Update】

就是两个事务同时处理一条数据,最后两个事务提交的时候其中一个被另一个覆盖了。

这里有两种情况:

第一种:就是标准意义上的更新丢失:【这种只要是有事务的都不会发生】

  • 事务T1更新记录r为v1,但事务为提交 【–预计值vaulev1】
  • 事务T2更新记录r为v2,也未提交事务 【–预计值vaulev1v2】
  • 事务T1提交【–结果值vaulev1 】
  • 事务T2提交【 --结果值vaulev1v2 – 丢失更新值vaulev2】

这里有个前提,这里的更新是直接使用update 语句进行数据更新操作(包括业务逻辑也在语句中)
比如这里的语句是update table set vaule = CONCAT(vaule,'v1/v2') where id = r

这种情况出现的T2事务覆盖T1事务的情况为标准意义上的更新丢失,但是通才是不可能会出现的,如论什么级别的事务,在更新的时候都会加锁,也就是在T2更新的时候会因为T1的事务而阻塞的。

第二种:常见业务处理上的更新丢失:【这种是会发生的,尤其是在钱包处理上】

  • 事务T1查询user1钱包金额amount,放入本地回显或者业务处理;
  • 事务T2查询user1钱包金额amount,放入本地回显或者业务处理(这里可能展示在不同终端上);
  • 事务T1修改金额amount(内存中的值) - 100块 ,并提交。
  • 事务T2修改金额amount (内存中的值)- 100块 ,并提交。

这里会出现的问题是,最终的数据只是 amount -100,其中前一条的数据更新丢失了。

这里对于这第二种的问题,书中给出的解决方式是:两个事务都使用select .. for update方式加X锁。

但是我认为这个不是很好用,因为在正常业务的情况下,以上T1事务查询和修改可能还不是同一个事务。

所以我觉得,应该把修改值直接使用UPDATE的时候限制,如:update wallet set amount = amount - 100 where user = user1 and amount >= 100,这样就变成第一种的情况了,也就不会发生更新丢失了。

注意:在业务处理的时候需要判断update语句是否更新了具体的数据,因为有可能更新数量为0;为数据条件边界的问题。

事务级别

通常有4种隔离级别的事务:

隔离级别/读数据一致性及允许的并发副作用读数据一致性脏读不可重复读幻读
未提交读(Read uncommitted)最低级别,只能保证不读取物理上损坏的数据
已提交度(Read committed)语句级
可重复读(Repeatable read)事务级 【InnoDB默认事务级别】
可序列化(Serializable)最高级别,事务级

最后要说明的是:各具体数据库并不一定完全实现了上述4个隔离级别,例如,Oracle只提供Read committed和Serializable两个标准级别,另外还自己定义的Read only隔离级别:SQL Server除支持上述ISO/ANSI SQL92定义的4个级别外,还支持一个叫做"快照"的隔离级别,但严格来说它是一个用MVCC实现的Serializable隔离级别。MySQL支持全部4个隔离级别,但在具体实现时,有一些特点,比如在一些隔离级下是采用MVCC一致性读,但某些情况又不是。

阻塞

在事务中的锁冲突,需要等待其他线程释放资源的情况。默认情况下等待时间50秒,并且超时不会滚的。

-- 查看阻塞超时时间设置  默认50 秒
select @@innodb_lock_wait_timeout
-- 查看锁超时是否回滚  默认 --OFF:不会滚
show variables like 'innodb_rollback_on_timeout'   

所以这里如果事务【大的存储过程同时执行】,出现阻塞超时的情况需要程序判断处理的是都ROLLBACK。

死锁

死锁就是指两个+的事务在执行过程中,因争夺锁资源而造成的一种相互等待的现象。

MyISAM表锁是deadlock free的,这是因为MyISAM总是一次性获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。但是在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,这就决定了InnoDB发生死锁是可能的。

发生死锁后,InnoDB一般都能自动检测到,并使一个事务释放锁并退回,另一个事务获得锁,继续完成事务。

然后,死锁的发生概率是相当低的,等碰到的再说吧。

注意:如果有使用一致性锁定读或者降低了事务隔离模式的情况下才需要注意一下死锁的问题!

锁升级

就是行锁,升级为页锁,或者表锁,这样子的东东。

在InnoDB中有两种情况下会发生这种升级的情况:

  • 单独的SQL语句在一个对象上持有的锁数量超过阈值,默认为5000条。【需要是统一个对象的,不同对象不叠加】
  • 锁资源占用的内存超过了激活内存的40%的情况。

小结一下

这个锁的东东,在了解了之后,需要注意几点即可,为的是避免一些由于锁导致的问题:

  • 对于事务上的更新丢失,在代码业务编写等地方的时候需要尤其注意一下
  • 对不同程序(包括存储过程)访问处理同一组表的时候,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大减少死锁的机会。
  • 选择合理的事务大小,小事务发生锁冲突的几率也更小。(尤其在使用存储过程处理大数据业务的时候)

2019-07-17 小杭

锁这个东东,没什么要配置调整的,只是平常使用的时候要注意一下即可!

之后看看事务讲解的章节,这个可能会比较麻烦了 (:з」∠)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小_杭

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

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

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

打赏作者

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

抵扣说明:

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

余额充值