今天中午准备吃饭的时候,随便走进了一家餐厅里,找了一个位置就坐了下来,随手一招就把服务员喊过来问道
菜单呢?
这时候服务员愣了一下 开始找菜单去哪儿了。结果发现被另外一个客人A拿在手上了。
我说你这么大的餐厅不会只有一份菜单吧?
这时服务员从抽屉里面不慌不忙的拿出另外一个备份的菜单给我,这个场景怎么样? 有没有似曾相识与mysql的一致性非锁定读是不是基本一致?
一致性非锁定读
其主要的意思就是在讲MVVC多版本并发的情况下,怎么让读不会被阻塞。在上面的场景中客户A是一个事务的角色,而我也是一个事务的角色,菜单是某行的记录, 当 服务员(Mysql服务器) 发现某个记录被锁定的时候,依旧可以让你读到数据,只不过在这时候你读的不是该行数据,而是行记录的快照数据,也就是最新事务提交过后的版本。
应对这上面的场景,也就是服务员把备份的菜单给我,因为备份的菜单并不会有客户拿得到
(不会有其他事务会去锁定快照数据)
但是在一致性非锁定读的环境下有2种情况需要注意
- REPEATABLE READ
- READ COMMITED
这2种情况在一致性非锁定读的表现是不一样的。我们可以看个例子。
我们先创建一张表,并且插入几条数据
我们先查看一下当前的事务隔离级别
也就是mysql 默认的事务隔离级别 repeatable read,这时候我们新开2个connection,分别单独开启事务
这时候我们在左边的窗口插入一条数据,提交事务, 再到右边窗口看看
依旧是3条数据。这就是repeatable read的特性,在当前事务下,并不会读到其他事务依旧提交的值。
接下来我们把事务隔离级别切换成read commited
然后我们再开启事务,并且执行查询语句
这时候,我们在左边窗口来一个插入,提交,再到右边窗口看看
这时候,你会发现 右边窗口 居然能直接把左边事务提交的值给查询出来。
也就是说在Read Committed的事务隔离级别之下,当前事务是采取读取最新的一份快照数据。
而在Repeatable Read的事务隔离级别下,当前事务采取的是读取事务开始时候的数据版本
一致性锁定读
对于一致性锁定读的操作有2中
select … For Update
select … lock in share mode
这两者的区别主要在于前者是对行记录添加X锁, 该锁会与任何其他类型的锁冲突,阻塞。而后者只是添加了一个S锁,而其他事务也可以在该行记录上添加S锁。
举个栗子~还是餐厅的情景。你在餐厅里面只能阅读菜单,但你却无法修改菜单。而菜单一旦在修改的情况下。你是无法阅读了,因为菜单这时候被收了回去 统一修改。
这时候相当于服务员(事务A) 对菜单添加了X锁导致无法看到菜单(该行记录) 那请问我现在能不能看到这行记录呢(对这行记录添加S锁)? 结合刚才的思路 想一想?
答案是可以看,但不能加锁。你可以看到菜单的备份(数据快照版本) 服务员会把备份的菜单给你,你可以先看着。 等菜单修改完毕(事务把锁释放),这些备份的就会收回来换成新的。记得锁都是在事务的开启下进行的
在讲解锁的算法之前,我觉得得先探讨2个问题,锁的存在是为了解决什么问题?其他有几种问题,但我这边只探讨面试比较常见的2种
锁的问题
- 脏读(dirty read)
- 不可重复读 (phantom problem)
脏读
什么是脏读?, 如果你想知道什么是脏读,现在就带你研究。
脏读是指事务对缓存池中记录的修改,并且还没提交。
直白点,就是骚年你开的事务还没commit阿 而另外一个事务却能读到你更新的内容。这显然就是违反了事务的隔离性
序号 | sessionA | sessionB |
---|---|---|
1 | SET @@tx_isolation=’read-uncommitted’ | |
2 | SET @@tx_isolation=’read-uncommitted’ | |
3 | 1 | begin; |
4 | 1 | select * from read —-记录—- 1,2 3,4 |
5 | insert into read value (7,8) | |
6 | 1 | select * from read —-记录—- 1,2 3,4 7,8 |
很明显就是在seesionB里面可以直接读取到sessionA插入的数据,这是不正确的。但为什么你不会遇到呢?
就是因为这是在事务隔离级别read uncommited 下才会发生,我们的mysql innodb默认并不是它。
不可重复读(幻读)
幻读是指在一个事务下,两次同样的sql语句,查询出来的确实不一样的结果,也就是可能在第二次读的时候返回了一些第一次不存在的行。我相信说完这个,你就很清晰的知道为什么mysql innodb默认是采用repeatable read作为默认的事务隔离级别了
对于幻读,我们来演示一下? 我们先创建一张表
并且随便撒点数据上去,开启2个session
然后我们把事务等级修改成为read committed 然后对数据加锁。把 a>5的数据全部加锁,这时候再往后插入10,11这一行数据,我们会发现在sessionA的结果如下
我们依旧可以读到我们加了锁的数据,这是为什么? 接下来我们就讲解mysql锁的机制,去解决这些问题。
锁的算法
- Record Lock 单个行记录上的锁
- Gap Lock 间隙锁
- Next-Key Lock 范围锁定,并且锁定记录本身
Record Lock
对于record lock的定义很简单, 它就是针对表的隐式主键做来进行锁定的。
我们来看个例子证明一下
我们会发现, sessionB 卡在里了。然后我们看一下目前锁的记录
并且我们在事务表中也能发现
当前事务正在等待锁的释放。
Gap Lock 间隙锁
间隙锁通常是与 next-key lock 一起使用。我们直接讲next-key lock并且顺便讲gap lock
Next-Key Lock
这个锁的设计,主要也是为了解决我们刚才所说的幻读的问题,也就是对某个范围进行加锁。我们只要记得next-key lock就是对某个范围的锁然后我们直接上例子来观察。
然后我们随便插入点数据。这时候 a 是主键索引, b是辅助索引。
这时候,在sessionA里面 对b=3的数据加一个X锁。 然后在sessionB里面对a=5的数据再加一个S锁,这时候会发现 sessionB 就卡在那里了。
这时候有人会说 那不是废话吗。 b=3的时候 a=5阿, 那肯定 不能对a=5加锁吖。
那我们再看一个例子?
这时候。我还是锁住了b=3的记录,然后我在sessionB里面插入了一条数据 2,2
然后居然被阻塞了? 这是为什么?
其实这就是next-key lock的作用,对于辅助所以,其实next-key lock锁住的记录是 (1,3],和 (3,6] 这是什么意思?
这里是指,如果你插入的数据
例子1. 主键数据a在 (3,5) 且 b在 [1,3]
例子2. 主键数据a在 (5,7) 且 b在 [3,6]
这两个范围内的话。都是会被锁定的。对于例子1, 其实是next-key lock的作用,对于例子2, 是 gap lock的作用 这里需要特别注意一下, 因为innodb引擎默认会对辅助索引的下一个键值加上一个gap lock, 也就是例子2
总结
其实以上的内容就是今天所有的内容了。主要是讲解为什么我们要使用repeatable read作为默认的mysql innodb事务隔离级别, 原因其实很明了了。为了解决幻读,脏读等问题。以及让各种并发不会产生异常问题。因此才选择了repeatable read