Skip to main content

深度学习

思维导图

正文

MySQL 事务深度学习资料

引言:为什么我们需要事务?

在深入了解 MySQL 事务之前,我们首先要理解为什么数据库需要事务。考虑一个简单的银行转账的例子:

  • 步骤 1: 从账户 A 中扣除 100 元。
  • 步骤 2: 向账户 B 中增加 100 元。

这两个步骤必须作为一个不可分割的整体来执行。如果只完成了步骤 1,而步骤 2 失败了(例如,由于系统崩溃),那么账户 A 就少了 100 元,而账户 B 却没有增加,这就会导致数据不一致,造成严重的错误。

事务就是为了解决这类问题而生的。它保证了一系列操作要么全部成功完成(提交 - Commit),要么全部失败回滚(回滚 - Rollback),从而维护数据库的 数据一致性数据完整性

1. 事务的基本概念:ACID 属性

事务的核心概念是 ACID 属性,它们是事务可靠性的基石。ACID 代表:

  • 原子性 (Atomicity): 事务被视为一个不可分割的最小单元。事务中的所有操作要么全部完成,要么全部不完成。不存在部分完成的情况。

    • 例子: 在银行转账的例子中,原子性保证了要么账户 A 扣款 并且 账户 B 加款都成功,要么这两个操作都失败,账户状态保持不变。
    • 英文解释: Atomicity ensures that a transaction is treated as a single, indivisible unit of work. All operations within a transaction either succeed completely or fail completely. There is no state of partial completion.
  • 一致性 (Consistency): 事务必须保证数据库从一个一致性状态转换到另一个一致性状态。一致性状态是指数据库满足所有的约束和规则(如唯一性约束、外键约束等)。

    • 例子: 假设银行系统的总资产是恒定的。转账操作必须保证总资产不变。从账户 A 扣除的钱必须正好等于账户 B 增加的钱。
    • 英文解释: Consistency ensures that a transaction brings the database from one valid state to another valid state. A valid state means that the database adheres to all defined rules and constraints, such as uniqueness constraints, foreign key constraints, etc.
  • 隔离性 (Isolation): 多个事务并发执行时,每个事务都应该感觉像是独立运行的,不受其他事务的干扰。事务的隔离级别定义了事务之间互相影响的程度。

    • 例子: 假设两个用户同时从同一个账户 A 转账。隔离性要保证这两个转账操作不会互相干扰,例如,一个事务读取了账户 A 的余额后,另一个事务修改了余额,第一个事务不应该读到修改后的未提交的余额。
    • 英文解释: Isolation ensures that when multiple transactions are executed concurrently, each transaction should feel as if it is running in isolation from others. The isolation level of a transaction defines the degree to which transactions are isolated from each other.
  • 持久性 (Durability): 一旦事务提交成功,它对数据库的修改就应该是永久性的,即使系统发生故障(如断电、崩溃)。

    • 例子: 银行转账成功后,即使服务器突然崩溃,账户的余额也应该保持更新后的状态,不会丢失。
    • 英文解释: Durability ensures that once a transaction is committed, its changes to the database are permanent and will survive system failures (like power outages or crashes).

总结 ACID 属性:

ACID 属性共同确保了数据库事务的可靠性和数据的完整性。在实际应用中,我们需要根据业务需求和性能考虑来权衡 ACID 属性的实现程度,尤其是在隔离性方面,不同的隔离级别会影响并发性能和数据一致性。

2. MySQL 中的事务支持:存储引擎

MySQL 的事务支持主要由存储引擎来提供。最常用的支持事务的存储引擎是 InnoDB。 MyISAM 等其他存储引擎是不支持事务的。

  • InnoDB 存储引擎:

    • 默认的 MySQL 存储引擎,也是最推荐的存储引擎,尤其是在需要事务支持的应用中。
    • 完全支持 ACID 属性。
    • 提供行级锁,支持高并发。
    • 支持 MVCC (多版本并发控制),提高并发性能。
    • 支持崩溃恢复,保证持久性。
  • MyISAM 存储引擎:

    • 不支持事务。
    • 表级锁,并发性能较低。
    • 不支持崩溃恢复。
    • 通常用于只读或读多写少的场景,例如日志记录、数据仓库等。

