一直很想对数据库事务做一点总结,今天终于静下心来小小的总结一下。
一、数据库事务的特征
1、原子性
原子,顾名思义,自然界中的最小粒子(尽管物理上原子还可分割成夸克的什么玩意,这个我们就不管了)。定义一组操作集合,如果只执行一个子集,这就破坏的事务的目标,事务必须不可分割,要么全部执行,要么全部不执行。
2、一致性
在事务完成时,所有数据必须保持一致的状态。例如:张三给李四转账100,元,张三账号扣除了100,而李四账号却没有变化,这就违背了一致性。
3、隔离性
多用户操作同一条数据时,必须时相互隔离,以免A用户操作影响了B用户.。
4、持久性
一个事务成功了,那么就必定持久化到磁盘了,就算断电了,下次重启,数据依然正确完好。
二、几个有趣的现象
很多时候,我们习惯这样描述我们的系统,高并发、分布式、集群等等,对于一个多用户、多线程的应用,不可避免的共同访问相同的资源,如果没有一套规则去约束这些操作,势必要出乱子,这就是我们要重点讨论的事务隔离。
我们先来看看几个有趣现象:
1、脏读
期末考过了半个月,小明正在急切的查分,一遍一遍的刷新着浏览器,数学分数怎么还没出来??碰巧数学老师在录入分数,刚好录到小明90分,提交请求,恰好小明刷新,90分,欣喜若狂。而此时数学老师发现录入错误,撤销了事务,小明再次查询,分数消失,小明此时是各种猜测。
这个过程中,有两个事务在操作同一条数据:如下表,T代表时间
事务1
| 事务2
|
T1开启事务
|
|
| T2开启事务
|
| T3数学老师开始录入分数90
|
T4小明查到分数90
|
|
| T5老师发现录入错误回滚数据
|
T6小明忧伤的查不到分数了
|
|
T7提交事务
|
|
很明显,事务1读到了事务2未提交的数据。
2、不可重复读
最终小明查到了分数为59,伤心的要准备补考了,准备把成绩打印出来,发现打印出来的成绩是69,原来老师发现有道题目解题思路新颖,要额外给小明加10分。
事务1 | 事务2 |
T1事务开启 |
|
T2小明查到分数59 |
|
| T3事务开启 |
| T4修改分数为69 |
| T5提交事务 |
T6小明打印成绩69 |
|
T7提交事务 |
|
3、幻读
小明一直在焦急的等待最后一门英语成绩,发现始终没有出来,便想把已经出来的成绩先打印,最后发现打印出来的有英语成绩。
事务1 | 事务2 |
T1事务开启 |
|
T2小明查分,不包括英语 |
|
| T3开启事务 |
| T4英语老师录入分数 |
| T5提交事务 |
T6小明打印到了英语成绩 |
|
T7提交事务 |
|
注:有人要问了,不可重复读和幻读,一个德行,有什么区别,不可重复读是读到了修改数据,幻读是读到了新增的数据,有又有什么不同呢,数据库的锁不一样,下次分享。
三、事务的隔离级别
以上的现象,不同的事务隔离级别,会产生对于的现象。
事务隔离级别:
1、读未提交(READ-UNCOMMITTED);
2、读已提交(READ-COMMITTED);
3、可重复读(REPEATABLE-READ);
4、序列化(SERIABLIABLE)
隔离级别 | 脏读 | 不可重复读 | 幻读 |
READ-UNCOMMITTED | Y | Y | Y |
READ-COMMITTED | N | Y | Y |
REPEATABLE-READ | N | N | Y |
SERIALIZABLE | N | N | N |
四、MySql的隔离级别和对应现象
Mysql默认的隔离级别是REPEATABLE-READ。
1、脏读
(1)、设定数据库隔离级别为READ-UNCOMMITTED;
(2)、A事务开启,查询,没查到数据;
(3)、B事务开启,修改分数,但不提交;
(4)、A事务查询,小明查到了分数90
(5)、B事务回滚
(6)、A事务再次查询,已经查不到;
(7)、A事务提交;
下面我们看把隔离级别设置成READ-COMMITTED能否解决这个问题。
(1)、设定隔离级别为READ-COMMITTED;;
(2)、A事务查询
(3)、B事务修改,但不提交;
(4)、A事务再次查询,却查询不到;
(5)、B事务提交;
(6)、A事务再次查询,查询到了分数;
2、不可重复读
紧接着上面的例子,上面的A事务还没结束。
(1)、B事务再次修改分数提交;
(2)、A事务再次查询;
那么这个时候有同学要问了,如果B事务没有提交,有C事务来修改小明的分数会怎么样??下面我们就来看看:
很明显,C事务无法修改这条记录,这条记录已经被加锁。我们略过这个小插曲。
两次读取的数据不一样,这就是不可重复读。读到的是其他事务已提交的数据,这也是无可厚非,当然有些系统特别变态要求可重复读。
下面我们看看REPEATABLE-READ,能否解决我们的问题:
(1)、设定事务隔离级别为REPEATABLE-READ;
(2)、A事务开启,读数据;
(3)、B事务开启,修改数据,并提交;
(4)、A事务再次查询,发现结果没变;
很明显,在REPEATABLE-READ级别下,A事务读取数据是完全不受其他事务影响的,这就解决了不可重复读的问题。
3、幻读
紧接着上面的例子,事务A再次查询,碰巧再次之前B事务新增了一条记录,英语成绩被录入进来。
(1)、B事务新增一条记录,并提交;
(2)、A事务再次查询;
这里有同学就要奇怪了,为什么没有查到英语成绩,理论上要出现幻读了,这是Why??
Mysql的可重复读的实现和其他数据库是有区别的,不会造成幻读,那么还是要说说幻读,幻读,就是同一个事务内,多次查询,读取到其他session 事务insert并已经提交的数据。
那么我们来看看最后一个隔离级别SERIALIZABLE。
(1)、设置隔离级别为SERIALIZABLE
(2)、B事务开启,新增记录,并不提交;
(3)、A事务开启事务查询;
B事务没提交,A事务就阻塞,就连select也会阻塞,这就是串行化得名的原因,只能顺序执行,所有安全级别最高,性能最低。
至于JDBC与隔离级别,请听下回分解。