MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python与MySQL数据库事务处理详解

2021-04-132.5k 阅读

Python 与 MySQL 数据库事务处理基础概念

事务的定义与特性

在数据库操作中,事务(Transaction)是一个不可分割的工作逻辑单元。它由一系列数据库操作组成,这些操作要么全部成功执行,要么全部失败回滚,就好像这些操作是一个整体一样。事务具有 ACID 特性,这是理解和使用事务的基础。

  1. 原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部失败回滚。例如,在银行转账操作中,从账户 A 扣除金额和向账户 B 添加金额这两个操作必须作为一个原子操作。如果从账户 A 扣除金额成功,但向账户 B 添加金额失败,那么整个事务必须回滚,即账户 A 的金额应恢复到转账前的状态。
  2. 一致性(Consistency):事务执行前后,数据库的完整性约束不会被破坏。以银行转账为例,转账前后,系统中的总金额应该保持不变。如果数据库中有约束条件,如账户余额不能为负数,事务执行过程中也必须确保不会违反这个约束。
  3. 隔离性(Isolation):多个并发事务之间相互隔离,一个事务的执行不会被其他事务干扰。不同的隔离级别决定了事务之间相互隔离的程度。例如,在高隔离级别下,一个事务读取到的数据不会因为其他并发事务的修改而出现不一致的情况。
  4. 持久性(Durability):一旦事务提交成功,它对数据库所做的修改就会永久保存。即使系统发生崩溃或重启,这些修改也不会丢失。

MySQL 数据库中的事务支持

MySQL 是一种广泛使用的关系型数据库管理系统,它对事务提供了良好的支持。MySQL 的 InnoDB 存储引擎完全支持事务的 ACID 特性,而 MyISAM 存储引擎则不支持事务。在实际应用中,如果需要使用事务,通常会选择 InnoDB 存储引擎。 在 MySQL 中,事务的开始和结束可以通过以下 SQL 语句控制:

  1. START TRANSACTION:显式地开始一个事务。从这个语句之后的所有 SQL 语句都将被视为事务的一部分,直到遇到 COMMIT 或 ROLLBACK 语句。
  2. COMMIT:提交事务,将事务中所有的修改永久保存到数据库中。一旦执行 COMMIT,事务中的操作就不可回滚。
  3. ROLLBACK:回滚事务,撤销事务中所有未提交的修改,将数据库恢复到事务开始前的状态。

例如,以下是一个简单的 MySQL 事务示例:

START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
COMMIT;

在这个示例中,首先通过 START TRANSACTION 开始一个事务,然后执行两个 UPDATE 语句,分别从账户 1 扣除 100 元,向账户 2 添加 100 元。最后通过 COMMIT 提交事务,将这些修改永久保存到数据库中。如果在执行 COMMIT 之前发生错误,可以使用 ROLLBACK 回滚事务,撤销这些未提交的修改。

Python 连接 MySQL 数据库

使用 mysql - connector - python

在 Python 中,有多种库可以用于连接 MySQL 数据库,其中 mysql - connector - python 是官方推荐的库之一。它是一个纯 Python 实现的 MySQL 驱动,使用起来相对简单。 首先,需要安装 mysql - connector - python 库。可以使用 pip 进行安装:

pip install mysql - connector - python

安装完成后,就可以在 Python 代码中使用它来连接 MySQL 数据库。以下是一个简单的连接示例:

import mysql.connector

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="yourdatabase"
    )
    print("Connected to MySQL database")
except mysql.connector.Error as err:
    print(f"Error: {err}")

在这个示例中,使用 mysql.connector.connect() 方法来建立与 MySQL 数据库的连接。需要提供主机地址(host)、用户名(user)、密码(password)和数据库名(database)。如果连接成功,会打印 “Connected to MySQL database”;如果连接失败,会打印错误信息。

使用 pymysql

pymysql 也是一个常用的用于连接 MySQL 数据库的 Python 库。它的使用方式与 mysql - connector - python 类似。 首先,使用 pip 安装 pymysql

pip install pymysql

以下是使用 pymysql 连接 MySQL 数据库的示例代码:

import pymysql

