数据库的事务
事务
- 事务的概述
- 事务的特性
- 在MySQL中操作事务
- 命令式显式开启事务
- 关闭自动提交(全局设置式)
- 事务的隔离级别
- Read uncommitted
- Read committed
- Repeatable read
- Serializable
事务的概述
事务(Transaction)是数据库管理系统(DBMS)中,被封装为单个逻辑工作单元的一系列数据库操作(如查询、插入、更新、删除等)的集合。事务确保确保要么所有操作均成功执行并永久生效(提交),要么任一操作失败时,所有已执行的操作均被撤销,数据库恢复到事务开始前的状态(回滚)。事务旨在维护数据库的完整性、一致性和可靠性,即使在系统故障或并发访问的情况下也是如此。
经典示例:银行转账
假设用户 A 向用户 B 转账 100 元,这个过程包含两条核心 SQL 操作:
从 A 的账户扣除 100 元:UPDATE account SET balance = balance - 100 WHERE id = ‘A’;
向 B 的账户增加 100 元:UPDATE account SET balance = balance + 100 WHERE id = ‘B’;
这两条操作必须构成一个事务:
若两条都执行成功,事务提交(COMMIT),数据生效;
若任意一条失败,事务回滚(ROLLBACK),A 被扣的 100 元恢复。
事务的特性
- 原子性(Atomicity):事务中的所有操作是一个不可拆分的原子单元,不存在部分执行的中间状态。要么全部成功,要么全部回滚,不会因故障导致操作一半的情况。
- 一致性(Consistency):事务执行后,数据库状态与业务规则保持一致。例如转账业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变的。
- 隔离性(Isolation):多个事务同时执行时,彼此的操作互不干扰。DBMS 通过隔离级别控制并发事务的可见性,避免脏读、不可重复读、幻读等问题。
- 持久性(Durability):一旦事务提交成功,事务中所有的数据操作都被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据
在MySQL中操作事务
命令式显式开启事务
第一种方式是命令式显式开启事务,通过 start transaction;
显式声明事务的开始,后续操作作为事务内的逻辑单元,最终通过 commit 或 rollback 结束事务。
示例:
commit
rollback
一旦commit,即提交事务,事务结束,数据永久的保存到数据库中
一旦roolback,即回滚事务,事务结束,数据回滚到最初始化的状态
事务边界清晰,通过 start transaction 和 commit / rollback 明确控制事务的状态。每次事务需手动开启,适用于临时、单次的事务操作。
关闭自动提交(全局设置式)
第二种方式是关闭自动提交,MySQL 默认开启 autocommit(自动提交),即每条 SQL 语句会被自动封装为一个独立事务并立即提交。
show variables like '%commit%';
查看事务的是事务是否是默认提交,通过关闭自动提交 set autocommit = off;
,可将多条 SQL 纳入同一个事务,直到手动提交或回滚。
示例:
commit
rollback
若需恢复默认行为,执行 set autocommit = on;
或关闭此cmd窗口
一次设置影响后续所有 SQL,无需重复开启事务,适用于批量、连续的事务操作。若未手动提交 / 回滚而断开连接,MySQL 会自动回滚事务,避免数据不一致。
在 Java 中通过 JDBC 操作事务时,与 MySQL 操作一致默认自动提交,核心是通过 Connection 对象控制事务。void setAutoCommit(boolean autoCommit)
如果传入了false,即设置MySQL数据库的事务不默认提交,void commit() 提交事务,void rollback() 回滚事务
补充:在 Java 中通过 JDBC 操作事务时,JDBC 规范默认将连接(Connection)的事务设置为自动提交模式(即 autoCommit 默认为 true),这与 MySQL 数据库默认的一致。但本质是 JDBC 连接级别的控制,仅影响当前 Connection 对象,不会修改数据库全局的 autocommit 配置。
事务的隔离级别
不考虑事务的隔离性会引发的问题
- 脏读:一个事务在执行过程中,读取到了另一个尚未提交的事务对数据的修改结果;
- 不可重复读:在同一个事务的生命周期内,对同一条数据进行多次读取,期间另一个事务对该数据执行了修改并提交的操作,导致事务多次读取的结果不一致;
- 虚度(幻读):在同一个事务的生命周期内,对同一范围条件的数据进行多次查询,期间另一个事务执行了插入或删除符合该范围条件的记录并提交的操作,导致事务多次查询的结果集不一致。
设置事务的隔离级别
- 事务的隔离级别,查询隔离级别
select @@tx_isolation;
Read uncommitted ‐‐ 什么都解决不了
Read committed ‐‐ 避免脏读,但是不可重复读和虚读有可能产生
Repeatable read ‐‐ 避免脏读和不可重复读,虚度有可能产生的
Serializable ‐‐ 避免各种读 - 四种隔离级别的安全性和效率
安全:Serializable > Repeatable read > Read committed > Read uncommitted
效率:Serializable < Repeatable read < Read committed < Read uncommitted - 数据库默认的隔离级别
MySQL的数据库,默认的隔离级别是Repeatable read,避免脏读和不可重复读
下面我们来具体演示不同的事务隔离级别产生的不同效果
Read uncommitted
开启两个窗口,一个A窗口(左),一个B窗口(右)
设置A窗口的隔离级别为最低级别 set session transaction isolation level read uncommitted;
,在两个窗口中,都开启事物 start transaction;
,在B窗口中完成转账的工作 update account set money = money - 100 where name = ‘熊二’;,注意B窗口的事务未提交,在A窗口中查询结果集
发现产生了脏读,一个事务读取到了另一个事物未提交的数据
此时B窗口回滚事务,在A窗口中查询结果集
A与B窗口读到的均是事务回滚后的数据集
我们继续演示,A与B均开启事务,在B窗口中完成转账的工作 update account set money = money - 100 where name = ‘熊二’;,B窗口提交事务,A窗口不提交,在A窗口中查询结果集
发现产生了不可重复读,一个事务读取到了另一个事务提交的修改数据,导致了多次查询的结果不一致
我们继续演示,A与B均开启事务,在B窗口中添加一条数据 insert into account values (null,‘小苍’,1000); ,B窗口提交事务,A窗口不提交,在A窗口中查询结果集
发现产生了虚读(幻读),一个事务读取到了另一个事务提交的新增数据,导致了多次查询的结果不一致
Read committed
开启两个窗口,一个A窗口(左),一个B窗口(右)
设置A窗口的隔离级别为 set session transaction isolation level read committed;
,在两个窗口中,都开启事物 start transaction;
,在B窗口中完成转账的工作 update account set money = money - 100 where name = ‘熊二’;,注意B窗口的事务未提交,在A窗口中查询结果集
发现避免了脏读
我们继续演示,B窗口提交事务,A窗口不提交,在A窗口中查询结果集
发现产生了不可重复读,一个事务读取到了另一个事务提交的修改数据,导致了多次查询的结果不一致
此时A窗口也提交事务
A与B窗口读到的均是事务提交后的数据集
我们继续演示,A与B均开启事务,在B窗口中添加一条数据 insert into account values (null,‘小羊’,1000); ,B窗口提交事务,A窗口不提交,在A窗口中查询结果集
发现产生了虚读(幻读),一个事务读取到了另一个事务提交的新增数据,导致了多次查询的结果不一致
Repeatable read
开启两个窗口,一个A窗口(左),一个B窗口(右)
设置A窗口的隔离级别为 set session transaction isolation level repeatable read;
,在两个窗口中,都开启事物 start transaction;
,在B窗口中完成转账的工作 update account set money = money - 100 where name = ‘熊二’;,B窗口的事务未提交,在A窗口中查询结果集,以及B窗口的事务提交A未提交,在A窗口中查询结果集
发现避免了脏读和不可重复读
我们继续演示,A与B均开启事务,在B窗口中添加一条数据 insert into account values (null,‘小牛’,1000); ,B窗口提交事务,A窗口不提交
在A窗口中查询结果集,没有显示第30行数据,但是在第30行无法添加数据,即第30行数据已被占用。即产生了虚读(幻读),一个事务读取到了另一个事务提交的新增数据,导致了多次查询的结果不一致。
Serializable
开启两个窗口,一个A窗口(左),一个B窗口(右)
设置A窗口的隔离级别为 set session transaction isolation level serializable;
,在两个窗口中,都开启事务 start transaction;
B窗口中执行新增操作 insert into account values (null,‘小小’,1100);,无法执行成功,在A窗口中查询结果集没有该数据,A窗口的事务提交后B窗口再次执行新增操作才可以成功,Serializable 避免了幻读。