重要结论: 如果你需要使用事务,必须确保你的表使用的是 InnoDB 存储引擎。 你可以通过 SHOW TABLE STATUS LIKE 'your_table_name'; 命令查看表的存储引擎,或者在创建表时指定 ENGINE=InnoDB;

3. 事务的生命周期和状态

一个事务的生命周期通常包括以下几个阶段:

  • 开始事务 (Begin Transaction): 事务的开始标志。在 MySQL 中,可以使用 START TRANSACTIONBEGIN 语句显式地开始一个事务。

    • 英文: To start a transaction in MySQL, you can use the START TRANSACTION or BEGIN statement.
  • 事务中的操作 (Operations in Transaction): 在事务中执行一系列的 SQL 操作,例如 INSERTUPDATEDELETESELECT 等。

    • 英文: Within a transaction, you perform a series of SQL operations, such as INSERT, UPDATE, DELETE, SELECT, etc.
  • 提交事务 (Commit Transaction): 当事务中的所有操作都成功完成,并且希望将修改永久保存到数据库时,执行 COMMIT 语句。提交事务会使事务变为持久状态。

    • 英文: When all operations in a transaction are successful and you want to permanently save the changes to the database, execute the COMMIT statement. Committing a transaction makes it durable.
  • 回滚事务 (Rollback Transaction): 如果事务执行过程中发生错误,或者出于某种原因需要撤销事务的所有操作,可以执行 ROLLBACK 语句。回滚事务会将数据库恢复到事务开始之前的状态。

    • 英文: If an error occurs during transaction execution, or if you need to undo all operations for any reason, you can execute the ROLLBACK statement. Rolling back a transaction reverts the database to the state it was in before the transaction began.

事务状态:

  • 活跃 (Active): 事务正在执行中,尚未提交或回滚。
  • 部分提交 (Partially Committed): 事务的最后操作已经完成,但事务尚未完全提交。这个状态通常是短暂的。
  • 已提交 (Committed): 事务成功完成并提交,对数据库的修改已永久保存。
  • 失败 (Failed): 事务执行过程中发生错误,事务需要回滚。
  • 已回滚 (Rolled Back): 事务已回滚,数据库恢复到事务开始之前的状态。

代码示例 (银行转账事务):

START TRANSACTION; -- 开始事务

-- 步骤 1:从账户 A 扣除 100 元
UPDATE accounts SET balance = balance - 100 WHERE account_id = 'A';

-- 步骤 2:向账户 B 中增加 100 元
UPDATE accounts SET balance = balance + 100 WHERE account_id = 'B';

-- 假设一切顺利,提交事务
COMMIT;

-- 如果发生错误,例如账户 A 余额不足,可以回滚事务
-- ROLLBACK;

4. 事务的隔离级别 (Isolation Levels)

