深入解析 MySQL 事务:从基础概念到高级应用

深入解析 MySQL 事务:从基础概念到高级应用

一、定义

事务是由一个或多个 SQL 语句组成的独立工作单元,在这个单元中,每个 SQL 语句都是相互依赖的。事务作为一个不可分割的整体存在,要么全部成功,要么全部失败,不可能存在部分成功、部分失败的情况。

简而言之,事务就是一个整体,内部的 SQL 语句要么全部执行成功,要么全部执行失败。一旦某条 SQL 语句执行失败或出错,整个事务将回滚(返回最初状态),所有受到影响的数据将恢复到事务开始前的状态;若所有 SQL 语句执行成功,则事务顺利提交。

在 MySQL 中,数据存储由不同的存储引擎管理,不同存储引擎有各自特点。常见存储引擎有 InnoDB、MyISAM 和 Memory 等。其中,InnoDB 支持事务(transaction),而 MyISAM 和 Memory 等不支持事务。

可以通过 SHOW ENGINES; 语句查看 MySQL 支持的存储引擎。

二、特性(ACID)

事务具有四个关键特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),简称 ACID。

  • 原子性(Atomicity):事务是一个不可分割的最小工作单位,事务中的操作要么全部发生,要么全部不发生。
  • 一致性(Consistency):事务必须使数据库从一个一致状态变换到另一个一致状态。例如,李二给王五转账 50 元,要么李二账户减 50 元、王五账户加 50 元,要么都不发生。
  • 隔离性(Isolation):一个事务的执行不能被其他事务干扰,事务内部的操作及使用的数据对并发的其他事务是隔离的。
  • 持久性(Durability):事务一旦提交成功,它对数据库的改变将是永久性的,后续的操作或故障不会影响其结果。

三、分类

事务分为隐式事务和显式事务。DML 语句(INSERT、UPDATE、DELETE)是隐式事务。

隐式事务

没有明显的开启和结束标记,自动提交事务。比如,UPDATE 语句修改数据,相当于一个隐式事务。

举例:张三同学购买一个 CSDN 定制保温杯花 99 元,这相当于一个 UPDATE 语句,减去张三余额中的 99 元:

UPDATE accounts SET balance = balance - 99 WHERE name = '张三';

显式事务

具有明显的开启和结束标记。使用显式事务的前提是禁用自动提交功能(将 autocommit 设置为 0)。

查看当前 autocommit 状态:

SHOW VARIABLES LIKE 'autocommit';

禁用自动提交:

SET autocommit = 0;

四、开启事务

显式事务需要手动开启和提交或回滚。以下是一个使用显式事务的示例:

-- 步骤一:开启事务
START TRANSACTION;

-- 步骤二:编写事务中的 SQL 语句(INSERT、UPDATE、DELETE)
-- 例如,实现“李二给王五转账”的事务过程
UPDATE t_account SET balance = balance - 50 WHERE vname = '李二';
UPDATE t_account SET balance = balance + 50 WHERE vname = '王五';

-- 步骤三:结束事务
COMMIT; -- 提交事务
-- ROLLBACK; -- 回滚事务:即事务不执行,恢复到事务执行前的状态

五、并发问题

多个事务并发运行时,如果没有适当的隔离机制,就会发生以下问题:

  • 脏读(Dirty Read):事务 T1 读取了事务 T2 更新但未提交的数据,如果 T2 回滚,T1 读取的数据是无效的。
  • 不可重复读(Non-repeatable Read):事务 T1 读取某字段后,事务 T2 更新了该字段,T1 再次读取时,值不同。
  • 幻读(Phantom Read):事务 T1 读取某表的数据后,事务 T2 向该表插入新数据,T1 再次读取时,发现多了几行数据。

为了避免这些并发问题,MySQL 提供了四种事务隔离级别:

六、隔离级别

MySQL 提供四种事务隔离级别,不同隔离级别对应不同的并发控制程度和性能影响:

  • 读未提交(Read Uncommitted):允许读取未提交的变更,可能出现脏读、不可重复读和幻读。适用于对数据一致性要求低的场景。
  • 读已提交(Read Committed):只能读取已提交的变更,可以避免脏读,但仍可能出现不可重复读和幻读。适用于对数据一致性要求较高,但允许部分并发的场景。
  • 可重复读(Repeatable Read):确保多次读取同一字段的值一致,可以避免脏读和不可重复读,但仍可能出现幻读。MySQL 的默认隔离级别。
  • 串行化(Serializable):完全锁定事务执行期间的数据,避免所有并发问题,但性能较低。适用于对数据一致性要求极高,且允许性能降低的场景。

