导航菜单

ACID 事务详解

在用户认证系统中,数据一致性至关重要。ACID 是数据库事务的四个核心特性。

什么是 ACID?

┌─────────────────────────────────────────────────┐
│              ACID 事务特性                       │
├───────────┬─────────────────────────────────────┤
│ Atomicity │ 原子性 - 要么全部完成,要么全部失败   │
├───────────┼─────────────────────────────────────┤
│Consistency│ 一致性 - 事务前后数据状态都合法      │
├───────────┼─────────────────────────────────────┤
│ Isolation │ 隔离性 - 并发事务互不干扰            │
├───────────┼─────────────────────────────────────┤
│ Durability│ 持久性 - 提交后永久保存              │
└───────────┴─────────────────────────────────────┘

A - Atomicity 原子性

场景:用户转账

# ❌ 错误示例:没有原子性保证
def transfer_wrong(user_a, user_b, amount):
    cursor = db.cursor()
    # A 扣钱
    cursor.execute(
        'UPDATE users SET balance = balance - %s WHERE id = %s',
        (amount, user_a)
    )
    # 如果这里系统崩溃...
    # B 永远收不到钱!
    cursor.execute(
        'UPDATE users SET balance = balance + %s WHERE id = %s',
        (amount, user_b)
    )
    db.commit()

# ✅ 正确示例:使用事务
def transfer_correct(user_a, user_b, amount):
    cursor = db.cursor()
    try:
        cursor.execute('BEGIN')  # 开启事务
        cursor.execute(
            'UPDATE users SET balance = balance - %s WHERE id = %s',
            (amount, user_a)
        )
        cursor.execute(
            'UPDATE users SET balance = balance + %s WHERE id = %s',
            (amount, user_b)
        )
        db.commit()  # 提交事务
    except Exception as e:
        db.rollback()  # 回滚
        raise e

原子性保证

事务执行过程:

BEGIN
  ├─ 操作 1:A 账户 -100  ✓
  ├─ 操作 2:B 账户 +100  ✓
  └─ COMMIT               ← 全部提交

如果任何一步失败:
BEGIN
  ├─ 操作 1:A 账户 -100  ✓
  ├─ 操作 2:B 账户 +100  ✗
  └─ ROLLBACK             ← 全部回滚,A 账户恢复原状

C - Consistency 一致性

约束检查

-- 创建约束
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    balance DECIMAL(10, 2) NOT NULL CHECK (balance >= 0),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

一致性示例

事务前状态:
用户 A: balance = 500
用户 B: balance = 300
总计:800

事务:A 转账 100 给 B

事务后状态:
用户 A: balance = 400
用户 B: balance = 400
总计:800  ← 守恒!

违反一致性的情况

# ❌ 错误:没有检查余额
def transfer_no_check(user_a, user_b, amount):
    cursor.execute(
        'UPDATE users SET balance = balance - %s WHERE id = %s',
        (amount, user_a)
    )
    # A 的余额可能变成负数!
    db.commit()

# ✅ 正确:检查余额
def transfer_with_check(user_a, user_b, amount):
    cursor = db.cursor()
    # 先检查余额
    cursor.execute('SELECT balance FROM users WHERE id = %s FOR UPDATE', (user_a,))
    balance = cursor.fetchone()['balance']

    if balance < amount:
        raise ValueError('余额不足')

    cursor.execute('BEGIN')
    cursor.execute(
        'UPDATE users SET balance = balance - %s WHERE id = %s',
        (amount, user_a)
    )
    cursor.execute(
        'UPDATE users SET balance = balance + %s WHERE id = %s',
        (amount, user_b)
    )
    db.commit()

I - Isolation 隔离性

并发问题

场景:A 账户有 500 元,同时发起两笔转账

时间线:
T1: 事务 1 读取 A 余额 = 500
T2: 事务 2 读取 A 余额 = 500   ← 脏读
T3: 事务 1 转账 100 给 B,A = 400
T4: 事务 2 转账 200 给 C,A = 300  ← 错误!应该是 100

结果:A 的余额计算错误

隔离级别

隔离级别脏读不可重复读幻读性能
READ UNCOMMITTED可能可能可能最高
READ COMMITTED避免可能可能
REPEATABLE READ避免避免可能
SERIALIZABLE避免避免避免最低

MySQL 中的使用

-- 查看当前隔离级别
SELECT @@transaction_isolation;

-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 使用行锁防止并发
SELECT * FROM users WHERE id = 123 FOR UPDATE;

D - Durability 持久性

持久化机制

MySQL InnoDB 的持久化保证:

1. Redo Log(重做日志)
   ├─ 记录所有修改
   ├─ 先写日志,再写数据
   └─ 崩溃后可恢复

2. Binlog(二进制日志)
   ├─ 记录 SQL 语句
   ├─ 用于主从复制
   └─ 用于时间点恢复

3. checkpoint(检查点)
   ├─ 定期刷盘
   └─ 加速恢复

配置建议

# my.cnf - InnoDB 配置

[mysqld]
# 每次提交都刷盘(最安全)
innodb_flush_log_at_trx_commit = 1

# 每秒刷盘(性能更好,可能丢失 1 秒数据)
innodb_flush_log_at_trx_commit = 2

# 双 1 配置(推荐生产环境)
sync_binlog = 1
innodb_flush_log_at_trx_commit = 1

事务最佳实践

1. 保持事务简短

# ❌ 错误:事务太长
def long_transaction():
    cursor.execute('BEGIN')
    cursor.execute('SELECT * FROM users WHERE id = 1')
    # 在这里调用外部 API... 耗时 5 秒
    call_external_api()
    cursor.execute('UPDATE users SET ...')
    db.commit()

# ✅ 正确:减少事务持有时间
def short_transaction():
    # 先做外部调用
    result = call_external_api()
    # 再开启事务
    cursor.execute('BEGIN')
    cursor.execute('UPDATE users SET ...')
    db.commit()

2. 避免死锁

# ❌ 可能导致死锁
# 事务 1: UPDATE users ... UPDATE orders ...
# 事务 2: UPDATE orders ... UPDATE users ...

# ✅ 统一加锁顺序
# 事务 1: UPDATE users ... UPDATE orders ...
# 事务 2: UPDATE users ... UPDATE orders ...

3. 使用连接池

from dbutils.pooled_db import PooledDB

db_pool = PooledDB(
    creator=pymysql,
    host='localhost',
    user='root',
    database='api_platform',
    maxconnections=20,
    autocommit=False  # 关闭自动提交
)

def use_transaction():
    conn = db_pool.connection()
    try:
        cursor = conn.cursor()
        cursor.execute('BEGIN')
        # ... 业务逻辑
        conn.commit()
    except Exception as e:
        conn.rollback()
        raise e
    finally:
        conn.close()

本节小结

✅ ACID 回顾:

特性保证实现方式
原子性All or NothingBEGIN/COMMIT/ROLLBACK
一致性数据合法约束检查
隔离性并发不干扰锁 + 隔离级别
持久性提交不丢失Redo Log + Binlog

✅ 最佳实践:

  • 事务要简短
  • 统一加锁顺序
  • 使用连接池
  • 选择合适的隔离级别

🎯 下一步

通过练习题巩固本章所学知识。

搜索