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事务机制。

相关推荐
今儿敲了吗5 小时前
Python ——第三方包
笔记·python
麒麟水手5 小时前
国产银河麒麟系统开发实录:跑通Streamlit,我踩过的6个坑
python
麒麟水手5 小时前
麒麟系统部署检查清单:上线前30分钟,逐项自查这4类问题
python
xiaoye-duck6 小时前
MySQL 表约束全解:从基础约束到外键关联规则
数据库·mysql
用户0332126663676 小时前
使用 Python 在 PDF 中添加或删除数字签名
python
冷凝娇7 小时前
【MySQL】2026总结(二)
数据库·mysql
代码方舟7 小时前
零信任架构实战:基于天远柠檬查出险-登记证API构建自动化车辆履约评估网关
人工智能·python·架构·自动化
估值探索者8 小时前
【Python实时盯盘与预警 #08】成交额突然放大2倍?Python窗口比较抓异动
java·开发语言·python
yk 坤帝8 小时前
用Python实现发送《自动周报》实战脚本
开发语言·python