MySQL事务与并发之：`事务`的`ACID`特性：`原子性`、`一致性`、`隔离性`和`持久性`的底层实现。

admin
Sat, 30 Aug 2025 09:48:48 +0000
Mysql, acid, mysql, 事务

MySQL事务与并发：ACID特性的底层实现 各位，大家好。今天我们来深入探讨MySQL事务的ACID特性：原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）和持久性（Durability），并剖析它们在MySQL底层的实现原理。 一、事务的基本概念 在开始之前，我们先简单回顾一下事务的概念。 事务是数据库管理系统执行过程中的一个逻辑单位，由一个有限的操作序列构成，要么全部执行成功（commit），要么全部不执行（rollback）。 二、原子性（Atomicity） 原子性意味着事务是一个不可分割的工作单元。事务中的所有操作要么全部成功，要么全部失败。不存在部分成功的情况。 实现原理：Undo Log MySQL通过Undo Log来实现原子性。Undo Log记录了事务修改数据之前的状态（即旧值）。当事务执行过程中发生错误或需要回滚时，MySQL会利用Undo Log将数据恢复到事务开始之前的状态，从而保证原子性。 Undo Log 的存储方式： Undo Log通常以顺序写的方式写入磁盘，以提高写入效率。当事务提交后，Undo Log 可 …

继续阅读“MySQL事务与并发之：`事务`的`ACID`特性：`原子性`、`一致性`、`隔离性`和`持久性`的底层实现。”

The post MySQL事务与并发之：`事务`的`ACID`特性：`原子性`、`一致性`、`隔离性`和`持久性`的底层实现。 first appeared on 智猿学院-前后端，数据库，人工智能，云计算等领域前沿技术讲座.

MySQL事务与并发：ACID特性的底层实现

各位，大家好。今天我们来深入探讨MySQL事务的ACID特性：原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）和持久性（Durability），并剖析它们在MySQL底层的实现原理。

一、事务的基本概念

在开始之前，我们先简单回顾一下事务的概念。 事务是数据库管理系统执行过程中的一个逻辑单位，由一个有限的操作序列构成，要么全部执行成功（commit），要么全部不执行（rollback）。

二、原子性（Atomicity）

原子性意味着事务是一个不可分割的工作单元。事务中的所有操作要么全部成功，要么全部失败。不存在部分成功的情况。

实现原理：Undo Log

MySQL通过Undo Log来实现原子性。Undo Log记录了事务修改数据之前的状态（即旧值）。当事务执行过程中发生错误或需要回滚时，MySQL会利用Undo Log将数据恢复到事务开始之前的状态，从而保证原子性。

Undo Log 的存储方式：

Undo Log通常以顺序写的方式写入磁盘，以提高写入效率。当事务提交后，Undo Log 可以被清理。

Undo Log 的回滚过程：

1. 在事务开始时，记录需要修改的数据的旧值到Undo Log。

2. 如果事务执行过程中发生错误，或者显式调用ROLLBACK语句，MySQL会读取Undo Log中的信息。

3. 根据Undo Log中的信息，将数据库中的数据恢复到事务开始之前的状态。

示例代码（模拟Undo Log）：

虽然我们无法直接查看MySQL底层的Undo Log，但我们可以通过代码模拟Undo Log的工作原理：

class UndoLog:
    def __init__(self):
        self.log = []

    def record(self, table_name, row_id, column_name, old_value):
        self.log.append({
            "table_name": table_name,
            "row_id": row_id,
            "column_name": column_name,
            "old_value": old_value
        })

    def rollback(self, db):
        for entry in reversed(self.log): # 逆序回滚
            table_name = entry["table_name"]
            row_id = entry["row_id"]
            column_name = entry["column_name"]
            old_value = entry["old_value"]

            # 模拟数据库操作：将数据恢复到旧值
            sql = f"UPDATE {table_name} SET {column_name} = '{old_value}' WHERE id = {row_id}"
            print(f"Executing rollback SQL: {sql}") # 打印回滚SQL
            db.execute(sql) # 模拟数据库执行SQL

        self.log = [] # 清空Undo Log

class MockDatabase:
    def __init__(self):
        self.data = {
            "users": {
                1: {"name": "Alice", "balance": 100},
                2: {"name": "Bob", "balance": 50}
            }
        }

    def execute(self, sql):
        print(f"Executing SQL: {sql}")
        # 模拟SQL执行，这里简化操作
        if "UPDATE users" in sql:
            parts = sql.split()
            row_id = int(parts[-1])
            new_balance = int(sql.split("=")[-1].split(" ")[0])
            self.data["users"][row_id]["balance"] = new_balance

    def get_data(self):
        return self.data

