本文共 3326 字,大约阅读时间需要 11 分钟。
1.什么是事务
首先说一下什么是事务。
事务(Transaction)指一个操作,由多个步骤组成,要么全部成功,要么全部失败。
比如我们常用的转账功能,假设A账户向B账号转账,那么涉及两个操作:
(1)从A账户扣钱; (2)往B账户加入等量的钱。因为是独立的两个操作,所以可能有一个成功,一个失败的情况。但是因为在这种场景下,必须要保证事务,即要么同时成功,要么同时失败(一个失败需要回滚),不能存在从A账户扣钱成功,往B账户加入等量钱失败这种情况。
2.生活中处处可见事务
事务不止存在于数据库中,生活中处处存在事务,只要是涉及多个步骤来完成一件事情时,就涉及到事务。
比如彩礼三金和结婚是一个事务,南方给了价值几十万的彩礼和三金,女方会答应如期将女儿嫁出。如果女方毁约,一般会如数退还彩礼三金。如果遇到蛮横无理的女方,那么就破坏了事务,男方会采取法律或特殊手段要回彩礼三金,强制达到事务。
再如菜市场买东西,一手交钱一手交货;购买机票到最后完成乘机或退还机票(2021年春节因疫情尚未结束倡导就地过年就出现大面积免手续费退还机票的事情);网购下单到满意确收货或不满意退款退货等等。
3.数据库事务
因为数据库操作在日常开发中较为常见,所以在计算机术语中,我们说的事务一般指数据库事务。
数据库事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特性。
原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。 持久性(Durability):一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。我们还是用上面“A账户向B账号汇钱”的例子来说明如何通过数据库事务保证数据的正确性。
熟悉关系型数据库事务的都知道从帐号A到帐号B需要6个操作:
(1)从A账号中把余额读出来(500)。(2)对A账号做减法操作(500-100)。(3)把结果写回A账号中(400)。(4)从B账号中把余额读出来(500)。(5)对B账号做加法操作(500+100)。(6)把结果写回B账号中(600)。
原子性:
保证1-6所有过程要么都执行,要么都不执行。一旦在执行某一步骤的过程中发生问题,就需要执行回滚操作。 假如执行到第五步的时候,B账户突然不可用(比如被注销),那么之前的所有操作都应该回滚到执行事务之前的状态。一致性:
在转账之前,A和B的账户中共有500+500=1000元钱。在转账之后,A和B的账户中共有400+600=1000元。也就是说,数据的状态在执行该事务操作之后从一个状态改变到了另外一个状态,两个状态数据总额时一致的,不能凭空变多或变少。隔离性:
在 A 向 B 转账的整个过程中,只要事务还没有提交(commit),查询 A 账户和 B 账户的时候,两个账户里面的钱的数量都不会有变化。如果在 A 给 B 转账的同时,有另外一个事务执行了 C 给 B 转账的操作,那么当两个事务都结束的时候,B 账户里面的钱应该是 A 转给 B 的钱加上 C 转给 B 的钱再加上自己原有的钱。持久性:
一旦转账成功(事务提交),两个账户的里面的钱就会真的发生变化(会把数据写入数据库做持久化保存)。事务是个好多西,因为它符合我们的预期。但是很多场景下,很难保证事务,或者说保证事务需要付出很大的成本。此时就需要我们来权衡利弊,整出一个低成本又符合实际应用场景的设计方案。
4.数据库事务的使用
对于单条SQL语句,数据库系统自动将其作为一个事务执行,这种事务被称为隐式事务。
要手动把多条SQL语句作为一个事务执行,使用BEGIN开启一个事务,使用COMMIT提交一个事务,这种事务被称为显式事务,例如,把上述的转账操作作为一个显式事务:
BEGIN;UPDATE accounts SET balance = balance - 100 WHERE id = 1;UPDATE accounts SET balance = balance + 100 WHERE id = 2;COMMIT;
很显然多条SQL语句要想作为一个事务执行,就必须使用显式事务。
COMMIT是指提交事务,即试图把事务内的所有SQL所做的修改永久保存。如果COMMIT语句执行失败了,整个事务也会失败。
有些时候,我们希望主动让事务失败,这时,可以用ROLLBACK回滚事务,整个事务会失败:
BEGIN;UPDATE accounts SET balance = balance - 100 WHERE id = 1;UPDATE accounts SET balance = balance + 100 WHERE id = 2;ROLLBACK;
数据库事务是由数据库系统保证的,我们只需要根据业务逻辑使用它就可以。
5.事务隔离级别
对于两个并发执行的事务,如果涉及到操作同一条记录的时候,可能会发生问题。因为并发操作会带来数据的不一致性,包括脏读、不可重复读、幻读等。数据库系统提供了隔离级别来让我们有针对性地选择事务的隔离级别,避免数据不一致的问题。
SQL标准定义了4种隔离级别,分别对应可能出现的数据不一致的情况:
Isolation Level | 脏读(Dirty Read) | 不可重复读(Non Repeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
Read Uncommitted(读取未提交内容) | Yes | Yes | Yes |
Read Committed(读取提交内容) | - | Yes | Yes |
Repeatable Read(可重读) | - | - | Yes |
Serializable(可串行化) | - | - | - |
第 1 级别:Read Uncommitted:
Read Uncommitted 是隔离级别最低的一种事务级别。在这种隔离级别下,一个事务会读到另一个事务更新后但未提交的数据,如果另一个事务回滚,那么当前事务读到的数据就是脏数据,这就是脏读(Dirty Read)。
第 2 级别:Read Committed:
Read Committed 满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。它是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。
在 Read Committed 隔离级别下,一个事务可能会遇到不可重复读(Non Repeatable Read)的问题。
不可重复读是指,在一个事务内,多次读同一数据,结果不一致。比如第二次读数据时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
第 3 级别:Repeatable Read:
Repeatable Read 确保同一事务的多个实例在并发读取数据时,会看到同样的数据。这是 MySQL 的默认事务隔离级别。
在 Repeatable Read 隔离级别下,一个事务可能会遇到幻读(Phantom Read)的问题。
幻读是指,在一个事务中,第一次读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当再读取该范围的数据行时,会发现有新的“幻影” 行。
第 4 级别:Serializable :
Serializable 是最严格的隔离级别。在 Serializable 隔离级别下,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。做法是在每个读的数据行上加上共享锁(行锁的一种)。
虽然 Serializable 隔离级别下的事务具有最高的安全性。但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用 Serializable 隔离级别。
参考文献
[1]
[2] [3] [4]转载地址:https://dablelv.blog.csdn.net/article/details/114085663 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!