Python MySQL 错误回滚实战代码

这个例子模拟了一个经典的"转账"场景:A 给 B 转钱,如果在扣款后、收款前系统发生错误(比如断电、代码异常),必须让数据回到转账前的状态,保证钱不凭空消失。

环境准备

你需要安装 pymysql 库:

bash 复制代码
pip install pymysql

代码实现

python 复制代码
import pymysql
import sys

# 数据库配置(请根据你的实际情况修改)
DB_CONFIG = {
    'host': 'localhost',
    'user': 'root',
    'password': 'your_password',
    'database': 'test_db',
    'charset': 'utf8mb4'
}

def setup_database(cursor):
    """初始化测试表和数据"""
    try:
        cursor.execute("DROP TABLE IF EXISTS accounts")
        cursor.execute("""
            CREATE TABLE accounts (
                id INT AUTO_INCREMENT PRIMARY KEY,
                name VARCHAR(50),
                balance DECIMAL(10, 2)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """)
        # 插入初始数据:Alice有1000元,Bob有500元
        cursor.execute("INSERT INTO accounts (name, balance) VALUES ('Alice', 1000.00)")
        cursor.execute("INSERT INTO accounts (name, balance) VALUES ('Bob', 500.00)")
        print("✅ 数据库初始化完成:Alice=1000, Bob=500")
    except Exception as e:
        print(f"❌ 初始化失败: {e}")

def transfer_money_with_rollback(from_user, to_user, amount):
    """
    模拟转账业务,并在发生错误时回滚
    """
    connection = None
    try:
        # 1. 建立连接
        connection = pymysql.connect(**DB_CONFIG)
        
        # 2. 关键步骤:关闭自动提交,开启事务
        connection.autocommit(False)
        
        with connection.cursor() as cursor:
            # --- 步骤一:扣款 ---
            print(f"\n💰 正在从 {from_user} 扣除 {amount} 元...")
            sql_deduct = "UPDATE accounts SET balance = balance - %s WHERE name = %s"
            cursor.execute(sql_deduct, (amount, from_user))
            
            # 模拟查询扣款后的余额(仅为了演示,实际业务中可能不需要)
            cursor.execute("SELECT balance FROM accounts WHERE name = %s", (from_user,))
            result = cursor.fetchone()
            print(f"   👉 扣款后查询 {from_user} 余额: {result[0]} (此时数据在内存/Redo Log中,未永久落盘)")

            # --- 步骤二:模拟突发异常 ---
            # 比如:此时服务器断电、网络中断、或者代码逻辑错误
            print("⚠️  模拟系统崩溃:准备加款时发生除零错误!")
            error_simulation = 1 / 0  # 故意制造一个异常
            
            # --- 步骤三:加款(正常情况下会执行,但上面报错了就不会走到这) ---
            sql_add = "UPDATE accounts SET balance = balance + %s WHERE name = %s"
            cursor.execute(sql_add, (amount, to_user))
            
            # 3. 如果一切顺利,提交事务
            connection.commit()
            print("✅ 转账成功,事务已提交!")

    except Exception as e:
        print(f"\n❌ 发生严重错误: {e}")
        if connection:
            # 4. 核心:发生任何异常,回滚所有操作
            print("🔄 正在执行回滚操作 (ROLLBACK)...")
            connection.rollback()
            print("🛡️ 回滚成功!数据已恢复到事务开始前的状态。")
            
    finally:
        if connection:
            # 5. 恢复自动提交模式并关闭连接
            connection.autocommit(True)
            connection.close()

def check_final_balance():
    """检查最终结果"""
    conn = pymysql.connect(**DB_CONFIG)
    with conn.cursor() as cursor:
        cursor.execute("SELECT name, balance FROM accounts")
        results = cursor.fetchall()
        print("\n----- 最终账户余额 -----")
        for row in results:
            print(f"用户: {row[0]}, 余额: {row[1]}")
        print("------------------------")
        # 验证结果
        alice_balance = results[0][1] if results[0][0] == 'Alice' else results[1][1]
        bob_balance = results[0][1] if results[0][0] == 'Bob' else results[1][1]
        assert alice_balance == 1000, f"Alice余额错误!期望1000,实际{alice_balance}"
        assert bob_balance == 500, f"Bob余额错误!期望500,实际{bob_balance}"
        print("🎉 验证通过:数据一致,回滚生效!")
    conn.close()

if __name__ == "__main__":
    # 初始化
    conn_init = pymysql.connect(**DB_CONFIG)
    with conn_init.cursor() as cur:
        setup_database(cur)
        conn_init.commit()
    conn_init.close()

    # 执行带回滚的转账
    transfer_money_with_rollback('Alice', 'Bob', 200)

    # 检查最终数据是否正确回滚
    check_final_balance()

代码讲解重点(写进博客里):

  1. connection.autocommit(False):这是事务的开关。默认情况下 MySQL 是自动提交的(每句 SQL 都是一个事务),关掉它才能把多步操作打包成一个整体。
  2. try...except... 结构 :业务逻辑必须放在 try 里。
  3. connection.rollback() :这是"后悔药"。一旦进入 except 块,调用此方法会撤销从 autocommit(False) 之后的所有未提交更改。
  4. connection.commit():这是"确认键"。只有执行了这个,数据才真正写入磁盘(配合 Redo Log 和 Binlog)。
  5. Engine=InnoDB :注意建表时指定了引擎为 InnoDB。如果是 MyISAM 引擎,它不支持事务,rollback 会失效!这是面试常考点。

运行这个脚本,你会看到虽然执行了扣款 SQL,但因为中间报错触发了回滚,最后 Alice 的钱还是 1000,Bob 还是 500,完美保证了数据的一致性。

相关推荐
怣501 天前
MySQL多表连接:全外连接、交叉连接与结果集合并详解
数据库·sql
摘星编程1 天前
深入理解CANN ops-nn BatchNormalization算子:训练加速的关键技术
python
魔芋红茶1 天前
Python 项目版本控制
开发语言·python
wjhx1 天前
QT中对蓝牙权限的申请,整理一下
java·数据库·qt
lili-felicity1 天前
CANN批处理优化技巧:从动态批处理到流水线并行
人工智能·python
一个有梦有戏的人1 天前
Python3基础:进阶基础,筑牢编程底层能力
后端·python
冰暮流星1 天前
javascript之二重循环练习
开发语言·javascript·数据库
摘星编程1 天前
解析CANN ops-nn中的Transpose算子:张量维度变换的高效实现
python
Liekkas Kono1 天前
RapidOCR Python 贡献指南
开发语言·python·rapidocr
万岳科技系统开发1 天前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法