http://arthornye.github.io/2018/mysql/mysql%E4%BA%8B%E5%8A%A1%E5%92%8C%E4%BA%8B%E5%8A%A1%E9%9A%94%E7%A6%BB%E6%9C%BA%E5%88%B6
在学习mysql的事务隔离机制的过程中,对mysql的会话和事务的概念有点模糊不清,这里主要通过mysql可视化工具sequel pro来记录在实践过程中遇到的问题以及思考。
mysql会话begin自动提交事务
我们建立一个mysql连接,然后开启一个会话session1,执行上面的sql。这条sql会在行记录上加锁。但是当前的事务是没有提交的,mysql的默认事务隔离机制是RR(可重复读)。我们在另一个会话session2中执行:
会话2会报超时:
通过这种方式我们成功验证了session1的事务还在执行中,数据库的记录已经上锁。
那么我们再去验证,当前会话的某个事务还在执行中,当前会话是不是可以继续执行其他事务,执行下面的select
执行结果:
实际上这个事务没有被提交上去,但是在本会话中再提交select却可以读取到这个值。这里其实我们应该将其理解为实际上本事务并没有提交,数据库会检测是同一个会话提交的sql,将其整合为一个未提交的事务。所以这里的变更对其他会话的事务依旧是不可见的。
那么什么时候这个事务什么时候会被提交?我们可以联想到是不是在执行一个commit本会话的事务便会被提交,对其他的会话可见。我们在session1单独执行一句commit,在session2中执行:
结果:
可见我们的事务已经被提交了,同样的,我们发现在session1中执行一句begin,看session2同样查询的结果:
做个总结:
当我们忘了对一个事务进行提交的时候,该会话接下来执行的事务也会叠加,直到我们显示的去进去提交或者开启一个新的事务。一个会话的事务没有提交可能导致另一个会话获取不到锁。
mysql事务隔离机制
熟悉了我们的可视化工具,我们尝试分析mysql的事务隔离机制,相信对mysql有基本了解的都知道,mysql的四种隔离机制。这四种隔离机制是我们分析数据库锁机制的基本,我也将会在本篇介绍隔离机制之后介绍一下mysql的锁机制。接下来的介绍需要我们去改变mysql的事务隔离机制,我们可以通过语句:
mysql的默认隔离机制RR,前一个是全局session的隔离级别,后一个是当前会话的隔离级别,我们可以在当前会话中设置隔离级别,通过sql语句:
Read Uncommited
这个基本不用考虑,因为如果事务没有被提交就被其他的事务看到这样的设计从逻辑上来看是不合理的,会造成大量的脏读。
Read Commited(不可重复读)
在这个隔离机制下,事务在提交之后对另一个事务可见。如果在一个事务A的执行过程中进行了两次查询,事务B在查询间隙进行了数据更新,这个隔离级别会存在脏读+幻读的可能。我们新建一个test_test表,开启session1,执行事务A,不提交,模拟不可重复读:
开启session2,执行事务B,直接提交:
回到session1,执行查询:
RC隔离级别下不可重复读,两次读的结果不一致。
用同样的方式,测试该隔离模式下会不会存在幻读:
该模式下存在幻读(如果没有显式begin开始一个事务,事务都默认自动提交,部分语句没有加begin,commit,自动提交)。
Repeatable Read(可重复读)
首先通过恢复到该数据库隔离级别:
模拟是否存在脏读,session1:
session2:
session1:
RR隔离模式下不存在脏读,那么mysql是如何实现该隔离模式下的避免脏读?了解过juc的cas的都知道我们可以通过在共享内存标记一个版本号,来防止aba问题。那么类比mysql也是通过在每行的记录后面添加一列标记版本号,读的时候不会修改这个版本号,但是更新删除都会版本号+1。参考上面的例子,session1在执行事务A的时候,假设当前事务的版本号为1,当前事务并没有提交,然后session2执行了更新事务B,提交之后数据库该行记录的版本号变成了2。在该隔离模式下事务A只会读取到版本号不大于当前事务版本号的记录,也就是说,虽然这条记录的更改已经在数据库真实存在,但是事务A并不能读取到这条记录的变更。
SELECT时,读取创建版本号<=当前事务版本号,删除版本号为空或>当前事务版本号。
INSERT时,保存当前事务版本号为行的创建版本号。
DELETE时,保存当前事务版本号为行的删除版本号。
UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行。
2018.11.20增加对MVCC多版本并发控制的理解:如果我们数据库中某一条记录的值经历的版本变化为4->3->2->1,那么数据库引擎同时会记录一个回滚日志,根据对应事务的版本号去回滚到事务对应的值。
同样的方式看该该隔离级别下是否存在幻读,session1,事务A:
session2,事务B:
session1,事务A:
session1,事务A:
可以看到这里是不存在幻读的,很多博客在介绍mysql的四种隔离级别的时候会说RR隔离模式下可以避免脏读但是不能避免幻读,我们通过实践看到这个级别是可以避免幻读的。
Serializable(串行化)
这个隔离级别,个人的理解可以类比RetrantWriteReadLock的设计。事务在执行的时候如果数据行存在写锁(排他锁),读锁是共享锁,这种情况下会等待写锁释放。同样的如果是事务尝试去获取某个数据行的写锁,发现共享锁的读锁没有完全释放开,也需要等待读锁全部释放完才可以获取到写锁。
将数据库隔离级别调整为串行,然后分别执行事务,session1,事务A:
session2,事务B:
执行结果,事务B,获取锁超时,Lock wait timeout exceeded; try restarting transaction。通过下面的语句也可以查看到当前存在锁竞争。
总结
本篇主要介绍了mysql的四种事务隔离级别,这里终结下它们的特点:
Read Uncommited | 不可重复读 | 幻读 |
---|---|---|
Read Commited | 不可重复读 | 幻读 |
Repeatable Read | 可重复读 | 不存在幻读 |
Serializable | 不可重复读 | 不存在幻读 |
关注我每天进步一点点
你点的在看,我都当成了喜欢
本文分享自微信公众号 - JAVA乐园(happyhuangjinjin88)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。