try:
    mydb = pymysql.connect(
        host='localhost',
        user='yourusername',
        password='yourpassword',
        database='yourdatabase'
    )
    print("Connected to MySQL database")
except pymysql.Error as err:
    print(f"Error: {err}")

在这个示例中,使用 pymysql.connect() 方法来建立连接。同样需要提供主机、用户、密码和数据库等信息。

Python 中 MySQL 数据库事务处理实现

使用 mysql - connector - python 进行事务处理

在使用 mysql - connector - python 库连接 MySQL 数据库后,可以通过以下方式进行事务处理。

  1. 自动提交模式:默认情况下,mysql - connector - python 处于自动提交模式。这意味着每执行一条 SQL 语句,就会立即提交到数据库,相当于每个 SQL 语句本身就是一个独立的事务。例如:
import mysql.connector

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="yourdatabase"
    )
    mycursor = mydb.cursor()
    sql = "UPDATE accounts SET balance = balance - 100 WHERE account_id = 1"
    mycursor.execute(sql)
    mydb.commit()
    print(mycursor.rowcount, "record(s) affected")
except mysql.connector.Error as err:
    print(f"Error: {err}")
    mydb.rollback()
finally:
    mycursor.close()
    mydb.close()

在这个示例中,执行 UPDATE 语句后,通过 mydb.commit() 手动提交事务。如果执行过程中发生错误,通过 mydb.rollback() 回滚事务。

  1. 手动管理事务:可以通过设置 autocommit=False 来关闭自动提交模式,然后手动控制事务的开始、提交和回滚。例如:
import mysql.connector

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="yourdatabase"
    )
    mydb.autocommit = False
    mycursor = mydb.cursor()
    sql1 = "UPDATE accounts SET balance = balance - 100 WHERE account_id = 1"
    mycursor.execute(sql1)
    sql2 = "UPDATE accounts SET balance = balance + 100 WHERE account_id = 2"
    mycursor.execute(sql2)
    mydb.commit()
    print("Transaction committed successfully")
except mysql.connector.Error as err:
    print(f"Error: {err}")
    mydb.rollback()
    print("Transaction rolled back")
finally:
    mycursor.close()
    mydb.close()

在这个示例中,首先将 autocommit 设置为 False,然后执行两条 UPDATE 语句。如果没有发生错误,通过 mydb.commit() 提交事务;如果发生错误,通过 mydb.rollback() 回滚事务。

使用 pymysql 进行事务处理

使用 pymysql 进行事务处理的方式与 mysql - connector - python 类似。

  1. 自动提交模式:默认情况下,pymysql 也是自动提交模式。例如:
import pymysql

try:
    mydb = pymysql.connect(
        host='localhost',
        user='yourusername',
        password='yourpassword',
        database='yourdatabase'
    )
    mycursor = mydb.cursor()
    sql = "UPDATE accounts SET balance = balance - 100 WHERE account_id = 1"
    mycursor.execute(sql)
    mydb.commit()
    print(mycursor.rowcount, "record(s) affected")
except pymysql.Error as err:
    print(f"Error: {err}")
    mydb.rollback()
finally:
    mycursor.close()
    mydb.close()

在这个示例中,执行 UPDATE 语句后手动提交事务,如果发生错误则回滚事务。

  1. 手动管理事务:可以通过设置 autocommit = 0 来关闭自动提交模式,手动管理事务。例如:
import pymysql

try:
    mydb = pymysql.connect(
        host='localhost',
        user='yourusername',
        password='yourpassword',
        database='yourdatabase'
    )
    mydb.autocommit = 0
    mycursor = mydb.cursor()
    sql1 = "UPDATE accounts SET balance = balance - 100 WHERE account_id = 1"
    mycursor.execute(sql1)
    sql2 = "UPDATE accounts SET balance = balance + 100 WHERE account_id = 2"
    mycursor.execute(sql2)
    mydb.commit()
    print("Transaction committed successfully")
except pymysql.Error as err:
    print(f"Error: {err}")
    mydb.rollback()
    print("Transaction rolled back")
finally:
    mycursor.close()
    mydb.close()

