MySQL 事务从入门到精通(上):概念、操作、特性、隔离级别全解析

一、事务的概述

事务是数据库提供的一个非常重要的特性,我们可以把事务理解为组成各个数据操作的执行单元 ,这个单元有一个原则:要么都成功,要么都不成功,不允许出现一部分成功一部分失败的情况。

用一个经典的转账例子来说明:比如小帅给小美转 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. 四大特性侧重点总结

  • 原子性:强调的是事务的不可分割的特性
  • 一致性:强调的是事务执行前后数据需要保证一致
  • 隔离性:强调的是多个事务同时操作一条记录,事务之间不能互相干扰
  • 持久性:强调的是事务一旦结束了,数据将永久的保存到数据库中

这里需要特别注意,这些特性都是数据库提供的,不是我们代码手动实现的。

五、不考虑事务的隔离性会引发的问题

事务的隔离性是为了解决并发操作的问题,如果我们不考虑事务的隔离性,在多个事务同时运行的时候,就会出现三种读取数据的问题,我们需要清楚每种问题的含义。

  1. 脏读:一个事务读取到了另一个事务未提交的数据
  2. 不可重复读:一个事务读取到了另一个事务提交的数据,导致了多次查询的结果不一致,强调的是 update,修改记录的数据
  3. 幻读(虚读):一个事务读取到了另一个事务提交的数据,导致了多次查询的结果不一致,强调是 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. 演示脏读

脏读就是一个事务读取到了另一个事务未提交的数据,测试步骤:

  1. 开启两个窗口,A 窗口和 B 窗口
  2. 在 A 窗口查询隔离级别:select @@tx_isolation;
  3. 设置 A 窗口隔离级别为最低:set session transaction isolation level read uncommitted;
  4. 两个窗口都开启事务:start transaction;
  5. B 窗口执行转账操作,不提交事务
  6. A 窗口查询数据,能读到 B 窗口未提交的数据
  7. B 窗口执行 rollback 回滚事务,A 窗口读到的数据就是无效的脏数据

2. 避免脏读和演示不可重复读

想要避免脏读,我们需要提高隔离级别:

  1. 设置隔离级别为 Read committed:set session transaction isolation level read committed;
  2. 两个窗口开启事务
  3. B 窗口执行转账,不提交事务,A 窗口查询不到未提交数据,避免了脏读
  4. B 窗口提交事务后,A 窗口再次查询,结果不一致,出现不可重复读

3. 避免不可重复读

想要解决不可重复读,设置隔离级别为 Repeatable read:set session transaction isolation level repeatable read;按照同样的步骤测试,就能避免不可重复读问题。

4. 避免各种读问题

如果想要完全避免脏读、不可重复读、幻读所有问题,设置最高隔离级别 Serializable:set session transaction isolation level serializable;这种级别会串行执行事务,所有读问题都能解决,但是效率最低。

总结

事务是数据库操作的核心,尤其是在金融、订单、支付等业务中,事务的使用直接决定了数据是否安全可靠。下一篇文章,我们会接着学习数据库连接池的相关知识,包括连接池概念、常用连接池以及 Druid 连接池的使用和工具类封装。

相关推荐
若鱼19192 小时前
JPA/Hibernate中一对一关联时不持有外键方的属性延迟加载为什么不生效?
java·spring
砍材农夫2 小时前
spring-ai 第八模型介绍-图像模型
java·人工智能·spring
川trans2 小时前
基于 Docker & K8s 的 MySQL 容器化部署与应用关联实践
mysql·docker·kubernetes
橘子hhh2 小时前
Netty基础服务器实现
java·nio
墨雪遗痕3 小时前
工程架构认知(二):从 CDN 到 Keep-Alive,理解流量如何被“消化”在系统之外
java·spring·架构
刘~浪地球3 小时前
数据库与缓存--MySQL 高可用架构设计
数据库·mysql·缓存
用户6688599847663 小时前
Sprint Boot登录案例
java
Ivanqhz3 小时前
LLVM IR 转 SMT公式
java·开发语言
一个心烑3 小时前
奖项届定获取方式
java