mysql事务和事务隔离机制

阅读文本大概需要3分钟。

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自动提交事务
1
2
begin;
update retail_order set order_status=111 where order_code='899120869590';

我们建立一个mysql连接,然后开启一个会话session1,执行上面的sql。这条sql会在行记录上加锁。但是当前的事务是没有提交的,mysql的默认事务隔离机制是RR(可重复读)。我们在另一个会话session2中执行:

1
2
3
begin;
update retail_order set order_status=111 where order_code='899120869590';
commit;

会话2会报超时:

1
Lock wait timeout exceeded; try restarting transaction

通过这种方式我们成功验证了session1的事务还在执行中,数据库的记录已经上锁。

那么我们再去验证,当前会话的某个事务还在执行中,当前会话是不是可以继续执行其他事务,执行下面的select

1
select order_status from retail_order where order_code='899120869590'

执行结果:

1
111

实际上这个事务没有被提交上去,但是在本会话中再提交select却可以读取到这个值。这里其实我们应该将其理解为实际上本事务并没有提交,数据库会检测是同一个会话提交的sql,将其整合为一个未提交的事务。所以这里的变更对其他会话的事务依旧是不可见的。

那么什么时候这个事务什么时候会被提交?我们可以联想到是不是在执行一个commit本会话的事务便会被提交,对其他的会话可见。我们在session1单独执行一句commit,在session2中执行:

1
select order_status from retail_order where order_code='899120869590'

结果:

1
111

可见我们的事务已经被提交了,同样的,我们发现在session1中执行一句begin,看session2同样查询的结果:

1
111

做个总结:

当我们忘了对一个事务进行提交的时候,该会话接下来执行的事务也会叠加,直到我们显示的去进去提交或者开启一个新的事务。一个会话的事务没有提交可能导致另一个会话获取不到锁。

mysql事务隔离机制

熟悉了我们的可视化工具,我们尝试分析mysql的事务隔离机制,相信对mysql有基本了解的都知道,mysql的四种隔离机制。这四种隔离机制是我们分析数据库锁机制的基本,我也将会在本篇介绍隔离机制之后介绍一下mysql的锁机制。接下来的介绍需要我们去改变mysql的事务隔离机制,我们可以通过语句:

1
select @@global.tx_isolation,@@tx_isolation;
1
REPEATABLE-READ	REPEATABLE-READ

mysql的默认隔离机制RR,前一个是全局session的隔离级别,后一个是当前会话的隔离级别,我们可以在当前会话中设置隔离级别,通过sql语句:

1
2
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
Read Uncommited

这个基本不用考虑,因为如果事务没有被提交就被其他的事务看到这样的设计从逻辑上来看是不合理的,会造成大量的脏读。

Read Commited(不可重复读)

在这个隔离机制下,事务在提交之后对另一个事务可见。如果在一个事务A的执行过程中进行了两次查询,事务B在查询间隙进行了数据更新,这个隔离级别会存在脏读+幻读的可能。我们新建一个test_test表,开启session1,执行事务A,不提交,模拟不可重复读:

1
2
begin;
select * from test_test where code=1
1
2
3
4
5
1	wmm	1
2	wmm	1
3	wmm	1
4	wmm	1
5	wmm	1

开启session2,执行事务B,直接提交:

1
update test_test set name='wmm' where code=1;

回到session1,执行查询:

1
select * from test_test where code=1
1
2
3
4
5
1	pyx	1
2	pyx	1
3	pyx	1
4	pyx	1
5	pyx	1

RC隔离级别下不可重复读,两次读的结果不一致。

用同样的方式,测试该隔离模式下会不会存在幻读:

1
2
begin;
select * from test_test where code=1
1
2
3
4
5
1	pyx	1
2	pyx	1
3	pyx	1
4	pyx	1
5	pyx	1
1
insert test_test values(6,'yqz',1);
1
2
3
4
5
6
1	pyx	1
2	pyx	1
3	pyx	1
4	pyx	1
5	pyx	1
6	yqz	1

该模式下存在幻读(如果没有显式begin开始一个事务,事务都默认自动提交,部分语句没有加begin,commit,自动提交)。

Repeatable Read(可重复读)

首先通过恢复到该数据库隔离级别:

1
set session transaction isolation level repeatable read;

模拟是否存在脏读,session1:

1
2
begin;
select * from test_test where code=1
1
2
3
4
5
6
1	pyx	1
2	pyx	1
3	pyx	1
4	pyx	1
5	pyx	1
6	yqz	1

session2:

1
update test_test set name='pyx' where code=1;

session1:

1
select * from test_test where code=1
1
2
3
4
5
6
1	pyx	1
2	pyx	1
3	pyx	1
4	pyx	1
5	pyx	1
6	yqz	1

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:

1
2
begin;
select * from test_test where code=1
1
2
3
4
5
6
1	pyx	1
2	pyx	1
3	pyx	1
4	pyx	1
5	pyx	1
6	pyx	1

session2,事务B:

1
insert test_test values(7,'yqz',1);

session1,事务A:

1
select * from test_test where code=1
1
2
3
4
5
6
1	pyx	1
2	pyx	1
3	pyx	1
4	pyx	1
5	pyx	1
6	pyx	1

session1,事务A:

1
commit;
1
2
3
4
5
6
7
1	pyx	1
2	pyx	1
3	pyx	1
4	pyx	1
5	pyx	1
6	pyx	1
7	yqz	1

可以看到这里是不存在幻读的,很多博客在介绍mysql的四种隔离级别的时候会说RR隔离模式下可以避免脏读但是不能避免幻读,我们通过实践看到这个级别是可以避免幻读的。

Serializable(串行化)

这个隔离级别,个人的理解可以类比RetrantWriteReadLock的设计。事务在执行的时候如果数据行存在写锁(排他锁),读锁是共享锁,这种情况下会等待写锁释放。同样的如果是事务尝试去获取某个数据行的写锁,发现共享锁的读锁没有完全释放开,也需要等待读锁全部释放完才可以获取到写锁。

1
set session transaction isolation level serializable;

将数据库隔离级别调整为串行,然后分别执行事务,session1,事务A:

1
2
begin;
select * from test_test where code=1
1
2
3
4
5
6
7
8
1	pyx	1
2	pyx	1
3	pyx	1
4	pyx	1
5	pyx	1
6	pyx	1
7	yqz	1
8	yqz	1

session2,事务B:

1
update test_test set name='pyx' where id=1

执行结果,事务B,获取锁超时,Lock wait timeout exceeded; try restarting transaction。通过下面的语句也可以查看到当前存在锁竞争。

1
select * from information_schema.innodb_locks
1
2
5930258879:6688936:3:3	5930258879	X	RECORD	`souche_retail`.`test_test`	PRIMARY	6688936	3	3	NULL
5930501833:6688936:3:3	5930501833	S	RECORD	`souche_retail`.`test_test`	PRIMARY	6688936	3	3	NULL
总结

本篇主要介绍了mysql的四种事务隔离级别,这里终结下它们的特点:

Read Uncommited不可重复读幻读
Read Commited不可重复读幻读
Repeatable Read可重复读不存在幻读
Serializable不可重复读不存在幻读

往期精彩

01 漫谈发版哪些事,好课程推荐

02 Linux的常用最危险的命令

03 互联网支付系统整体架构详解

04 优秀的Java程序员必须了解的GC哪些

05 IT大企业有哪些病,别被这些病毁了自己?

关注我每天进步一点点

你点的在看,我都当成了喜欢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值