在这个示例中,同样先关闭自动提交模式,然后执行两条 UPDATE 语句,根据执行结果决定是提交还是回滚事务。

事务隔离级别

事务隔离级别概述

事务隔离级别定义了一个事务与其他并发事务之间的隔离程度。不同的隔离级别可以在并发性和数据一致性之间提供不同的平衡。MySQL 支持以下几种事务隔离级别:

  1. 读未提交(Read Uncommitted):这是最低的隔离级别。在这种级别下,一个事务可以读取另一个未提交事务修改的数据。这可能会导致脏读(Dirty Read)问题,即读取到了其他事务尚未提交的数据,如果这些数据随后被回滚,那么读取的数据就是无效的。
  2. 读已提交(Read Committed):在这种隔离级别下,一个事务只能读取其他已提交事务修改的数据。这避免了脏读问题,但可能会出现不可重复读(Non - Repeatable Read)问题。不可重复读是指在一个事务内多次读取同一数据时,由于其他事务在期间提交了对该数据的修改,导致每次读取的结果不一致。
  3. 可重复读(Repeatable Read):这是 MySQL 的默认隔离级别。在可重复读隔离级别下,一个事务在整个执行过程中,多次读取同一数据时,读取的结果始终保持一致,即使其他事务在期间对该数据进行了修改并提交。但是,这种隔离级别可能会出现幻读(Phantom Read)问题。幻读是指在一个事务内多次执行相同的查询,由于其他事务在期间插入了新的数据,导致每次查询返回的结果集数量不一致。
  4. 串行化(Serializable):这是最高的隔离级别。在串行化隔离级别下,所有事务依次串行执行,避免了所有的并发问题,包括脏读、不可重复读和幻读。但是,这种隔离级别会严重影响系统的并发性能,因为所有事务必须排队执行。

在 Python 中设置事务隔离级别

  1. 使用 mysql - connector - python 设置隔离级别:可以在连接数据库时设置事务隔离级别。例如:
import mysql.connector

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="yourdatabase",
        isolation_level='READ - COMMITTED'
    )
    print("Connected to MySQL database with isolation level READ - COMMITTED")
except mysql.connector.Error as err:
    print(f"Error: {err}")

在这个示例中,通过 isolation_level 参数设置隔离级别为 READ - COMMITTED。其他可用的隔离级别字符串为 'READ - UNCOMMITTED''REPEATABLE - READ''SERIALIZABLE'

  1. 使用 pymysql 设置隔离级别:在 pymysql 中,可以在连接数据库后通过执行 SQL 语句来设置隔离级别。例如:
import pymysql

try:
    mydb = pymysql.connect(
        host='localhost',
        user='yourusername',
        password='yourpassword',
        database='yourdatabase'
    )
    mycursor = mydb.cursor()
    mycursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED")
    print("Set isolation level to READ COMMITTED")
except pymysql.Error as err:
    print(f"Error: {err}")
finally:
    mycursor.close()
    mydb.close()

在这个示例中,通过执行 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED SQL 语句来设置隔离级别为 READ COMMITTED。同样,可以将 READ COMMITTED 替换为其他隔离级别,如 READ UNCOMMITTEDREPEATABLE READSERIALIZABLE

事务处理中的异常处理

常见异常类型及处理

在 Python 中进行 MySQL 数据库事务处理时,可能会遇到各种异常。以下是一些常见的异常类型及其处理方法:

  1. 数据库连接异常:如 mysql.connector.Errormysql - connector - python 库)或 pymysql.Errorpymysql 库),这通常是由于连接信息错误(如主机地址、用户名、密码错误)或数据库服务未启动等原因导致的。在前面的连接示例中,已经展示了如何捕获并处理这类异常。例如:
import mysql.connector

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="yourdatabase"
    )
except mysql.connector.Error as err:
    print(f"Connection error: {err}")
  1. SQL 语法错误:当执行的 SQL 语句存在语法错误时,会抛出异常。例如,使用 mysql - connector - python 库时:
import mysql.connector

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="yourdatabase"
    )
    mycursor = mydb.cursor()
    sql = "UPDATE accounts SET balance = balance - 100 WHERE account_id = 1; INVALID SQL STATEMENT"
    mycursor.execute(sql)
