什么是事务

事务的定义

数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。

假如A转账给B 100元,先从A的账户里扣除100元,再在B的账户上加上100元。如果扣完A的100元后,还没来得及给B加上,银行系统异常了,最后导致A的余额减少了,B的余额却没有增加。所以就需要事务,将A的钱回滚回去,就是这么简单。

事务的四大特性

  1. 原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部完成,要么全部不执行。
  2. 一致性(Consistency):几个并行执行的事务,其执行结果必须与按某一顺序 串行执行的结果相一致。
  3. 隔离性(Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。
  4. 持久性(Durability):对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障。

事务并发存在的问题

假设现在有张表

CREATE TABLE `account` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `balance` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_name_idx` (`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

数据如下

idnamebalance
1张三100
2李四200

更新丢失(lost update)

A和B事务并发执行,A事务执行更新后,提交;B事务在A事务更新后,B事务结束前也做了对该行数据的更新操作,然后回滚,则两次更新操作都丢失了。

  1. 第一类丢失更新(回滚丢失,Lost update)

事务A和B并发执行,A事务修改数据并提交事务,然后B事务回滚事务,A事务已经提交的数据被回滚。

时间节点A事务B事务
T1开始事务-
T2-开始事务
T3查询账号余额 100 元-
T4-查询账号余额 100 元
T5存入 100 元,账户余额 200 元-
T6提交事务-
T7-取款10元,账户余额 90 元
T8-回滚事务
T9-账户余额 100 元(A事务更新丢失)
  1. 第二类丢失更新(覆盖丢失/两次更新问题,Second lost update)

事务A和B并发执行,A事务修改数据并提交事务,然后B事务修改数据并提交,A事务已经提交的数据被覆盖。

时间节点A事务B事务
T1开始事务-
T2-开始事务
T3查询账号余额 100 元-
T4-查询账号余额 100 元
T5存入 100 元,账户余额 200 元-
T6提交事务-
T7-取款10元,账户余额 90 元
T8-提交事务
T9-账户余额 90 元(A事务更新被覆盖)

脏读(dirty read)

A和B事务并发执行,A事务读取了B事务修改后未提交的数据,如果之后B事务进行事务回滚,则事务A读到的数据是不存在的脏数据。

时间节点A事务B事务
T1开始事务-
T2-开始事务
T3-查询账号余额 100 元
T4-取款10元,账户余额 90 元
T5查询账号余额 90 元-
T6存入 100 元,账户余额 190 元-
T7-回滚事务
T8-账户余额 100 元
T9提交事务-
T10账户余额 190 元(数据被B事务干扰)-

不可重复读(unrepeatable read)

A和B事务并发执行,A事务读取了B事务修改并已经提交的数据,导致A事务内多次读取同一记录但结果却不相同不稳定。

时间节点A事务B事务
T1开始事务-
T2-开始事务
T3查询账号余额 100 元-
T4-查询账号余额 100 元
T5-取款10元,账户余额 90 元
T6-提交事务
T7查询账号余额 90 元(数据被B事务干扰)-

幻读(phantom read)

A和B事务并发执行,A事务读取了B事务提交的新增数据,这时A事务将出现幻读的问题。幻读一般发生在计算统计数据的事务中。

时间节点A事务B事务
T1开始事务-
T2-开始事务
T3查询ID大于1的账户数量:1-
T4-新增账户(id:3,name:王五,balance:300)
T5-提交事务
T6查询ID大于1的账户数量:2 (莫名其妙多出一条数据)-

可以看出前面几种问题都偏重于对同一记录的修改更新,幻读偏重于多条记录的增删,它们之间是递进的。

事务的四大隔离级别

ISO标准定义了4个事务隔离等级,分别是

  • 读未提交(Read Uncommitted)
  • 读已提交(Read Committed)
  • 可重复读(Repeatable Read)
  • 串行化(Serializable)

MySQL查看和设置隔离级别

查看MySQL当前事务隔离级别

命令:

* show variables like 'tx_isolation'
* show variables like 'transaction_isolation'
* select @@tx_isolation
* select @@transaction_isolation

查看事务隔离级别

设置本次会话的事务隔离级别

该设置只对本次会话的事务生效,不影响其他会话,随着本次会话关闭而失效。

set session transaction isolation level read uncommitted / read committed / repeatable read / serializable

修改session事务隔离级别

设置全局会话的事务隔离级别

该设置不影响当前已连接的会话,新会话使用新的事务隔离级别。

set global transaction isolation level read uncommitted / read committed / repeatable read / serializable

修改全局事务隔离级别

设置一次操作的事务隔离级别

该设置只对下一次事务生效,随着事务的提交而失效。

set transaction isolation level read uncommitted / read committed / repeatable read / serializable

MySQL的事务四大隔离级别

我们通过MySQL看看四大隔离级别的效果

读未提交(READ UNCOMMITTED)

读未提交是隔离级别最低的一种,打开两个MySQL连接,分别设置隔离级别,然后两个事务交替执行,如下:

读未提交

可以看到,事务A可以读取到事务B未提交的数据(事实上事务B也可以读到事务A的),如果一方回滚事务,则会造成脏读等严重问题。

读已提交(Read Committed)

读已提交

可以看到,此隔离级别可以有效解决脏读的问题,但还是无法避免不可重复读的问题

可重复读(Repeatable Read)

可重复读

可以看到,此隔离级别可以有效解决不可重复读的问题,接下来我们看看会不会出现幻读。

可重复读
显然,还是有幻读的问题。

串行化(Serializable)

串行化

可以看到,串行化隔离最严格,可以避免幻读问题,但效率也是最低的。

在选择隔离级别时,需要考虑开发的业务环境和需求,以选择最适合自己业务的隔离级别。

MySQL隔离级别的实现原理

待续...

最后编辑:2020年07月16日 ©著作权归作者所有

发表评论