隔离性是事务 ACID 属性中的重要组成部分。然而,完全的隔离性会降低并发性能。为了平衡隔离性和并发性能,SQL 标准定义了四种事务隔离级别,MySQL InnoDB 存储引擎都支持这些级别:

  • 读未提交 (Read Uncommitted): 最低的隔离级别。事务可以读取到其他事务未提交的数据(脏读)。

    • 脏读 (Dirty Read): 一个事务读取了另一个事务尚未提交的数据。如果未提交的事务最终回滚,那么第一个事务读取到的数据就是无效的“脏数据”。
    • 英文解释: The lowest isolation level. Transactions can read data that has been modified by other transactions but not yet committed (dirty read). Dirty read occurs when a transaction reads uncommitted data from another transaction. If the uncommitted transaction is rolled back, the data read by the first transaction becomes invalid "dirty data".
    • 例子: 事务 1 修改了账户 A 的余额,但尚未提交。事务 2 读取了账户 A 的余额,读到了事务 1 未提交的修改。如果事务 1 最终回滚,事务 2 读到的就是脏数据。
    • 风险: 数据不一致性非常严重,实际应用中几乎不会使用此隔离级别。
  • 读已提交 (Read Committed): 事务只能读取到其他事务已提交的数据。可以防止脏读,但仍然可能出现不可重复读。

    • 不可重复读 (Non-repeatable Read): 在同一个事务中,多次读取同一行数据,由于其他已提交事务的修改或删除操作,导致每次读取的结果不一致。
    • 英文解释: Transactions can only read data that has been committed by other transactions. Prevents dirty reads, but non-repeatable reads can still occur. Non-repeatable read happens when, within the same transaction, reading the same row multiple times yields different results due to modifications or deletions by other committed transactions.
    • 例子: 事务 1 两次读取账户 A 的余额。在事务 1 两次读取之间,事务 2 修改并提交了账户 A 的余额。事务 1 两次读取到的余额不一致。
    • 风险: 在某些需要数据一致性的场景下,不可重复读可能会导致问题。例如,在统计分析中,如果数据在读取过程中被修改,可能会导致统计结果不准确。
  • 可重复读 (Repeatable Read): MySQL InnoDB 的默认隔离级别。在同一个事务中,多次读取同一行数据,结果始终保持一致。可以防止脏读和不可重复读,但仍然可能出现幻读。

    • 幻读 (Phantom Read): 在同一个事务中,多次执行相同的范围查询,由于其他已提交事务的插入操作,导致每次查询的结果集中出现新的行(“幻影”行)。
    • 英文解释: The default isolation level in MySQL InnoDB. Within the same transaction, multiple reads of the same row will always return the same result. Prevents dirty reads and non-repeatable reads, but phantom reads can still occur. Phantom read happens when, within the same transaction, performing the same range query multiple times yields different result sets due to insertions by other committed transactions, resulting in "phantom" rows.
    • 例子: 事务 1 两次执行相同的范围查询(例如,查询余额大于 1000 元的所有账户)。在事务 1 两次查询之间,事务 2 插入了一个新的账户,余额大于 1000 元并提交。事务 1 第二次查询会比第一次查询多出一个账户,就像出现了“幻影”一样。
    • 注意: InnoDB 通过 MVCC (多版本并发控制)间隙锁 (Gap Locking) 在一定程度上缓解了幻读问题,但并非完全消除。在某些特定场景下,仍然可能出现幻读。
  • 串行化 (Serializable): 最高的隔离级别。事务串行执行,完全隔离,可以防止所有并发问题,包括脏读、不可重复读和幻读。

    • 英文解释: The highest isolation level. Transactions are executed serially, providing complete isolation, and preventing all concurrency issues, including dirty reads, non-repeatable reads, and phantom reads.
    • 实现方式: 通常通过强制事务串行执行来实现,例如使用严格的锁机制。
    • 性能影响: 并发性能最低,因为事务需要排队执行。
    • 适用场景: 对数据一致性要求极高,且并发量较低的场景,例如银行系统的核心交易系统。

隔离级别总结和对比:

隔离级别脏读 (Dirty Read)不可重复读 (Non-repeatable Read)幻读 (Phantom Read)并发性能数据一致性
读未提交 (Read Uncommitted)可能可能可能最高最低
读已提交 (Read Committed)不可能可能可能较高较高
可重复读 (Repeatable Read)不可能不可能可能 (InnoDB 缓解)中等中等
串行化 (Serializable)不可能不可能不可能最低最高

设置事务隔离级别:

可以使用以下 SQL 语句设置会话或全局的事务隔离级别:

-- 设置当前会话的事务隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL <isolation_level>;

-- 设置全局的事务隔离级别 (需要 SUPER 权限)
SET GLOBAL TRANSACTION ISOLATION LEVEL <isolation_level>;

-- <isolation_level> 可以是:
-- READ UNCOMMITTED
-- READ COMMITTED
-- REPEATABLE READ
-- SERIALIZABLE

-- 查看当前会话的事务隔离级别
SELECT @@transaction_isolation;

选择合适的隔离级别:

选择合适的隔离级别需要在 数据一致性并发性能 之间进行权衡。

  • 高并发、读多写少: 可以考虑使用 读已提交 (Read Committed) 级别,牺牲一定的可重复读保证,换取更高的并发性能。
  • 高并发、写多读多、数据一致性要求较高: 可重复读 (Repeatable Read) 是一个比较好的平衡选择,也是 InnoDB 的默认级别。
  • 低并发、数据一致性要求极高: 可以考虑使用 串行化 (Serializable) 级别,但需要评估性能影响。
  • 永远不要使用 读未提交 (Read Uncommitted): 除非你非常清楚你在做什么,并且可以接受脏读带来的数据不一致性风险。

