一、事务的概述
事务是数据库提供的一个非常重要的特性,我们可以把事务理解为组成各个数据操作的执行单元 ,这个单元有一个原则:要么都成功,要么都不成功,不允许出现一部分成功一部分失败的情况。
用一个经典的转账例子来说明:比如小帅给小美转 1000 元钱,这个业务操作必须放在一个事务中执行。第一步,先给小帅扣除掉 1000 元;第二步,再给小美加上 1000 元;这两步操作必须同时成功或者同时失败,事务才算正常结束。如果中间出现异常,就需要把数据恢复到最初的状态。
二、在 MySQL 中操作事务
先准备好测试用的数据库表和数据,以便后续的演示:
1. 准备数据库信息
首先我们创建一张账户表,用于存储用户的姓名和余额信息,SQL 语句如下:
create table t_account(
id int primary key auto_increment,
username varchar(20),
money double
);
表创建完成之后,我们插入几条测试数据,方便后续演示转账业务:
insert into t_account values (null,'美美',10000);
insert into t_account values (null,'冠希',10000);
insert into t_account values (null,'小凤',10000);
insert into t_account values (null,'熊大',10000);
insert into t_account values (null,'熊二',10000);
2. 在 MySQL 数据库中使用事务的两种方式
MySQL 给我们提供了两种操作事务的方式。
第一种方式:使用命令的方式
这种方式是最常用的,通过命令手动开启、提交和回滚事务,我们还是用冠希给美美转账的例子来演示:
start transaction; -- 开启事务
update t_account set money = money - 1000 where username = '冠希';
update t_account set money = money + 1000 where username = '美美';
commit; -- 提交事务,事务已经结束了,数据永久的保存到数据库中了
-- rollback; -- 回滚事务,事务已经结束了,数据回滚到最初始化的状态
第二种方式:设置 MySQL 事务不默认提交的方式
MySQL 数据库的事务是默认提交 的。也就是说,单独执行一条 update 语句,它会默认使用一个事务,执行完自动提交。
如果我们想把多个 SQL 放在一个事务中,就可以设置 MySQL 事务不默认提交,具体操作如下:
set autocommit = off或者0 -- 设置MySQL事务不默认提交
-- 编写SQL语句,执行完之后都没有提交
update t_account set money = money - 1000 where username = '冠希';
update t_account set money = money + 1000 where username = '美美';
-- 需要手动提交或者回滚
commit;
-- rollback;
我们也可以先查看事务是否默认提交,再进行设置,完整演示语句:
show variables like '%commit%'; -- 查看事务是否是默认提交
set autocommit = off; -- 设置不默认提交
update t_account set money = money - 1000 where username = '冠希';
update t_account set money = money + 1000 where username = '美美';
commit; -- 或者rollback;
三、在 JDBC 中操作事务
学会了 MySQL 端的事务操作,在 Java 代码中通过 JDBC 操作事务也是我们开发中常用的技能,JDBC 中操作事务,依靠的是 Connection 接口。
1. Connection 接口操作事务的核心方法
Connection 接口给我们提供了三个操作事务的方法:
- void setAutoCommit (boolean autoCommit):如果传入 false,就设置 MySQL 数据库的事务不默认提交
- void commit ():提交事务
- void rollback ():回滚事务
2. Connection 接口的两个作用
在 JDBC 中,Connection 接口有两个重要的作用:第一,我们操作数据库,都需要使用 Connection 接口;第二,Connection 接口可以用来管理事务。
四、事务的特性
事务有四大核心特性,我们通常称为 ACID 特性,这是数据库本身提供的特性
1. 事务四大特性详细说明
- 原子性(Atomicity):事务中所有操作是不可再分割的原子单位,事务中所有操作要么全部执行成功,要么全部执行失败
- 一致性(Consistency):事务执行后,数据库状态与其它业务规则保持一致,例如转账业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变的
- 隔离性(Isolation):是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰
- 持久性(Durability):指的是一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后数据库马上崩溃,重启后也必须能保证通过某种机制恢复数据
2. 四大特性侧重点总结
- 原子性:强调的是事务的不可分割的特性
- 一致性:强调的是事务执行前后数据需要保证一致
- 隔离性:强调的是多个事务同时操作一条记录,事务之间不能互相干扰
- 持久性:强调的是事务一旦结束了,数据将永久的保存到数据库中
这里需要特别注意,这些特性都是数据库提供的,不是我们代码手动实现的。
五、不考虑事务的隔离性会引发的问题
事务的隔离性是为了解决并发操作的问题,如果我们不考虑事务的隔离性,在多个事务同时运行的时候,就会出现三种读取数据的问题,我们需要清楚每种问题的含义。
- 脏读:一个事务读取到了另一个事务未提交的数据
- 不可重复读:一个事务读取到了另一个事务提交的数据,导致了多次查询的结果不一致,强调的是 update,修改记录的数据
- 幻读(虚读):一个事务读取到了另一个事务提交的数据,导致了多次查询的结果不一致,强调是 insert,向表中添加一条数据
六、设置事务的隔离级别(解决读的问题)
为了解决上面提到的脏读、不可重复读、幻读问题,数据库给我们提供了四种事务隔离级别,不同的隔离级别能解决不同的问题,同时安全性和效率也不一样。
1. 四种事务隔离级别
- Read uncommitted:什么问题都解决不了
- Read committed:避免脏读,但是不可重复读和幻读有可能产生
- Repeatable read:避免脏读和不可重复读,幻读有可能产生
- Serializable:避免各种读问题
2. 隔离级别安全性与效率对比
安全性:Serializable > Repeatable read > Read committed > Read uncommitted
效率:Serializable < Repeatable read < Read committed < Read uncommitted
3. 数据库默认隔离级别
不同的数据库有自己默认的隔离级别,其中 MySQL 数据库的默认隔离级别是Repeatable read,可以避免脏读和不可重复读。
七、事务隔离级别相关演示
为了让大家更直观理解不同隔离级别解决的问题,我们可以通过两个 MySQL 窗口进行测试演示。
1. 演示脏读
脏读就是一个事务读取到了另一个事务未提交的数据,测试步骤:
- 开启两个窗口,A 窗口和 B 窗口
- 在 A 窗口查询隔离级别:select @@tx_isolation;
- 设置 A 窗口隔离级别为最低:set session transaction isolation level read uncommitted;
- 两个窗口都开启事务:start transaction;
- B 窗口执行转账操作,不提交事务
- A 窗口查询数据,能读到 B 窗口未提交的数据
- B 窗口执行 rollback 回滚事务,A 窗口读到的数据就是无效的脏数据
2. 避免脏读和演示不可重复读
想要避免脏读,我们需要提高隔离级别:
- 设置隔离级别为 Read committed:set session transaction isolation level read committed;
- 两个窗口开启事务
- B 窗口执行转账,不提交事务,A 窗口查询不到未提交数据,避免了脏读
- B 窗口提交事务后,A 窗口再次查询,结果不一致,出现不可重复读
3. 避免不可重复读
想要解决不可重复读,设置隔离级别为 Repeatable read:set session transaction isolation level repeatable read;按照同样的步骤测试,就能避免不可重复读问题。
4. 避免各种读问题
如果想要完全避免脏读、不可重复读、幻读所有问题,设置最高隔离级别 Serializable:set session transaction isolation level serializable;这种级别会串行执行事务,所有读问题都能解决,但是效率最低。
总结
事务是数据库操作的核心,尤其是在金融、订单、支付等业务中,事务的使用直接决定了数据是否安全可靠。下一篇文章,我们会接着学习数据库连接池的相关知识,包括连接池概念、常用连接池以及 Druid 连接池的使用和工具类封装。