# 模拟事务
def transfer_money(db, user_id1, user_id2, amount, undo_log):
    try:
        # 1. 记录Alice的旧余额
        old_balance_alice = db.data["users"][user_id1]["balance"]
        undo_log.record("users", user_id1, "balance", old_balance_alice)

        # 2. Alice扣款
        new_balance_alice = old_balance_alice - amount
        sql_alice = f"UPDATE users SET balance = {new_balance_alice} WHERE id = {user_id1}"
        db.execute(sql_alice)

        # 3. 记录Bob的旧余额
        old_balance_bob = db.data["users"][user_id2]["balance"]
        undo_log.record("users", user_id2, "balance", old_balance_bob)

        # 4. Bob收款
        new_balance_bob = old_balance_bob + amount
        sql_bob = f"UPDATE users SET balance = {new_balance_bob} WHERE id = {user_id2}"
        db.execute(sql_bob)

        # 模拟提交事务
        print("Transaction committed.")

    except Exception as e:
        print(f"Transaction failed: {e}")
        print("Rolling back transaction...")
        undo_log.rollback(db)
        print("Transaction rolled back successfully.")

# 主程序
db = MockDatabase()
undo_log = UndoLog()

print("Initial data:", db.get_data())

# 模拟事务成功
transfer_money(db, 1, 2, 20, undo_log) # Alice转给Bob 20元

print("Data after successful transaction:", db.get_data())

# 模拟事务失败 (例如，转账金额大于Alice余额)
db = MockDatabase() # 重置数据库
undo_log = UndoLog()

print("nInitial data:", db.get_data())

try:
    transfer_money(db, 1, 2, 200, undo_log) # Alice转给Bob 200元 (余额不足)
except Exception as e:
    pass # 异常已经在transfer_money函数中处理

print("Data after failed transaction:", db.get_data())

代码解释：

UndoLog 类模拟Undo Log的功能，记录每次更新操作的旧值。

MockDatabase 类模拟数据库的操作，简化了SQL执行过程。

transfer_money 函数模拟一个转账事务，如果发生异常，则调用 undo_log.rollback() 回滚事务。

在模拟事务失败的例子中，由于Alice余额不足，转账失败，Undo Log会将Alice和Bob的余额恢复到事务开始之前的状态。

三、一致性（Consistency）

一致性是指事务执行前后，数据库的数据必须从一个一致性状态转换到另一个一致性状态。一致性是事务的最终目标。

实现原理：依赖于其他特性

一致性并非由MySQL单独实现，而是依赖于原子性、隔离性和持久性来保证。

原子性保证： 事务要么全部执行，要么全部不执行，避免了数据不完整的情况。

隔离性保证： 并发事务之间互不干扰，保证每个事务执行时都看到一个一致性的数据状态。

持久性保证： 事务提交后，数据变更会被永久保存，即使系统崩溃也不会丢失。

示例：

假设有一个银行转账事务，Alice向Bob转账100元。

一致性要求： 转账前后，Alice和Bob的账户总金额保持不变。

实现方式：

1. 原子性保证：要么Alice扣款和Bob收款都成功，要么都失败。

2. 隔离性保证：如果另一个事务也在修改Alice或Bob的账户，则该事务不会影响当前转账事务的一致性。

3. 持久性保证：转账完成后，Alice和Bob的账户余额会被永久保存。

四、隔离性（Isolation）

隔离性是指并发执行的事务之间应该相互隔离，互不干扰。每个事务都应该感觉不到其他事务的存在。

实现原理：锁机制和MVCC

MySQL通过锁机制和多版本并发控制（MVCC）来实现隔离性。

锁机制：

MySQL使用锁来控制并发访问。当一个事务需要修改数据时，它会先获取锁，防止其他事务同时修改该数据。

共享锁（Shared Lock）： 也称为读锁，允许事务读取数据。多个事务可以同时持有同一数据的共享锁。

排他锁（Exclusive Lock）： 也称为写锁，允许事务修改数据。只有一个事务可以持有同一数据的排他锁。

多版本并发控制（MVCC）：

MVCC是一种乐观并发控制机制，它允许事务读取旧版本的数据，而不需要等待其他事务释放锁。这样可以提高并发性能。

MVCC 的实现原理：

1. 版本链： 每行数据都存在多个版本，每个版本都有一个创建版本号和一个删除版本号。

2. Read View： 每个事务在启动时都会创建一个Read View，Read View包含当前活跃事务的列表。

3. 可见性判断： 当事务需要读取数据时，会根据Read View和数据的版本号来判断哪个版本的数据是可见的。

隔离级别：

MySQL支持四种隔离级别，分别是：

