四,事务

49 阅读6分钟

一,定义

  • 事务是一组操作的集合,它是一个不可分割的工作单位
  • 事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,这些操作要么同时成功,要么同时失败

二,流程

开启事务---->提交事务

如果中间出现了异常,就回滚事务,回滚事务指的是将修改的数据恢复。

注意:MYSQL中默认事务自动提交,即执行DML语句的时候MYSQL会立即隐式的提交事务.

三,事务的操作演示

以转账为例

假定有这样一个账单

账户余额
张三2000
李四2000

现进行以下操作

  1. 查询张三的余额

    select * from account where name='张三';
    
  2. 对张三的余额-1000

    update account set money=money-1000 where name='张三';
    
  3. 对李四的余额+1000

    update account set money=money+1000 where name='李四';
    

对这三条指令在同一时间执行那么表会变成

账户余额
张三1000
李四3000

假定2--3中存在SQL语句A,且A中SQL语句必然不能执行成功

这张表就会变成

账户余额
张三1000
李四2000

那么将1,2,A,3的代码在同一时间执行,就会出现1,2执行成功A,3执行不超过

这时候我们回滚事务即可。

在MYSQL中每一条DML语句都是一个单独的事务,并且默认自动提交。

3.1 事务的执行语法

查看事务提交方式

Select @@autocommit;

如果是1代表手动提交,0代码自动提交。在MYSQL中@@代表系统变量,@代表用户自定义变量。

设置事务的提交方式

Set @@autocommit=0或者1;(这个是将整个控制台提交方式的修改)

或者

begin;  开启事务
Start transcation; 开启事务

提交事务

commit;

回滚事务

rollback;

一般事务用法:

beginstart transcation
SQL语句
commit;

出现错误
回滚事务
roolback;

四,事务的四大特性(ACID)

4.1 原子型(Atomicity)

  • 定义:事务的所有操作要么全部成功提交,要么全部失败回滚,不存在中间状态。
  • 实现机制
    • Undo Log(回滚日志):记录事务修改前的数据快照。若事务回滚,InnoDB利用Undo Log逆向恢复数据。
    • 事务状态管理:事务的提交(COMMIT)或回滚(ROLLBACK)操作由引擎内部原子性保证。
  • 示例:转账事务中,若扣款成功但加款失败,系统自动回滚,确保两个账户余额不变。

4.2 一致性(Consistency)

  • 定义:事务执行前后,数据库必须满足所有预定义的业务规则和约束(如唯一索引、外键、数据类型等)。
  • 实现机制
    • 数据库约束:如主键、外键、NOT NULL、CHECK约束(MySQL 8.0+强化支持)。
    • 应用层逻辑:开发者需确保业务逻辑的正确性(例如转账前后总额不变)。
    • 原子性、隔离性、持久性的共同保障:其他三个ACID特性最终服务于一致性。
  • 示例:若用户定义“余额不可为负”,事务中的扣款操作会因违反约束而失败,保持数据一致性。

4.3 隔离性(Isolation)

  • 定义:并发事务的执行互不干扰,避免数据不一致。

  • 隔离级别与问题

    隔离级别脏读不可重复读幻读
    READ UNCOMMITTED✔️✔️✔️
    READ COMMITTED✖️✔️✔️
    REPEATABLE READ (默认)✖️✖️✖️*
    SERIALIZABLE✖️✖️✖️

    *注:InnoDB的REPEATABLE READ通过Next-Key锁避免幻读。

  • 实现机制

    • MVCC(多版本并发控制)
      • Read View:事务启动时生成数据快照,确保读取一致性。
      • Undo Log链:提供历史版本数据,支持非锁定读。
    • 锁机制
      • 行锁:锁定被修改的行,防止其他事务写冲突。
      • 间隙锁(Gap Locks)Next-Key锁:锁定索引范围,防止幻读。
  • 示例:在REPEATABLE READ级别下,事务A多次读取同一数据结果一致,即使事务B已修改并提交。