5. MySQL 中的并发控制:锁机制和 MVCC

为了实现事务的隔离性,MySQL InnoDB 存储引擎使用了多种并发控制机制,主要包括 锁机制 (Locking)多版本并发控制 (MVCC - Multi-Version Concurrency Control)

5.1 锁机制 (Locking)

锁是实现并发控制的基本机制。当一个事务需要访问数据库资源时,需要先获取相应的锁。根据锁的粒度、排他性、和作用范围,MySQL 锁可以分为多种类型:

  • 锁的粒度:

    • 表级锁 (Table-level Lock): 锁定整个表。开销小,加锁快,但并发度低。MyISAM 存储引擎使用表级锁。
    • 行级锁 (Row-level Lock): 锁定表中的特定行。开销大,加锁慢,但并发度高。InnoDB 存储引擎使用行级锁。
  • 锁的排他性:

    • 共享锁 (Shared Lock, S Lock): 也称为读锁。多个事务可以同时持有同一个资源的共享锁。用于读取操作,防止其他事务排他地修改该资源。
    • 排他锁 (Exclusive Lock, X Lock): 也称为写锁。一次只能有一个事务持有资源的排他锁。用于修改操作(INSERT, UPDATE, DELETE),防止其他事务读取或修改该资源。
  • 锁的类型 (InnoDB 具体锁类型):

    • 记录锁 (Record Lock): 锁定索引记录上的单行记录。
      • 例子: SELECT * FROM table WHERE id = 1 FOR UPDATE; 会对 id=1 的记录加排他记录锁。
    • 间隙锁 (Gap Lock): 锁定索引记录之间的间隙,以及第一个索引记录之前或最后一个索引记录之后的间隙。用于防止幻读。
      • 例子: SELECT * FROM table WHERE id BETWEEN 10 AND 20 FOR UPDATE; 可能会对 id 值在 10 和 20 之间的间隙加间隙锁。
    • 临键锁 (Next-Key Lock): 是记录锁和间隙锁的组合,锁定索引记录本身以及记录之前的间隙。InnoDB 默认使用临键锁,用于实现可重复读隔离级别。
    • 插入意向锁 (Insert Intention Lock): 一种特殊的间隙锁,在 INSERT 操作准备插入新记录时产生。多个事务持有插入意向锁并不冲突,提高了并发插入性能。
    • 自增锁 (AUTO-INC Lock): 表级别的锁,用于处理自增列的并发插入。

锁的兼容性:

锁类型共享锁 (S)排他锁 (X)
共享锁 (S)兼容冲突
排他锁 (X)冲突冲突

锁的获取和释放:

  • 隐式锁: InnoDB 在执行 SQL 语句时,会自动根据需要添加锁。例如,SELECT ... FOR UPDATE 会加排他锁,普通的 SELECT 语句在可重复读隔离级别下,如果读取的数据在快照版本中,则不加锁,如果读取的是当前版本,则会加共享锁。
  • 显式锁: 可以使用 LOCK TABLESUNLOCK TABLES 语句显式地对表加锁和解锁 (通常不推荐在事务中使用表级锁,影响并发)。

锁的等待和死锁:

  • 锁等待: 当事务请求的锁被其他事务持有,事务会进入锁等待状态。
  • 死锁 (Deadlock): 两个或多个事务互相等待对方释放锁,造成循环等待,导致所有事务都无法继续执行。
    • 例子:
      • 事务 1 持有表 A 的锁,请求表 B 的锁。
      • 事务 2 持有表 B 的锁,请求表 A 的锁。
      • 事务 1 和 事务 2 互相等待对方释放锁,形成死锁。
    • 死锁检测和解决: MySQL InnoDB 会自动检测死锁,并选择一个事务进行回滚,释放其持有的锁,让其他事务继续执行。通常回滚的是 "代价" 较小的事务。

5.2 多版本并发控制 (MVCC - Multi-Version Concurrency Control)