except mysql.connector.Error as err:
    print(f"SQL syntax error: {err}")
    mydb.rollback()
finally:
    mycursor.close()
    mydb.close()

在这个示例中,故意在 UPDATE 语句后添加了无效的 SQL 语句,执行时会捕获到 mysql.connector.Error 异常,并回滚事务。

  1. 数据完整性异常:当执行的操作违反了数据库的完整性约束(如唯一约束、外键约束等)时,会抛出异常。例如,插入一条违反唯一约束的数据:
import mysql.connector

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="yourdatabase"
    )
    mycursor = mydb.cursor()
    sql = "INSERT INTO users (username, email) VALUES ('testuser', 'test@example.com')"
    mycursor.execute(sql)
    sql = "INSERT INTO users (username, email) VALUES ('testuser', 'test2@example.com')"  # 违反唯一约束
    mycursor.execute(sql)
    mydb.commit()
except mysql.connector.Error as err:
    print(f"Data integrity error: {err}")
    mydb.rollback()
finally:
    mycursor.close()
    mydb.close()

在这个示例中,尝试插入两条具有相同 username 的记录,第二次插入会违反唯一约束,从而捕获异常并回滚事务。

异常处理策略

  1. 记录异常信息:在捕获异常时,将异常信息记录下来,以便后续排查问题。可以使用 Python 的 logging 模块来记录日志。例如:
import mysql.connector
import logging

logging.basicConfig(level = logging.ERROR)

try:
    mydb = mysql.connector.connect(
        host="localhost",
        user="yourusername",
        password="yourpassword",
        database="yourdatabase"
    )
    mycursor = mydb.cursor()
    sql = "UPDATE accounts SET balance = balance - 100 WHERE account_id = 1; INVALID SQL STATEMENT"
    mycursor.execute(sql)
except mysql.connector.Error as err:
    logging.error(f"SQL syntax error: {err}")
    mydb.rollback()
finally:
    mycursor.close()
    mydb.close()
  1. 向上抛出异常:在某些情况下,可能需要将捕获到的异常向上抛出,让上层调用函数来处理。例如:
import mysql.connector

def perform_transaction():
    try:
        mydb = mysql.connector.connect(
            host="localhost",
            user="yourusername",
            password="yourpassword",
            database="yourdatabase"
        )
        mycursor = mydb.cursor()
        sql = "UPDATE accounts SET balance = balance - 100 WHERE account_id = 1; INVALID SQL STATEMENT"
        mycursor.execute(sql)
        mydb.commit()
    except mysql.connector.Error as err:
        mydb.rollback()
        raise  # 向上抛出异常
    finally:
        mycursor.close()
        mydb.close()

try:
    perform_transaction()
except mysql.connector.Error as err:
    print(f"Error in outer scope: {err}")

在这个示例中,perform_transaction 函数捕获到异常后回滚事务并向上抛出异常,外层代码捕获并处理该异常。

事务处理在实际项目中的应用场景

金融交易场景

在金融领域,事务处理是确保数据一致性和完整性的关键。例如,在银行转账操作中,涉及到两个账户的金额变动,必须作为一个事务来处理。以下是一个简化的 Python 代码示例,使用 mysql - connector - python 库模拟银行转账:

import mysql.connector

def transfer_funds(from_account, to_account, amount):
    try:
        mydb = mysql.connector.connect(
            host="localhost",
            user="yourusername",
            password="yourpassword",
            database="bank"
        )
        mydb.autocommit = False
        mycursor = mydb.cursor()
        sql1 = "UPDATE accounts SET balance = balance - %s WHERE account_id = %s"
        val1 = (amount, from_account)
        mycursor.execute(sql1, val1)
        sql2 = "UPDATE accounts SET balance = balance + %s WHERE account_id = %s"
        val2 = (amount, to_account)
        mycursor.execute(sql2, val2)
        mydb.commit()
        print("Funds transferred successfully")
    except mysql.connector.Error as err:
        print(f"Error: {err}")
        mydb.rollback()
        print("Transaction rolled back")
    finally:
        mycursor.close()
        mydb.close()