4.4 持久性(Durability)

  • 定义:事务提交后,数据永久保存,即使系统崩溃也不丢失。
  • 实现机制
    • Redo Log(重做日志)
      • 记录物理修改(如页的变更),事务提交时先写入Redo Log,再异步刷盘。
      • 崩溃恢复时,重放Redo Log恢复未写入数据文件的操作。
    • Double Write Buffer
      • 防止页断裂(Partial Page Write),确保数据页写入的完整性。
    • Binlog(归档日志)
      • 用于主从复制,通过两阶段提交与Redo Log协作保证一致性。
  • 示例:事务提交后,即使数据库立即崩溃,重启后仍能通过Redo Log恢复数据。

五,并发事务产生的问题

A事务和B事务同时操作同一张表引发的问题叫并发事务问题,就多个操作端,同时操作一张表产生的问题。

测试:

-- 创建一个银行账户表
CREATE TABLE bank_account (
    id INT PRIMARY KEY AUTO_INCREMENT,
    account_name VARCHAR(50) NOT NULL,
    balance DECIMAL(10, 2) NOT NULL DEFAULT 0.00
) ENGINE=InnoDB;  -- 必须使用 InnoDB 引擎(支持事务)

-- 插入初始数据
INSERT INTO bank_account (account_name, balance)
VALUES
    ('Alice', 1000.00),
    ('Bob', 500.00);

5.1 脏读

事务A读取到了事务B未提交的数据

  1. 设置隔离级别为 READ UNCOMMITTED

    SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
    
  2. 在会话 A 中执行未提交的写操作

    -- 会话 A
    BEGIN;
    UPDATE bank_account SET balance = balance - 100 WHERE account_name = 'Alice';
    -- 注意:不提交事务!
    
  3. 在会话 B 中读取未提交的数据

    -- 会话 B
    SELECT * FROM bank_account WHERE account_name = 'Alice';
    

    结果:Alice 的余额变为 900.00(即使会话 A 未提交)。

总结:

  1. 事务A更新数据
  2. 事务B查询数据,查询到了事务A未提交的数据

5.2 不可重复读

在一个事务中,查询俩次前后查询数据不一致的叫不可重复读

  1. 设置隔离级别为 READ COMMITTED

    SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
    
  2. 在会话 A 中多次查询同一数据

    -- 会话 A
    BEGIN;
    SELECT balance FROM bank_account WHERE account_name = 'Alice';  -- 第一次查询
    -- 结果:1000.00
    
  3. 在会话 B 中修改并提交数据

    -- 会话 B
    BEGIN;
    UPDATE bank_account SET balance = balance - 100 WHERE account_name = 'Alice';
    COMMIT;
    
  4. 在会话 A 中再次查询

    SELECT balance FROM bank_account WHERE account_name = 'Alice';  -- 第二次查询
    -- 结果:900.00(两次结果不一致)
    

总结:

  1. 事务A执行1查询
  2. 事务B更新
  3. 事务A执行2查询

5.3 幻读

在一个事务内,多次查询同一范围的数据,后一次查询发现了前一次查询未出现的新行(这些新行是其他事务插入并提交的)。

  1. 设置隔离级别为 REPEATABLE READ

    SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    
  2. 在会话 A 中查询范围数据

    -- 会话 A
    BEGIN;
    SELECT * FROM bank_account WHERE balance > 800;  -- 结果:Alice(1000.00)
    
  3. 在会话 B 中插入新数据并提交

    -- 会话 B
    BEGIN;
    INSERT INTO bank_account (account_name, balance) VALUES ('Charlie', 900.00);
    COMMIT;
    
  4. 在会话 A 中再次查询

    SELECT * FROM bank_account WHERE balance > 800;  -- 结果:仍只有 Alice(REPEATABLE READ 通过 MVCC 避免幻读)
    

六,并发事务的隔离级别

  • √代表会出现

  • ×代表不会出现

隔离级别脏读不可重复读幻读
Read uncommitted
Read committed×
Repeatable Read(默认)×××
Serializable×××

从read uncommitted到serializable 隔离级别依次提高,性能依次下降。

语法

查询当前事务的隔离级别

select @@transaction_isolation;  

设置事务的隔离级别

set [session/global] transaction isolation level [级别];

session代表对当前窗口有效,global代表对全部窗口有效