Python 如何使用 MySQL 的事务

后端开发中,转账、订单创建、库存扣减这类业务,必须保证多条数据库操作全部成功或者全部回滚,这就是事务的价值。本文基于 pymysql 讲解 Python 下 MySQL 事务完整用法,包含原理、代码示例、异常处理、常见坑与最佳实践。

环境准备

安装 pymysql

bash 复制代码
pip install pymysql

注意:MySQL 只有 InnoDB 引擎支持事务,MyISAM 不支持事务。

测试表准备

sql 复制代码
CREATE TABLE `account` (
  `id` INT PRIMARY KEY AUTO_INCREMENT,
  `username` VARCHAR(32) NOT NULL,
  `balance` DECIMAL(12,2) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO account(username,balance) VALUES 
('zhangsan',1000.00),
('lisi',1000.00);

什么是事务

事务是一组SQL操作的逻辑单元:

  • 全部执行成功,执行 commit() 提交,修改永久生效;
  • 任意步骤失败,执行 rollback() 回滚,撤销这一组所有修改。

ACID四大特性

  • 原子性(Atomicity):事务内操作不可分割,全部成功或全部失败。
  • 一致性(Consistency):事务执行前后业务数据保持合法状态。
  • 隔离性(Isolation):多个事务之间互相隔离,受事务隔离级别控制。
  • 持久性(Durability):commit提交之后,修改永久保存,数据库宕机也不会丢失。

pymysql事务核心要点

  1. pymysql 默认 autocommit=True,每条SQL执行后立即提交,此时没有事务效果
  2. 使用事务必须关闭自动提交:conn.autocommit(False)
  3. 两个关键方法:
    • conn.commit():提交事务
    • conn.rollback():回滚事务
  4. 同一个事务必须使用同一个connection连接对象,不能换连接。

示例1:转账业务(try‑except标准写法)

张三转账200元给李四,模拟业务异常回滚。

python 复制代码
import pymysql

def transfer():
    conn = pymysql.connect(
        host="127.0.0.1",
        port=3306,
        user="root",
        password="xxx",
        database="test_db",
        charset="utf8mb4"
    )
    # 关闭自动提交,开启事务模式
    conn.autocommit(False)
    cursor = conn.cursor(pymysql.cursors.DictCursor)

    try:
        # 1. 张三扣200
        cursor.execute("UPDATE account SET balance = balance - %s WHERE username = %s", (200, "zhangsan"))
        # 2. 模拟异常,触发回滚
        # 1 / 0
        # 3. 李四加200
        cursor.execute("UPDATE account SET balance = balance + %s WHERE username = %s", (200, "lisi"))

        # 全部成功,提交事务
        conn.commit()
        print("事务提交成功")
    except Exception as e:
        # 出现任何异常,回滚所有变更
        conn.rollback()
        print(f"事务回滚,异常:{e}")
    finally:
        cursor.close()
        conn.close()

if __name__ == "__main__":
    transfer()

打开代码中 1/0 模拟报错,你会发现两条update全部失效,不会出现张三扣钱、李四没加钱的数据错乱。

示例2:with上下文管理器用法

pymysql 的 connection 支持 with,退出上下文如果没有commit会自动回滚。

python 复制代码
import pymysql

def transfer_with():
    conn = pymysql.connect(
        host="127.0.0.1",
        user="root",
        password="xxx",
        database="test_db",
        autocommit=False,
        charset="utf8mb4"
    )
    try:
        with conn.cursor(pymysql.cursors.DictCursor) as cur:
            cur.execute("UPDATE account SET balance=balance-%s WHERE username=%s", (100, "zhangsan"))
            cur.execute("UPDATE account SET balance=balance+%s WHERE username=%s", (100, "lisi"))
        # with游标结束不会自动commit,需要手动提交
        conn.commit()
        print("提交成功")
    except Exception as e:
        conn.rollback()
        print(f"回滚:{e}")
    finally:
        conn.close()

⚠️重要提醒:with cursor() 只是管理游标,不会自动commit/rollback,事务的提交回滚仍然由connection控制。

示例3:嵌套事务?MySQL没有真正嵌套事务

MySQL InnoDB 不支持真正嵌套事务,可以使用保存点 savepoint实现局部回滚。

python 复制代码
def savepoint_demo():
    conn = pymysql.connect(host="127.0.0.1", user="root", password="xxx", database="test_db", autocommit=False)
    cur = conn.cursor()
    try:
        cur.execute("UPDATE account SET balance=balance-50 WHERE username='zhangsan'")
        # 设置保存点
        cur.execute("SAVEPOINT sp1")

        cur.execute("UPDATE account SET balance=balance+50 WHERE username='lisi'")
        # 回滚到保存点sp1,只撤销后面的操作,前面的保留
        cur.execute("ROLLBACK TO SAVEPOINT sp1")

        conn.commit()
    except Exception as e:
        conn.rollback()
        print(e)
    finally:
        cur.close()
        conn.close()

常见踩坑清单

  1. autocommit忘记关闭

    autocommit=True 时每一条SQL直接生效,commit/rollback完全无效,事务失效。

  2. 事务中途更换connection对象

    同一个事务的多条SQL必须在同一个连接,换连接等于新开另一个事务。

  3. 查询不会加锁,update/delete才会产生事务变更

    普通select不会修改数据;SELECT ... FOR UPDATE 会开启行锁,用于并发扣库存场景。

  4. 异常捕获后忘记rollback

    如果发生异常不执行rollback,未提交的事务会一直挂起,占用数据库锁资源,产生锁等待。

  5. MyISAM引擎使用事务

    MyISAM不支持事务,commit/rollback调用无效果,建表必须指定 ENGINE=InnoDB

  6. 长事务

    事务不要长时间不commit/rollback,长事务会大量占用回滚段、锁资源,严重影响数据库性能。

结合SELECT FOR UPDATE(并发扣库存场景)

悲观锁示例,防止并发超卖

python 复制代码
def deduct_stock():
    conn = pymysql.connect(host="127.0.0.1",user="root",password="xxx",database="test_db",autocommit=False)
    cur = conn.cursor()
    try:
        # for update 行锁,其他事务会阻塞此处
        cur.execute("SELECT balance FROM account WHERE username='zhangsan' FOR UPDATE")
        row = cur.fetchone()
        if row[0] >= 100:
            cur.execute("UPDATE account SET balance=balance-100 WHERE username='zhangsan'")
        conn.commit()
    except Exception as e:
        conn.rollback()
        print(e)
    finally:
        cur.close()
        conn.close()

最佳实践总结

  1. 业务涉及多写操作,必须使用事务;设置 autocommit=False
  2. 使用 try‑except‑finally,异常分支必须执行 rollback(),最后关闭连接。
  3. 事务粒度尽量小,避免长事务,执行完尽快commit或rollback释放锁。
  4. 并发场景需要锁时,合理使用 SELECT ... FOR UPDATE 悲观锁,或业务层乐观锁。
  5. 确认表引擎为InnoDB。
  6. 一个事务全程复用同一个数据库连接对象。

如果你使用 SQLAlchemy ORM,框架会封装事务逻辑,但底层依然是MySQL事务机制。

相关推荐
阿kun要赚马内8 小时前
MCP(Model Context Protocol,模型上下文协议)
人工智能·python
来了就未晚9 小时前
Python基础 学习代码存储与命名规范
开发语言·python·学习
夜雪一千9 小时前
MySQL 各种数据同步方案全梳理:从简单复制到异构同步实战
数据库·mysql
落魄大学生之流水线上谋生计9 小时前
LangGraph 基本用法指南
java·windows·python·langchain
ruleslol9 小时前
MySQL: OR查询为什么会导致索引失效
mysql
华研前沿标杆游学10 小时前
宇树科技对外开放参访?机器人企业参访体验与行业核心价值解析
python
今天会营业10 小时前
Miniconda安装
python·机器学习
benchmark_cc10 小时前
Python量化实战:如何检测并剔除历史数据中的“闪崩/乌龙指”异常价格点?
开发语言·人工智能·python·量化·quantdash·量化数据源
进阶的小木桩10 小时前
mysql 安装问题解决方案记录
数据库·mysql
伞伞悦读11 小时前
【第14期】Python 基础语法入门:缩进、注释、标识符、代码块和解释器执行
python