事务会遇到的问题
我们在使用数据库中事务的时候经常会有以下几类问题:脏读、不可重复读、幻读这些问题
为了掩饰这个结果,我在这里先创建测试数据库
CREATE TABLE test (
username CHAR(10),
balance INT
)
INSERT INTO test VALUES('tom', 100);
INSERT INTO test VALUES('jack', 200);
- 脏读
脏读其实这个词看起来很让人难以理解,个人感觉使用它的隔离级别的英文会好理解一些:read uncommitted。翻译过来就是读取未提交,假如我们有两个线程操作同一个数据库表的一条记录,线程A开启事务并修改记录X,但是并未提交,而线程B开启事务并读取记录X,那么B能够读取到A修改后的记录。这个过程B读取的就是脏数据。
有人会问:“这样不是很好吗?数据一更新马上这边就会知道了”,这样其实不好,因为A并没有提交自己这条记录,它只是修改了,那么假如它一旦回滚了,这边B拿到的数据就可能有问题了。
那我们来演示一下,首先,将mysql的事务隔离级别设置成read uncommitted。
我们看看默认Mysql的事务隔离级别
这个隔离级别会在后文提到,那么首先就是修改它,将这个事务隔离级别设置成read uncommitted
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
线程A操作
# 关闭事务自动提交
SET autocommit=0
# 修改字段
UPDATE test SET balance= 20 where username = 'tom';
线程B操作
// 关闭事务自动提交
SET autocommit=0
// 查询内容
SELECT * FROM test;
绿色的表示一个新线程,这个线程就能够读取到未提交的内容。
为了解决这个问题,我们可以将事务隔离级别设置成read committed,然后就可以解决这个问题。
- 不可重复读
这个中文也让人第一眼不知道它在说什么,老样子,我们看看它对应的事务隔离级别的英文:repeated read。其实就是读取提交了的内容。但是为什么这也是个问题呢?其实读取提交内容并不是问题,真正的问题在于线程B在一次事务中读取了两次不同的内容,换句话说,A线程提交了修改记录,但是B线程开启事务以后两次读取A修改的内容,发现并不相同,这就是不可重复读,我们的要求其实是在B线程这个事务中应该读取相同的内容,即使这次内容已经过期了。
聊聊危害
其实不可重复读是我觉得最匪夷所思的,为什么我们宁愿要一个过期数据而不要最新数据。我在网上能够找到的合理解释就是,线程B其他数据如果是依据这个查到的数据进行计算的话,为了两次数据的一致性,就必须采用这个事务隔离机制,让结果一样,因为B毕竟还是在一次会话中。
首先修改隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL repeated READ;
线程A操作
# 关闭事务自动提交
SET autocommit=0
# 修改字段
UPDATE test SET balance= 50 where username = 'tom';
# 提交事务
COMMIT;
线程B操作
// 关闭事务自动提交
SET autocommit=0
// 查询内容
SELECT * FROM test;
// 再次查询内容
SELECT * FROM test;
利用这个事务隔离级别就可以解决读取两次内容结果不一致的问题
- 幻读
这个就更加玄学了,名字已经开始有火影的感觉。那这个到底是什么呢?举个例子说明,A线程开启事务并向查看表中内容,发现A中只有两条数据,这是线程B向表中插入了一条新数据,此时,A线程现在对表进行操作,发现数据多了一条,就像产生了幻觉一样,这就是幻读。
为了保持A线程这个事务操作,也就是A只是想操作事务开始时查到的那些数据,而不是又插入的新数据,这就要通过另一个Mysql事务隔离级别来解决:serializable。
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
线程A的操作
// 关闭事务自动提交
SET autocommit=0
// 查询其中内容
SELECT * FROM test;
// 发现只有两条数据
// 修改全部字段
UPDATE test SET balance= 50;
// 提交事务
COMMIT;
线程B的操作
// 关闭事务自动提交
SET autocommit=0
// 在A执行查询操作以后就执行插入操作
INSERT INTO test VALUES('BOB', 600);
Mysql中的事务隔离级别
看了上面的例子,我想应该明白了问题,也能够理解Mysql提出的这四个事务隔离级别的作用,下面我们来总结一下
- read uncommitted
这个是隔离级别最差的,不提交的数据都能够读取,容易产生脏读、当然就更会产生不可重复读以及幻读的问题 - read committed
这个隔离级别比上面好一些,可以解决脏读的问题,但是,还是会出现不可重复读以及幻读的问题 - repeated read(Mysql默认)
这个又比上面好一些,可以解决脏读并且能够解决不可重复读的问题,但是没办法解决幻读问题 - serializable
这个是安全系数最好的,但是性能也打了折扣,解决上面提到的那些问题。