漫谈数据库事务(一)事务、事务隔离级别

该节内容为漫谈,内容相对比较多。

首先说明一下:事务不只是关系型数据库独有,非关系型数据库也有事务。只要在高并发或在短时间内操作做多条(批量,状态改变)数据的情况下,大都,注意是大都,而不是全部。

说事务,但解决问题的最好的办法是避免使用事务。在日常开发中要是迫不得己那就用下事务吧。目前大型国企在事务处理上大都选择orical ,一般私营企业还是选择MySQL,使用MySQL来处理事务,在MySQL中用的最多的存储引擎有:innodb,bdb,myisam ,memory 等。其中innodb和bdb支持事务而myisam等不支持事务。这也是 innodb和myisam的重要区别之一。

一、什么是事务

在数据库中事务是工作的逻辑单元,一个事务是由一个或多个完成一组的相关行为的SQL语句组成,通过事务机制确保这一组SQL语句所作的操作要么都成功执行,完成整个工作单元操作,要么一个也不执行。

如:网上转帐就是典型的要用事务来处理,用以保证数据的一致性。

事务就像是一个整体,执行的目的是要完整。要么都成功执行,要么都不执行。

二、事务有什么特性

特性就是更好的来描述事务。事务具体四大特性,也就是经常说的ACID 。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)的缩写。重要的知识点要默念三遍。

1、原子性(atomicity)

即不可分割,事务要么全部被执行,要么全部不执行。如果事务的所有子事务全部提交成功,则所有的数据库操作被提交,数据库状态发生变化;如果有子事务失败,则其他子事务的数据库操作被回滚,即数据库回到事务执行前的状态,不会发生状态转换。

2、一致性(consistency)

事务的执行使得数据从一种正确状态转换成另外一种正确状态。

3、隔离性(isolation)

在事务正确提交之前,不允许把事务对该数据的改变提供给任何其他事务,即在事务正确提交之前,它可能的结果不应该显示给其他事务。

在进行事务查看数据时数据所处的状态,要么是被另一并发事务修改之前的状态,要么是被另一并发事务修改之后的状态,即当前事务不会查看由另外一个并发事务正在修改的数据。这种特性主要由锁机制实现。

4、持久性(durability)

事务正确提交之后,其结果将永远保存在数据库之中,即使在事务提交之后有了其他故障,事务的处理结果也会得到保存。

三、有了事务的机制,那么在并发情况下会出现什么问题?

举个例子,事务A和事务B操纵的是同一个资源,事务A有若干个子事务,事务B也有若干个子事务,事务A和事务B在高并发的情况下,会出现各种各样的问题。“各种各样的问题”,常出现的有三种:脏读、不可重复读、幻读。

1、脏读

所谓脏读,就是指事务A读到了事务B还没有提交的数据,比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务–>取走100元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了100块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。

2、不可重复读

所谓不可重复读,就是指在一个事务里面读取了两次某个数据,读出来的数据不一致。(在同一事务中重复读取数据,读取的结果不一致,导致数据不可重复读)。读取的还是以银行取钱为例,事务A开启事务–>查出银行卡余额为1000元,此时切换到事务B,事务B开启事务–>事务B取走100元–>提交,数据库里面余额变为900元,此时切换回事务A,事务A再查一次查出账户余额为900元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。

3、幻读

所谓幻读,就是指在一个事务里面的操作中发现了未被操作的数据。比如学生信息,事务A开启事务–>修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务–>事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。

四、那在事务中怎么出现或避免这些情况的呢?

这就需要了解事务隔离级别。

SQL标准定义了4种隔离级别,包括了一些具体规则,用来限定事务内外哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。

以本地数据库为例:

数据库

1、第1级别:Read Uncommitted(读未提交)

通过名字可以看出,就是一个事务读取了另一个事务未提交的内容。该级别引发的问题是——脏读(Dirty Read):读取到了未提交的数据。

举例:

设置事务隔离为:READ-UNCOMMITTED

1
set tx_isolation='READ-UNCOMMITTED';

事务A:启动事务

1
2
start transaction;  
select * from my_user;