隔离级别 | 脏读 | 不可重复读 | 幻读
READ UNCOMMITTED | 是 | 是 | 是
READ COMMITTED | 否 | 是 | 是
REPEATABLE READ | 否 | 否 | 是
SERIALIZABLE | 否 | 否 | 否

READ UNCOMMITTED（读未提交）： 允许事务读取其他事务未提交的数据。隔离级别最低，并发性能最高，但可能导致脏读。

READ COMMITTED（读已提交）： 允许事务读取其他事务已提交的数据。可以避免脏读，但可能导致不可重复读。

REPEATABLE READ（可重复读）： 保证在同一个事务中多次读取同一数据的结果一致。可以避免脏读和不可重复读，但可能导致幻读。MySQL默认的隔离级别。

SERIALIZABLE（串行化）： 强制事务串行执行，隔离级别最高，并发性能最低，可以避免所有并发问题。

示例代码（模拟MVCC）：

import threading
import time

class MVCCDatabase:
    def __init__(self):
        self.data = {}  # {key: [(value, create_version, delete_version), ...]}
        self.version_counter = 0
        self.lock = threading.Lock()

    def get_next_version(self):
        with self.lock:
            self.version_counter += 1
            return self.version_counter

    def read(self, key, transaction_id):
        with self.lock:
            versions = self.data.get(key, [])
            visible_version = None

            for value, create_version, delete_version in reversed(versions):  # 从最新版本开始查找
                if create_version <= transaction_id and (delete_version is None or delete_version > transaction_id):
                    visible_version = value
                    break

            return visible_version

    def write(self, key, value, transaction_id):
        with self.lock:
            current_version = self.get_next_version()
            # 删除旧版本(标记为已删除)
            if key in self.data:
                latest_version = self.data[key][-1]
                self.data[key][-1] = (latest_version[0], latest_version[1], current_version)

            # 写入新版本
            self.data.setdefault(key, []).append((value, current_version, None))

    def delete(self, key, transaction_id):
        with self.lock:
            if key in self.data:
                current_version = self.get_next_version()
                latest_version = self.data[key][-1]
                self.data[key][-1] = (latest_version[0], latest_version[1], current_version)  # 标记为已删除

class Transaction:
    def __init__(self, db):
        self.db = db
        self.transaction_id = db.get_next_version()  # 分配事务ID
        print(f"Transaction {self.transaction_id} started.")

    def read(self, key):
        value = self.db.read(key, self.transaction_id)
        print(f"Transaction {self.transaction_id} read key '{key}': {value}")
        return value

    def write(self, key, value):
        self.db.write(key, value, self.transaction_id)
        print(f"Transaction {self.transaction_id} wrote key '{key}' with value {value}")

    def commit(self):
        print(f"Transaction {self.transaction_id} committed.")

    def rollback(self):
        print(f"Transaction {self.transaction_id} rolled back (not implemented in this example).") # 简化，不实际回滚

# 示例
db = MVCCDatabase()

# 事务1
tx1 = Transaction(db)
tx1.write("x", 10)
tx1.commit()

# 事务2 和 事务3 并发
def transaction_thread(db, tx_id):
    tx = Transaction(db)
    if tx_id == 2:
        print("nTransaction 2: Reads 'x' before Transaction 3 modifies it.")
        value_x = tx.read("x")
        time.sleep(1)  # 模拟一些操作
        tx.write("x", value_x + 5)
        tx.commit()
    elif tx_id == 3:
        time.sleep(0.5) # 稍微延迟，确保tx2先read
        print("nTransaction 3: Reads 'x' after Transaction 2 started but before Transaction 2 committed.")
        value_x = tx.read("x")
        tx.write("x", value_x * 2)
        tx.commit()

thread2 = threading.Thread(target=transaction_thread, args=(db, 2))
thread3 = threading.Thread(target=transaction_thread, args=(db, 3))

thread2.start()
thread3.start()

thread2.join()
thread3.join()

# 验证结果
tx_final = Transaction(db)
print(f"nFinal value of 'x': {tx_final.read('x')}")

代码解释：

MVCCDatabase 类模拟支持MVCC的数据库。

data 字典存储数据的多个版本，每个版本包含值、创建版本号和删除版本号。

read 方法根据事务ID和版本号找到可见的版本。

write 方法创建新的版本，并标记旧版本为已删除。

Transaction 类模拟事务，每个事务都有一个唯一的事务ID。

示例展示了两个并发事务对同一数据进行读写操作，MVCC保证了事务之间的隔离性。

五、持久性（Durability）

持久性是指事务提交后，对数据库的修改会被永久保存，即使系统崩溃也不会丢失。

