事务四大特征:原子性,一致性,隔离性和持久性(ACID)
Transaction 也就是所谓的事务了,通俗理解就是一件事情。
从小。父母就教育我们。做事情要有始有终,不能半途而废。 事务也是这样,不能做一半就不做了,要么做完,要么就不做。也就是说。事务必须是一个不可切割的总体,就像我们在化学课里学到的原子。原子是构成物质的最小单位。于是。人们就归纳出事务的第一个特性:原子性(Atomicity)。我靠。一点都不神奇嘛。
特别是在数据库领域。事务是一个很重要的概念,除了原子性以外,它另一个极其重要的特性,那就是:一致性(Consistency)。
也就是说,运行完数据库操作后。数据不会被破坏。打个例如。假设从 A 账户转账到 B 账户。不可能由于 A 账户扣了钱,而 B 账户没有加钱吧。假设出现了这类事情,您一定会很气愤,什么 diao 银行啊!
当我们编写了一条 update 语句,提交到数据库的一刹那间,有可能别人也提交了一条 delete 语句到数据库中。
或许我们都是对同一条记录进行操作,能够想象,假设不稍加控制,就会出大麻烦来。我们必须保证数据库操作之间是“隔离”的(线程之间有时也要做到隔离)。彼此之间没有不论什么干扰。这就是:隔离性(Isolation)。要想真正的做到操作之间全然没有不论什么干扰是非常难的,于是乎,每天上班打酱油的数据库专家们。開始动脑筋了,“我们要制定一个规范,让各个数据库厂商都支持我们的规范!”。这个规范就是:事务隔离级别(Transaction Isolation Level)。
能定义出这样牛逼的规范真的挺不easy的,事实上说白了就四个级别:
- READ_UNCOMMITTED
- READ_COMMITTED
- REPEATABLE_READ
- SERIALIZABLE
千万不要去翻译。那仅仅是一个代号而已。
从上往下。级别越来越高,并发性越来越差,安全性越来越高,反之则反。
当我们运行一条 insert 语句后,数据库必需要保证有一条数据永久地存放在磁盘中。这个也算事务的一条特性, 它就是:持久性(Durability)。
归纳一下。以上一共提到了事务的 4 条特性,把它们的英文单词首字母合起来就是:ACID,这个就是传说中的“事务 ACID 特性”!
真的是很牛逼的特性啊!这 4 条特性,是事务管理的基石,一定要透彻理解。此外还要明白。这四个家伙其中,谁才是老大?
事实上想想也就清楚了:原子性是基础,隔离性是手段。持久性是目的,真正的老大就是一致性。数据不一致了。就相当于“江湖乱套了”。所以说,这三个小弟都是跟着“一致性”这个老大混,为他全心全意服务。
这四个家伙其中,事实上最难理解的反倒不是一致性,而是隔离性。由于它是保证一致性的重要手段,是工具,使用它不能有半点差池,否则后果自负。怪不得数据库行业专家们都要来研究所谓的事务隔离级别了。事实上,定义这四个级别就是为了解决数据在高并发下所产生的问题,那又有哪些问题呢?
三类数据读问题
- Dirty Read(脏读)
- Unrepeatable Read(不可反复读)
- Phantom Read(幻读)
两类数据更新问题
- 第一类丢失更新
- 第二类丢失更新
首先看看“脏读”,看到“脏”这个字。我就想到了恶心、肮脏。数据怎么可能脏呢?事实上也就是我们常常说的“垃圾数据”了。
比方说,有两个事务,它们在并发运行(也就是竞争)。看看下面这个表格,您一定会明确我在说什么:
剩余金额应该为 1100 元才对!请看 T6 时间点,事务 A 此时查询剩余金额为 900 元,这个数据就是脏数据,它是事务 A 造成的,明显事务没有进行隔离,渗过来了,乱套了。
所以脏读这件事情是很要不得的,一定要解决掉!让事务之间隔离起来才是硬道理。
不可反复读又怎么解释呢?还是用类似的样例来说明:
事务 A 事实上除了查询了两次以外,其它什么事情都没有做。结果钱就从 1000 变成 0 了,这就是反复读了。
可想而知,这是别人干的,不是我干的。事实上这样也是合理的,毕竟事务 B 提交了事务。数据库将结果进行了持久化。所以事务 A 再次读取自然就发生了变化。
这样的现象基本上是能够理解的,但在有些变态的场景下却是不同意的。
毕竟这样的现象也是事务之间没有隔离所造成的,但我们对于这样的问题,似乎能够忽略。
幻读。我去!Phantom 这个单词不就是“幽灵、鬼魂”吗?刚看到这个单词时。真的把我的小伙伴们都给惊呆了。怪不得这里要翻译成“幻读”了。总不能翻译成“幽灵读”、“鬼魂读”吧。事实上意义就是鬼在读,不是人在读。或者说搞不清楚为什么。它就变了,非常晕,真的非常晕。还是用一个演示样例来说话吧:
银行工作人员,每次统计总存款,都看到不一样的结果。
只是这也确实也挺正常的。总存款增多了。肯定是这个时候有人在存钱。
可是假设银行系统真的这样设计。那算是玩完了。这相同也是事务没有隔离所造成的,但对于大多数应用系统而言,这似乎也是正常的,能够理解,也是同意的。银行里那些恶心的那些系统,要求很严密,统计的时候,甚至会将全部的其它操作给隔离开,这样的隔离级别就算很高了(预计要到 SERIALIZABLE 级别了)。
第一类丢失更新,A事务撤销时。把已经提交的B事务的更新数据覆盖了。这样的错误可能造成非常严重的问题,通过以下的账户取款转账就能够看出来:
可是,在当前的四种随意隔离级别中,都不会发生该情况,不然绝对乱套,我都没提交事务仅仅是撤销,就把别人的给覆盖了,这也太恐怖了。
第二类丢失更新。B事务覆盖A事务已经提交的数据,造成A事务所做操作丢失
归纳一下。以上提到了事务并发所引起的跟读取数据有关的问题。各用一句话来描写叙述一下:
- 脏读:事务 A 读取了事务 B 未提交的数据,并在这个基础上又做了其它操作。
- 不可反复读:事务 A 读取了事务 B 已提交的更改数据。
- 幻读:事务 A 读取了事务 B 已提交的新增数据。
第一条是坚决抵制的。后两条在大多数情况下可不作考虑。
这就是为什么必需要有事务隔离级别这个东西了,它就像一面墙一样。隔离不同的事务。看以下这个表格。您就清楚了不同的事务隔离级别能处理如何的事务并发问题:
依据您的实际需求。再參考这张表。最后确定事务隔离级别,应该不再是一件难事了。
JDBC 也提供了这四类事务隔离级别,但默认事务隔离级别对不同数据库产品而言,却是不一样的。我们熟知的 MySQL 数据库的默认事务隔离级别就是 READ_COMMITTED,Oracle、SQL Server、DB2等都有有自己的默认值。我觉得 READ_COMMITTED 已经能够解决绝大多数问题了,其它的就详细情况详细分析吧。
提示:在 java.sql.Connection 类中可查看全部的隔离级别。
我们知道 JDBC 仅仅是连接 Java 程序与数据库的桥梁而已,那么数据库又是如何隔离事务的呢?事实上它就是“锁”这个东西。当插入数据时,就锁定表,这叫“锁表”;当更新数据时。就锁定行,这叫“锁行”。
当然这个已经超出了我们今天讨论的范围,所以还是留点空间给我们的 DBA 同学吧,免得他没啥好写的了。
除了 JDBC 给我们提供的事务隔离级别这样的解决方式以外,还有哪些解决方式能够完好事务管理功能呢?
最好还是看看 Spring 的解决方式吧。事实上它是对 JDBC 的一个补充或扩展。
它提供了一个很重要的功能。就是:事务传播行为(Transaction Propagation Behavior)。