启动事务A

事务B:也启动一个事务(那么两个事务交叉了)。在事务B中执行更新语句,且不提交。

1
2
3
start transaction;  
update my_user set sex='0' where id=1;
select * from my_user;

启动事务B

事务A:回到事务A查看数据情况,两次操作前后对比:

1
select * from my_user;

查看事务A

数据改变了,说明我们读到了事务B还没有提交的数据。

事务B:事务B回滚,仍然未提交

1
2
rollback;  
select * from my_user;

事务B回滚

事务A:回到事务A,在事务A里面看到的也是B没有提交的数据。

查看事务A

脏读意味着我在这个事务中(A中),事务B虽然没有提交,但它任何一条数据变化,我都可以看到!

2、第2级别:Read Committed(读已提交)

还是从名字上看出,这个级别是指:一个事务只能读取其他事物已经提交的数据。这个级别隔离这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)
。而这种隔离级别出现的问题是——不可重复读(Nonrepeatable Read):不可重复读意味着我们在同一个事务中执行完全相同的select语句时可能看到不一样的结果。导致这种情况的原因可能有:(1)有一个交叉的事务有新的commit,导致了数据的改变;(2)一个数据库被多个实例操作时,同一事务的其他实例在该实例处理其间可能会有新的commit。总之就是有其他事务comment了,导致当前事务读取前后不一致。

修改隔离级别

1
set tx_isolation='read-committed'; 

修改隔离级别

事务A:启动一个事务

1
2
start transaction;   
select * from my_user;

启动事务A

事务B:也启动一个事务(那么两个事务交叉了)。在这事务中更新数据,且未提交。

1
2
3
4
start transaction;  
select * from my_user;
update my_user set sex='0' where id=1;
select * from my_user;

启动事务B

事务A:回到事务A,这个时候我们在事务A中能看到数据的变化吗?

查看事务A

这说明,事务A读取不到事务B未提交的数据,避免了上种情况的脏读。但是,把事务B commit 之后:

事务B commit

再来看事务A:

查看事务A

3、第3级别:Repeatable Read(可重读)

顾名思义,在同一个事务中,读取的数据是不会发生变化的,不受其他事务的影响。这是MySQL的默认事务隔离级别。此级别可能出现的问题——幻读(Phantom Read):当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。

InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题,下节再讲关于MVCC 的内容。

更改隔离级别

1
set tx_isolation='repeatable-read';

更改隔离级别

事务A:启动一个事务

1
2
start transaction;  
select * from my_user;

启动事务A

事务B:开启一个新事务(那么这两个事务交叉了),在事务B中增加一条数据,并提交 。

1
2
3
4
5
start transaction;  
insert into my_user (`name`,`sex`) values ('杨十三','1');
select * from my_user;
commit;

启动事务B

事务A:这时候即使事务B已经提交了,但A能不能看到数据变化?

1
select * from my_user;

查看事务A

事务A:只有当事务A也提交了,它才能够看到数据变化

查看事务A

4、第4级别:Serializable(可串行化)

可串行化是指,在同一个事务中要完全从头到尾一连串的执行完,不能在此过程中有其他事务改变数据。此级别的隔离是最高的隔离级别。它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。但在这个级别,可能导致大量的超时现象和锁竞争。随后章节说一下锁的内容。

修改隔离级别

1
set tx_isolation='serializable';  

修改隔离级别

事务A:开启一个新事务

1
start transaction;

开启事务A

1
select * from my_user;

查看事务A

在事务A中遇到了问题,只能插入数据,而不能查看数据。当事务A提交之后可select查看。

查看事务A

虽然出错了,但???奇怪的是,事务B怎么插入数据成功了?和预期的不一样?哪里出了问题呢?

原来 在 Serializable(可串行化) 级别下,表加的锁不是表锁而是行锁,在select 中加上where 条件就可查询出来。

而update 语句也执行错误。

漫谈数据库事务(一)事务、事务隔离级别

https://sunct.github.io/posts/b22c4717.html

作者

sunct

发布于

2019-04-16

更新于

2020-12-24

许可协议


评论