Oracle 支持两种事务隔离级别:Read Committed 和 Serializable。
Oracle 默认事务隔离级别是:Read Committed。
MySQL 默认事务隔离级别是:Repeatable Read。

事务隔离级别越高,数据一致性越好,但并发性能越差。开发者需要根据具体业务需求选择合适的隔离级别,以在性能和数据一致性之间取得平衡。

七、示例

假设我们有一个简单的银行账户转账系统,包含两个账户表(t_account)。我们希望在两个账户之间进行转账操作时,保证事务的一致性。

1. 创建数据库和表

CREATE DATABASE bank;
USE bank;

CREATE TABLE t_account (
    id INT AUTO_INCREMENT PRIMARY KEY,
    vname VARCHAR(50) NOT NULL,
    balance DECIMAL(10, 2) NOT NULL
);

INSERT INTO t_account (vname, balance) VALUES ('李二', 100.00), ('王五', 100.00);

2. 实现转账操作

-- 开启事务
START TRANSACTION;

-- 从李二账户扣除 50 元
UPDATE t_account SET balance = balance - 50 WHERE vname = '李二';

-- 向王五账户增加 50 元
UPDATE t_account SET balance = balance + 50 WHERE vname = '王五';

-- 提交事务
COMMIT;

-- 检查结果
SELECT * FROM t_account;

在实际应用中,开发者可以通过编程语言(如 Java、Python)结合数据库驱动(如 JDBC、SQLAlchemy)来管理事务。例如,在 Java 中,可以使用 JDBC 的 Connection 对象的事务管理方法:

try (Connection conn = dataSource.getConnection()) {
    conn.setAutoCommit(false);

    try (PreparedStatement stmt1 = conn.prepareStatement("UPDATE t_account SET balance = balance - ? WHERE vname = ?")) {
        stmt1.setBigDecimal(1, new BigDecimal("50.00"));
        stmt1.setString(2, "李二");
        stmt1.executeUpdate();
    }

    try (PreparedStatement stmt2 = conn.prepareStatement("UPDATE t_account SET balance = balance + ? WHERE vname = ?")) {
        stmt2.setBigDecimal(1, new BigDecimal("50.00"));
        stmt2.setString(2, "王五");
        stmt2.executeUpdate();
    }

    conn.commit();
} catch (SQLException e) {
    conn.rollback();
    throw e;
}

通过这种方式,可以在应用程序层面确保事务的原子性、一致性、隔离性和持久性(ACID)。

相关推荐

  1. 深入解析 MySQL 事务基础概念高级应用

    2024-06-15 09:28:02       34 阅读
  2. 深度解析Docker命令:入门高级应用

    2024-06-15 09:28:02       48 阅读
  3. 深度剖析Redis:基础高级应用

    2024-06-15 09:28:02       47 阅读
  4. 深入理解 Go 函数:基础高级

    2024-06-15 09:28:02       58 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-15 09:28:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-15 09:28:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-06-15 09:28:02       82 阅读
  4. Python语言-面向对象

    2024-06-15 09:28:02       91 阅读

热门阅读

  1. C++ const关键字有多种用法举例

    2024-06-15 09:28:02       34 阅读
  2. 回溯算法练习题(2024/6/12)

    2024-06-15 09:28:02       32 阅读
  3. 如何发布自己的NPM插件包?

    2024-06-15 09:28:02       37 阅读
  4. rman 后 PDB datafile 丢失要在PDB级删除

    2024-06-15 09:28:02       24 阅读
  5. 【技巧】Leetcode 191. 位1的个数【简单】

    2024-06-15 09:28:02       26 阅读
  6. 工业设计初学者手册——第四部分:制造工艺

    2024-06-15 09:28:02       41 阅读
  7. 浅析双面PCB电路板制造过程

    2024-06-15 09:28:02       28 阅读
  8. Web后端开发(分层解耦)

    2024-06-15 09:28:02       28 阅读