MVCC 是一种提高并发性能的技术,它允许多个事务同时读取同一行数据,而不需要加锁。InnoDB 的 MVCC 实现主要依赖于以下机制:

  • 隐藏列: InnoDB 为每行数据添加了两个隐藏列:

    • trx_id: 创建或最后一次修改该行的事务 ID。
    • roll_ptr: 指向 undo log 的指针,用于回滚操作。
  • Undo Log (回滚日志): 记录了事务修改前的旧版本数据。在事务回滚时,可以通过 undo log 恢复到旧版本。MVCC 的多版本就是通过 undo log 来实现的。

  • Read View (读视图): 当事务启动时,InnoDB 会为事务创建一个 Read View。Read View 包含以下信息:

    • 创建该 Read View 时,所有 活跃事务 的 ID 列表。
    • 系统当前最大的事务 ID。

MVCC 的读取过程:

当事务执行 SELECT 查询时,InnoDB 会根据 Read View 和数据的 trx_id 来判断应该读取哪个版本的数据:

  1. 读取最新版本: 如果数据的 trx_id 是当前事务自己的 ID,或者小于 Read View 中最小的活跃事务 ID,则读取最新版本的数据。
  2. 读取快照版本 (历史版本): 如果数据的 trx_id 大于等于 Read View 中最小的活跃事务 ID,并且小于 Read View 中最大的事务 ID,则需要根据 roll_ptr 指针回溯到 undo log 中,找到一个合适的历史版本数据,并读取该历史版本。
  3. 不可见版本: 如果数据的 trx_id 大于等于 Read View 中最大的事务 ID,则表示该数据版本对于当前事务不可见。

MVCC 的优势:

  • 提高并发性能: 读操作不需要加锁,可以并发执行,提高系统的吞吐量。
  • 实现一致性快照: MVCC 可以在可重复读隔离级别下,保证事务在整个执行过程中看到的数据是一致的快照,不受其他事务修改的影响。

MVCC 和 隔离级别:

  • 读已提交 (Read Committed) 和 可重复读 (Repeatable Read): InnoDB 在这两个隔离级别下都使用 MVCC。

    • 读已提交 (Read Committed): 每次执行 SELECT 语句都会创建一个新的 Read View,因此每次读取都可能看到最新的已提交数据。
    • 可重复读 (Repeatable Read): 在事务开始时创建一个 Read View,整个事务期间都使用同一个 Read View,保证了可重复读。
  • 读未提交 (Read Uncommitted) 和 串行化 (Serializable): 这两个隔离级别不使用 MVCC。

    • 读未提交 (Read Uncommitted): 直接读取最新的数据版本,无需 MVCC。
    • 串行化 (Serializable): 通过加锁强制事务串行执行,无需 MVCC。

总结:锁机制和 MVCC 的协同工作

InnoDB 使用锁机制和 MVCC 结合的方式来实现并发控制和事务隔离。

  • 写操作 (INSERT, UPDATE, DELETE): 需要加锁,保证排他性和数据一致性。
  • 读操作 (SELECT):读已提交可重复读 隔离级别下,通过 MVCC 读取快照版本,提高并发性能。在 串行化 隔离级别下,读操作也可能需要加锁。

6. Savepoints (保存点)

Savepoints 允许你在一个事务中设置多个保存点。你可以回滚事务到任何一个之前设置的保存点,而不是整个事务回滚。这提供了一种更细粒度的事务控制方式。

  • 创建保存点: SAVEPOINT savepoint_name;
  • 回滚到保存点: ROLLBACK TO SAVEPOINT savepoint_name;
  • 释放保存点: RELEASE SAVEPOINT savepoint_name; (释放保存点后,就不能再回滚到该保存点,但不会影响事务的提交或回滚)

使用场景:

  • 复杂事务中的错误处理: 在一个复杂的事务中,如果中间某个步骤出错,可以使用保存点回滚到之前的状态,而不是回滚整个事务,可以减少不必要的撤销操作。
  • 嵌套事务的模拟: MySQL 不支持真正的嵌套事务,但可以使用保存点来模拟嵌套事务的效果。

代码示例 (使用 Savepoints):

START TRANSACTION;