transfer_funds(1, 2, 100)

在这个示例中,transfer_funds 函数从 from_account 扣除金额,向 to_account 添加金额,整个过程作为一个事务处理。如果其中任何一步出现错误,事务将回滚,确保数据的一致性。

电子商务场景

在电子商务系统中,订单处理通常涉及多个数据库操作,如创建订单记录、更新库存、扣除用户账户余额等。这些操作必须在一个事务内完成,以保证数据的完整性。以下是一个简单的 Python 代码示例,使用 pymysql 库模拟订单处理:

import pymysql

def process_order(user_id, product_id, quantity):
    try:
        mydb = pymysql.connect(
            host='localhost',
            user='yourusername',
            password='yourpassword',
            database='ecommerce'
        )
        mydb.autocommit = 0
        mycursor = mydb.cursor()
        # 创建订单记录
        sql1 = "INSERT INTO orders (user_id, product_id, quantity) VALUES (%s, %s, %s)"
        val1 = (user_id, product_id, quantity)
        mycursor.execute(sql1, val1)
        # 更新库存
        sql2 = "UPDATE products SET stock = stock - %s WHERE product_id = %s"
        val2 = (quantity, product_id)
        mycursor.execute(sql2, val2)
        # 扣除用户账户余额(假设每个产品价格为 100)
        price = 100 * quantity
        sql3 = "UPDATE users SET balance = balance - %s WHERE user_id = %s"
        val3 = (price, user_id)
        mycursor.execute(sql3, val3)
        mydb.commit()
        print("Order processed successfully")
    except pymysql.Error as err:
        print(f"Error: {err}")
        mydb.rollback()
        print("Transaction rolled back")
    finally:
        mycursor.close()
        mydb.close()

process_order(1, 1, 2)

在这个示例中,process_order 函数执行了创建订单、更新库存和扣除用户余额等操作,这些操作在一个事务内完成。如果任何一步失败,事务将回滚,确保数据的一致性和完整性。

总结事务处理的要点与注意事项

要点总结

  1. 理解事务的 ACID 特性:原子性、一致性、隔离性和持久性是事务的核心特性。在编写事务处理代码时,要确保事务满足这些特性,以保证数据的正确性和可靠性。
  2. 选择合适的数据库驱动:根据项目需求和个人偏好,选择 mysql - connector - pythonpymysql 等合适的数据库驱动来连接 MySQL 数据库,并熟悉其事务处理的方法和接口。
  3. 控制事务边界:明确事务的开始和结束,合理使用 START TRANSACTION(在 Python 中通过关闭自动提交模式来实现类似效果)、COMMITROLLBACK 语句。确保事务内的所有操作要么全部成功提交,要么全部失败回滚。
  4. 设置合适的隔离级别:根据应用场景的并发需求和数据一致性要求,选择合适的事务隔离级别。不同的隔离级别在并发性和数据一致性之间有不同的平衡,需要权衡选择。

注意事项

  1. 异常处理:在事务处理过程中,要充分考虑可能出现的各种异常,如数据库连接异常、SQL 语法错误、数据完整性异常等。合理地捕获和处理这些异常,确保事务能够正确回滚,避免数据处于不一致状态。
  2. 性能问题:事务处理可能会对系统性能产生影响,特别是在高并发场景下。过高的隔离级别可能会导致锁竞争加剧,降低系统的并发性能。因此,在选择隔离级别和设计事务逻辑时,要综合考虑性能和数据一致性的需求。
  3. 事务嵌套:尽量避免事务嵌套,因为事务嵌套会增加事务管理的复杂性,并且可能导致死锁等问题。如果确实需要嵌套事务,要谨慎设计事务的提交和回滚逻辑。
  4. 资源管理:在事务处理完成后,要及时关闭数据库连接和游标等资源,避免资源泄漏。可以使用 try - finally 语句块来确保资源的正确关闭。

通过深入理解和掌握 Python 与 MySQL 数据库事务处理的相关知识,并在实际项目中合理应用,可以有效地保证数据的一致性和完整性,提高系统的可靠性和稳定性。