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 Nothing | BEGIN/COMMIT/ROLLBACK |
| 一致性 | 数据合法 | 约束检查 |
| 隔离性 | 并发不干扰 | 锁 + 隔离级别 |
| 持久性 | 提交不丢失 | Redo Log + Binlog |
✅ 最佳实践:
- 事务要简短
- 统一加锁顺序
- 使用连接池
- 选择合适的隔离级别
🎯 下一步:
通过练习题巩固本章所学知识。
