事务(1)
如果觉得写的还可以,点个赞支持一下笔者呗!你的点赞和关注会让我更快更新哦。笔者会持续更新关于Java和大数据有关的文章。目前集中精力在更新java框架的内容。
1. 概念
事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
很显然,事务的概念根本就不是给人看的(是给大神看的)。我们可以简单粗暴的理解为:对数据的一次操作就是一个事务。
1.1 事务的特性
事务具备 4 个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),简称:ACID。
- 原子性:事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行(有始有终);
- 一致性:事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束(表里如一);
- 隔离性:多个事务并发执行时,一个事务的执行不应影响其他事务的执行(井水不犯河水);
- 持久性:已被提交的事务对数据库的修改应该永久保存在数据库中(一诺千金)。
有始有终、表里如一、不多管闲事、一诺千金。真是集众多优秀品质于一身的奇男子。
事务竟然有这么多的优良品质,这不就是国民男朋友的标准吗?各位女生遇到这样的男生可一定不要放过呀!咦?怎么说的好像这篇专栏会有女生看似的呢?
2. 脏读、幻读、不可重复读
弄清楚概念对于我们学习一个东西有着至关重要的作用。通常我们学不会一个东西,是因为没有把它的概念搞清楚。概念很重要,但是有时候概念很抽象,不太容易理解。这个时候就需要类比,拿我们熟悉的事物做类比,通过我们已有的知识来学习新的知识。
2.1 脏读
脏读就是一个事务(A)读到了另一个事务(B)未提交的数据。
普通解释:
如果一个事务(A)读到另一个事务(B)并未提交的数据,恰好事务(B)由于某些原因导致了事务回滚,那么刚刚事务(A)就相当于读到了实际并不存在的数据。很显然,这种情况是存在问题的。
接地气解释:
某天傍晚,你跟女朋友手牵着手在路上慢慢悠悠的溜达着,你侬我侬的甜蜜着。这时你突然有一个想法:吃完晚饭带她去看刚刚上映的电影。但转念一想,不行,今晚有你超级喜欢的球队比赛直播,于是你打消了看电影的想法。但是此时,女朋友愤然甩开你的手,愤愤的扬长而去,留下你一个人在风中凌乱(因为她读到了你的想法)。
试想一下,如果你的女朋友能够读到你的想法,那将是一件多么可怕的事情,分分钟引来杀身之祸。
2.2 不可重复读
不可重复读是指在一个事务内对同一条记录(可以理解为根据同一个 Id 查询)进行多次查询的结果却不一致。
普通解释:
比如在一个事务(A)中,查询了一次账户余额。这时另一个事务(B)在该账户中扣除一笔钱(比如自动还款)并提交了事务,这时事务(A)再次查询账户余额,发现余额变了,这就不可重复读了。很显然,这种情况同样是存在问题。
接地气解释:
你跟好基友在商量假期怎么安排,你说想带女朋友一起出去玩。女朋友在旁边无意中听到了,她听到后很开心。但她不放心,想再确认一下,然后又凑过来偷听,这时候,你已经被好基友说服,要一起参加一个球迷聚会。你女朋友发现跟之前说的不一样了,于是你面前出现了一块搓衣板。
2.3 幻读
幻读指的是在同一个事务内进行多次操作之间,产生了新的数据,并对后续的操作造成了影响。
单纯的文字描述幻读不太好描述清楚,下面我们直接用代码来演示一下:
事务 A
mysql> begin; Query OK, 0 rows affected (0.00 sec) # 查询是否存在 Id 为 2 的数据 mysql> select * from user where id = 2; Empty set (0.00 sec) # 发现并不存在,准备插入数据。此时,另一个事务进来“捣乱”了 # 等真正执行插入的时候,发现无法插入数据 mysql> INSERT INTO `user` VALUES (2, 'xiaoqiang', 18); ERROR 1062 (23000): Duplicate entry '2' for key 'PRIMARY' # 再次查询,仍然无法查到对应数据,真是见鬼了 mysql> select * from user where id = 2; Empty set (0.00 sec)
事务 B
# 在事务 A 执行插入 Id 为 2 的数据之前,抢先插入一条 Id 为 2 的数据 mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> INSERT INTO `user` VALUES (2, 'xiaoqiang', 18); Query OK, 1 row affected (0.01 sec) mysql> commit; Query OK, 0 rows affected (0.01 sec)
看完这个例子,相信这次不用把女朋友大人请出来,你就能够清楚的理解什么是幻读了吧。
2.4 区别
脏读 指的是一个事务读到了其他事物未提交的数据
不可重复读 指的是一个事务中多次读到同一条(批)数据发生了变化,重点在于表里已经存在的数据被其他事务修改了(update/delete)。
幻读 指的是一个事务被其他事务新增的数据所影响,重点在于影响事务的数据开始是不存在的,是在事务开始后,被其他事务新插入的(insert)。
脏读、不可重复读、幻读是在并发事务的情况下才会发生的。为了解决这些问题,数据库引入了隔离级别,不同的隔离级别可以解决不同的问题。
3 隔离级别
下面是不同隔离级别与发生读问题的关系对照表:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(Read uncommitted) | 可能 | 可能 | 可能 |
读已提交(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable) | 不可能 | 不可能 | 不可能 |
读未提交 (Read Uncommitted): 所有读问题都可能发生,一般不会使用这种隔离级别
读已提交 (Read Committed): 只能避免脏读的情况发生,Oracle 的默认隔离级别
可重复读 (Repeated Read): 能够避免脏读和不可重复读,MySQL 中 InnoDB 引擎默认的隔离级别
串行 (Serializable): 可以解决所有读问题,但由于是串行执行,性能相当一般,所有通常也不会被使用
3.1 悲观锁和乐观锁
悲观锁
锁如其名,天生悲观,对于一切都持悲观态度,极度缺乏安全感,甚至到了被迫害妄想症的地步,觉得总有刁民想害朕。所有对于一切数据库操作(增删改查)都会加锁,导致所有事务只能串行执行,虽然保证了安全,但也导致了效率极其低下。
乐观锁
与悲观锁性格迥异,天生的乐天派,认为人之初性本善。对世界保持这友好开发的态度。但它绝非是一个无脑的傻白甜,它拥有过人的智慧与洞穿一切的观察能力。一旦发现有人图谋不轨,它也绝不手软,是一个既有菩萨心肠,又有霹雳手段的高手。
由于它乐观的处事态度,它更加的信任其他人,所以在与人合作的时候少了不必要的猜疑与防范,把更多的精力用在如何把事情做好上,所以效率非常的高。再加上它超人的智慧与洞察力,也能很好的保证大家做的都是好事。
乐观锁超人的智慧与洞察力其实就是数据的版本记录机制。就是为数据增加一个版本标识,读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交数据的版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
4. 传播行为
上面讨论的内容都是事务独立作战的场景(虽然有并发情况,但是事务之间并没有什么交集)。而事务的传播行为则是在事务之间出现包含或者叫嵌套时才会用到的。Spring 中有七种事务的传播行为,具体见下表:
事务传播行为类型 | 说明 |
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。 |
加粗项为比较常用的选项,其他不太常用。下一节会详细展开。
5 总结
OK,在这一节中,我们学习了事务的概念,了解了事务的四个特性 —— 原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。还学习了脏读、不可重复读以及幻读,并且对比了它们的区别与侧重点。然后引出了事务的隔离级别,还有悲观锁与乐观锁。最后简单介绍了一下 Spring 中事务的传播行为。可谓是收获满满的一节,而在下一节中我们会从代码层面去进一步深入的学习事务在日常开发中的应用,我在下一节等你!