-- 步骤 1
INSERT INTO table1 (col1) VALUES ('value1');
SAVEPOINT sp1; -- 设置保存点 sp1

-- 步骤 2
UPDATE table2 SET col2 = 'value2' WHERE id = 1;
SAVEPOINT sp2; -- 设置保存点 sp2

-- 步骤 3 (假设这里发生错误)
DELETE FROM table3 WHERE condition = 'error'; -- 假设 condition 不存在,导致删除失败

-- 回滚到保存点 sp2,撤销步骤 3 的操作
ROLLBACK TO SAVEPOINT sp2;

-- 继续执行其他操作 (例如,尝试另一种删除方式)
DELETE FROM table3 WHERE another_condition = 'valid';

-- 提交事务
COMMIT;

7. 事务管理最佳实践

  • 显式事务控制: 始终显式地使用 START TRANSACTION, COMMIT, ROLLBACK 来控制事务的边界,避免隐式事务带来的不可预测性。
  • 保持事务简短: 事务的执行时间越长,锁占用的时间就越长,并发性能越低。尽量将事务控制在必要的范围内,只包含完成逻辑操作所需的最小 SQL 语句集合。
  • 避免在事务中进行长时间的等待操作: 例如,等待用户输入、调用外部服务等。长时间的等待会占用数据库连接和资源,降低并发性能。
  • 合理选择隔离级别: 根据业务需求和数据一致性要求,选择合适的隔离级别。在满足数据一致性的前提下,尽量选择较低的隔离级别,以提高并发性能。
  • 处理事务中的错误: 在事务中要进行错误检查和处理,及时回滚事务,避免数据不一致。
  • 使用索引优化查询: 事务中的 SQL 查询语句要使用合适的索引,提高查询效率,减少锁的持有时间。
  • 避免死锁: 注意事务的加锁顺序,尽量保持一致的加锁顺序,减少死锁发生的概率。如果发生死锁,MySQL 会自动检测并回滚一个事务。应用程序需要处理事务回滚的情况,例如重试事务。
  • 监控事务: 监控数据库的事务执行情况,例如事务的执行时间、锁等待情况、死锁发生频率等,及时发现和解决性能问题。

8. 高级主题 (扩展学习方向)

  • 分布式事务 (Distributed Transactions): 当事务涉及多个数据库或服务时,需要使用分布式事务来保证跨多个系统的 ACID 属性。例如,XA 事务。
  • 两阶段提交 (Two-Phase Commit - 2PC): 一种常用的分布式事务协议。
  • 柔性事务 (柔性事务): 在分布式系统中,为了追求更高的性能和可用性,可以考虑使用柔性事务,牺牲一定的 ACID 属性,换取更高的系统扩展性。例如,最终一致性 (Eventually Consistent) 模型。
  • 事务日志 (Transaction Logs): MySQL InnoDB 使用 redo log 和 undo log 来保证事务的持久性和原子性。深入理解事务日志的原理和作用,有助于更好地理解 MySQL 的事务机制。
  • 事务的性能优化: 研究如何优化事务的性能,例如减少锁冲突、优化 SQL 语句、调整数据库参数等。

总结

MySQL 事务是保证数据一致性和完整性的重要机制。理解 ACID 属性、隔离级别、锁机制、MVCC 等核心概念,并掌握事务管理的最佳实践,对于开发可靠、高性能的 MySQL 应用至关重要。希望这份详细的学习资料能够帮助你深入理解 MySQL 事务的各个方面。

学习建议:

  • 实践操作: 在 MySQL 数据库中亲自编写和执行事务相关的 SQL 语句,例如银行转账、订单处理等例子,加深理解。
  • 阅读官方文档: MySQL 官方文档是学习 MySQL 最权威的资料,可以深入阅读关于事务、隔离级别、锁机制等章节。
  • 研究源码 (可选): 如果对底层实现感兴趣,可以研究 MySQL InnoDB 的源码,了解事务的内部实现机制。
  • 持续学习和探索: 数据库技术不断发展,事务管理也在不断演进。保持学习的热情,关注最新的技术发展趋势。

希望这份资料对你有所帮助!如果你有任何问题,欢迎随时提问。