实现原理：Redo Log

MySQL通过Redo Log来实现持久性。Redo Log记录了事务对数据的所有修改操作。当事务提交时，MySQL会先将Redo Log写入磁盘，然后再将数据写入磁盘。

Redo Log 的存储方式：

Redo Log通常以顺序写的方式写入磁盘，以提高写入效率。

Redo Log 的恢复过程：

1. 当系统崩溃重启时，MySQL会检查Redo Log。

2. 如果Redo Log中存在已提交但未写入磁盘的事务，MySQL会根据Redo Log中的信息，将这些事务的修改操作重新执行一遍，从而保证数据的持久性。

Write-Ahead Logging (WAL)：

MySQL使用Write-Ahead Logging (WAL) 策略，即先写Redo Log，再写数据。这样可以保证即使在数据写入磁盘之前系统崩溃，也可以通过Redo Log恢复数据。

示例代码（模拟Redo Log）：

import os

class RedoLog:
    def __init__(self, log_file="redo.log"):
        self.log_file = log_file
        self.log = []
        # 模拟崩溃后恢复
        if os.path.exists(self.log_file):
            with open(self.log_file, "r") as f:
                self.log = [line.strip() for line in f]
            print("Recovered from redo log.")

    def record(self, table_name, row_id, column_name, new_value):
        log_entry = f"UPDATE {table_name} SET {column_name} = '{new_value}' WHERE id = {row_id}"
        self.log.append(log_entry)
        self.flush_to_disk()

    def flush_to_disk(self):
        with open(self.log_file, "w") as f:
            for entry in self.log:
                f.write(entry + "n")

    def replay(self, db):
        for entry in self.log:
            print(f"Replaying: {entry}")
            db.execute(entry)
        print("Redo log replay complete.")

class MockDatabase:
    def __init__(self):
        self.data = {
            "users": {
                1: {"name": "Alice", "balance": 100},
                2: {"name": "Bob", "balance": 50}
            }
        }

    def execute(self, sql):
        print(f"Executing SQL: {sql}")
        if "UPDATE users" in sql:
            parts = sql.split()
            row_id = int(parts[-1])
            new_balance = int(sql.split("=")[-1].split(" ")[0])
            self.data["users"][row_id]["balance"] = new_balance

    def get_data(self):
        return self.data

# 模拟事务
def transfer_money(db, user_id1, user_id2, amount, redo_log):
    try:
        # 1. Alice扣款
        new_balance_alice = db.data["users"][user_id1]["balance"] - amount
        sql_alice = f"UPDATE users SET balance = {new_balance_alice} WHERE id = {user_id1}"
        db.execute(sql_alice)
        redo_log.record("users", user_id1, "balance", new_balance_alice) # 记录Redo Log

        # 2. Bob收款
        new_balance_bob = db.data["users"][user_id2]["balance"] + amount
        sql_bob = f"UPDATE users SET balance = {new_balance_bob} WHERE id = {user_id2}"
        db.execute(sql_bob)
        redo_log.record("users", user_id2, "balance", new_balance_bob) # 记录Redo Log

        # 模拟提交事务
        print("Transaction committed.")

    except Exception as e:
        print(f"Transaction failed: {e}")

# 主程序
# 模拟第一次运行
print("--- First Run ---")
db = MockDatabase()
redo_log = RedoLog()

print("Initial data:", db.get_data())

transfer_money(db, 1, 2, 20, redo_log)

print("Data after successful transaction:", db.get_data())

# 模拟系统崩溃
print("n--- Simulating System Crash ---")
del db # 模拟数据库实例丢失

# 模拟重启后恢复
print("n--- After Restart ---")
db = MockDatabase()
redo_log = RedoLog() # Redo Log 会自动从文件恢复

print("Data after recovery:", db.get_data())
redo_log.replay(db) # 重放Redo Log

print("Data after redo log replay:", db.get_data())

代码解释：

RedoLog 类模拟Redo Log的功能，记录每次更新操作。

flush_to_disk 方法将Redo Log写入磁盘。

replay 方法根据Redo Log中的信息，重新执行事务的修改操作。

示例展示了在系统崩溃重启后，如何通过Redo Log恢复数据。

六、总结与回顾

今天我们深入探讨了MySQL事务的ACID特性，并分析了它们的底层实现原理。原子性通过Undo Log实现，一致性依赖于其他特性保证，隔离性通过锁机制和MVCC实现，持久性通过Redo Log实现。 了解这些原理有助于我们更好地理解MySQL事务的工作方式，并在实际应用中更好地设计和优化数据库应用。

希望今天的分享对大家有所帮助。谢谢大家！