百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Python数据库编程教程:第 6 章 数据库事务管理与数据备份恢复

itomcoil 2025-08-30 23:58 5 浏览

在实际数据库开发中,事务管理是保障数据一致性的核心机制(如转账时 “扣款” 与 “到账” 需同时成功或同时失败),而数据备份恢复是应对数据丢失的重要保障。本章基于python_db数据库,系统讲解 Python 中的事务控制方法、事务 ACID 特性,以及数据备份(导出)与恢复(导入)的实操流程,通过规整格式和案例演示,帮助你掌握数据库稳定性保障的关键技能。

6.1 数据库事务的基础概念

6.1.1 事务的定义

事务(Transaction)是数据库中一组不可分割的操作单元,这组操作要么全部执行成功,要么全部执行失败,不存在 “部分成功” 的情况。例如:

  • 电商下单:需同时执行 “扣减库存” 和 “生成订单” 两个操作,若其中一个失败,整个流程需回退。
  • 银行转账:需同时执行 “转出账户扣款” 和 “转入账户到账”,若任一操作失败,需恢复初始状态。

6.1.2 事务的 ACID 特性(核心原则)

事务必须满足 ACID 四个特性,才能确保数据一致性,具体如下表所示:

特性

英文全称

核心含义

示例场景(转账)

原子性

Atomicity

事务中的操作要么全成,要么全败,不可拆分

若 “扣款” 成功但 “到账” 失败,需回退 “扣款” 操作,恢复账户初始余额

一致性

Consistency

事务执行前后,数据库数据需符合预设规则(如约束、业务逻辑)

转账前总余额 = 转出账户余额 + 转入账户余额;转账后总余额不变

隔离性

Isolation

多个事务同时执行时,彼此互不干扰,每个事务看到的数据是独立的

事务 A 执行转账时,事务 B 查询账户余额,只能看到事务 A 执行前或执行后的完整数据,看不到中间状态

持久性

Durability

事务执行成功并提交后,数据修改会永久保存到数据库,即使系统崩溃也不丢失

转账成功并提交后,即使数据库服务重启,账户余额修改仍有效

6.1.3 MySQL 的事务默认行为

  • MySQL 中,InnoDB存储引擎支持事务(MyISAM不支持),而python_db数据库的users表默认使用InnoDB。
  • 默认情况下,MySQL 开启 “自动提交” 模式(autocommit = ON),即每条INSERT/UPDATE/DELETE语句会自动作为一个独立事务提交。
  • 实际开发中,需关闭自动提交,手动控制事务(如多步操作组成一个事务)。

6.2 Python 中的事务控制流程

Python 通过数据库连接对象(connection)实现事务控制,核心步骤为 “关闭自动提交→执行事务操作→提交 / 回滚事务”,具体流程如下:

6.2.1 事务控制核心方法

方法名

作用

connection.autocommit = False

关闭自动提交模式(开启手动事务控制的前提)

connection.commit()

提交事务:将事务中的所有操作永久保存到数据库

connection.rollback()

回滚事务:撤销事务中的所有操作,恢复到事务执行前的状态

connection.is_connected()

检查连接是否有效(事务操作前常用)

6.2.2 单事务多操作案例(转账模拟)

以 “用户 A 向用户 B 转账 100 元” 为例,需执行两步操作:

  1. 用户 A(name='张三更新')余额减 100
  1. 用户 B(name='李四')余额加 100

若任一操作失败,需回滚整个事务,确保数据一致性。

前提:为users表新增balance字段(余额)

# 执行一次,为users表新增余额字段(默认值1000)

def add_balance_field():

connection = None

try:

connection = mysql.connector.connect(

host='localhost',

user='root',

password='your_password',

database='python_db'

)

if connection.is_connected():

cursor = connection.cursor()

cursor.execute("ALTER TABLE users ADD COLUMN balance INT DEFAULT 1000")

connection.commit()

print("已为users表新增balance字段(默认余额1000)")

except Error as e:

print(f"新增字段出错:{e}")

finally:

if connection and connection.is_connected():

cursor.close()

connection.close()

add_balance_field()

Python 事务控制实现(转账)

import mysql.connector

from mysql.connector import Error

def transfer_money(from_name, to_name, amount):

"""

转账事务函数

:param from_name: 转出用户姓名

:param to_name: 转入用户姓名

:param amount: 转账金额(正数)

"""

connection = None

try:

# 1. 数据库连接并关闭自动提交

connection = mysql.connector.connect(

host='localhost',

user='root',

password='your_password',

database='python_db'

)

connection.autocommit = False # 关闭自动提交,开启手动事务

cursor = connection.cursor()

# 2. 转账前查询:确认转出用户余额足够

print("=== 转账前账户状态 ===")

# 查询转出用户

cursor.execute("SELECT name, balance FROM users WHERE name = %s", (from_name,))

from_user = cursor.fetchone()

# 查询转入用户

cursor.execute("SELECT name, balance FROM users WHERE name = %s", (to_name,))

to_user = cursor.fetchone()

# 验证用户是否存在、余额是否足够

if not from_user:

raise Exception(f"转出用户'{from_name}'不存在")

if not to_user:

raise Exception(f"转入用户'{to_name}'不存在")

if from_user[1] < amount:

raise Exception(f"转出用户'{from_name}'余额不足(当前余额:{from_user[1]})")

# 打印转账前状态

field_names = ["用户姓名", "当前余额"]

print("|".join([f"{name:<10}" for name in field_names]))

print("-" * (10 * len(field_names) + len(field_names) - 1))

print("|".join([f"{from_user[0]:<10}", f"{from_user[1]:<10}"]))

print("|".join([f"{to_user[0]:<10}", f"{to_user[1]:<10}"]))

# 3. 执行转账操作(两步操作组成一个事务)

# 步骤1:转出用户余额减amount

cursor.execute(

"UPDATE users SET balance = balance - %s WHERE name = %s",

(amount, from_name)

)

# 步骤2:转入用户余额加amount

cursor.execute(

"UPDATE users SET balance = balance + %s WHERE name = %s",

(amount, to_name)

)

# 4. 提交事务(所有操作成功,永久保存)

connection.commit()

print(f"\n转账成功!{from_name}向{to_name}转账{amount}元")

# 5. 转账后验证

print("\n=== 转账后账户状态 ===")

cursor.execute("SELECT name, balance FROM users WHERE name IN (%s, %s)", (from_name, to_name))

post_transfer = cursor.fetchall()

print("|".join([f"{name:<10}" for name in field_names]))

print("-" * (10 * len(field_names) + len(field_names) - 1))

for user in post_transfer:

print("|".join([f"{user[0]:<10}", f"{user[1]:<10}"]))

except Exception as e:

# 6. 事务回滚(任一操作失败,撤销所有修改)

print(f"\n转账失败:{e}")

if connection and connection.is_connected():

connection.rollback()

print("已执行事务回滚,恢复到转账前状态")

finally:

# 7. 关闭连接

if connection and connection.is_connected():

cursor.close()

connection.close()

print("\nMySQL连接已关闭")

# 执行转账(张三更新向李四转账100元)

if __name__ == "__main__":

transfer_money(from_name="张三更新", to_name="李四", amount=100)

运行结果(成功场景,规整对齐)

=== 转账前账户状态 ===

用户姓名 |当前余额

-------------------------

张三更新 |1000

李四 |1000

转账成功!张三更新向李四转账100元

=== 转账后账户状态 ===

用户姓名 |当前余额

-------------------------

张三更新 |900

李四 |1100

MySQL连接已关闭

运行结果(失败场景:余额不足)

=== 转账前账户状态 ===

用户姓名 |当前余额

-------------------------

张三更新 |900

李四 |1100

转账失败:转出用户'张三更新'余额不足(当前余额:900)

已执行事务回滚,恢复到转账前状态

MySQL连接已关闭

6.3 事务隔离级别(避免并发问题)

当多个事务同时执行时,可能出现脏读、不可重复读、幻读等并发问题,MySQL 通过 “隔离级别” 控制事务间的可见性,解决这些问题。

6.3.1 常见并发问题

问题名称

定义

示例场景

脏读

事务 A 读取了事务 B 未提交的修改(若 B 回滚,A 读取的数据是 “无效” 的)

事务 B 修改用户余额为 2000 但未提交,事务 A 读取到 2000;随后 B 回滚,A 读取的 2000 是脏数据

不可重复读

事务 A 多次读取同一数据,期间事务 B 修改并提交该数据,导致 A 多次读取结果不一致

事务 A 第一次读用户余额为 1000,事务 B 修改为 1500 并提交,A 再次读变为 1500

幻读

事务 A 按条件查询数据,期间事务 B 插入符合条件的新数据,导致 A 再次查询结果增多

事务 A 查询 “余额> 800” 的用户有 2 个,事务 B 插入 1 个余额 900 的用户,A 再次查询变为 3 个

6.3.2 MySQL 的四种隔离级别

MySQL 支持四种隔离级别(从低到高,并发性能递减,数据一致性递增):

隔离级别名称

英文全称

解决的并发问题

Python 设置方式(connection 对象)

读未提交

Read Uncommitted

无(会出现所有并发问题)

connection.isolation_level = 'READ UNCOMMITTED'

读已提交

Read Committed

脏读

connection.isolation_level = 'READ COMMITTED'

可重复读(默认)

Repeatable Read

脏读、不可重复读

connection.isolation_level = 'REPEATABLE READ'

串行化

Serializable

脏读、不可重复读、幻读

connection.isolation_level = 'SERIALIZABLE'

6.3.3 Python 设置隔离级别示例

def set_isolation_level(level):

"""设置事务隔离级别并验证"""

connection = None

try:

connection = mysql.connector.connect(

host='localhost',

user='root',

password='your_password',

database='python_db'

)

# 设置隔离级别

connection.isolation_level = level

# 验证设置结果

current_level = connection.isolation_level

print(f"事务隔离级别设置成功,当前级别:{current_level}")

except Error as e:

print(f"设置隔离级别出错:{e}")

finally:

if connection and connection.is_connected():

connection.close()

# 示例:设置为“读已提交”级别

if __name__ == "__main__":

set_isolation_level('READ COMMITTED')

运行结果

事务隔离级别设置成功,当前级别:READ COMMITTED

6.4 数据备份(导出)与恢复(导入)

数据备份是防止数据丢失的关键手段,Python 可通过SELECT ... INTO OUTFILE实现数据导出(备份),通过LOAD DATA INFILE实现数据导入(恢复)。

6.4.1 数据备份(导出为 CSV 文件)

将users表数据导出为 CSV 文件(逗号分隔,便于 Excel 打开和长期存储),步骤如下:

Python 实现数据导出

def backup_table_to_csv(table_name, output_path):

"""

数据表导出为CSV文件

:param table_name: 表名(如'users')

:param output_path: 输出文件路径(如'./users_backup.csv')

"""

connection = None

try:

connection = mysql.connector.connect(

host='localhost',

user='root',

password='your_password',

database='python_db'

)

if connection.is_connected():

cursor = connection.cursor()

# 导出SQL:字段用逗号分隔,行用换行分隔,忽略表头

backup_query = f"""

SELECT * FROM {table_name}

INTO OUTFILE '{output_path}'

FIELDS TERMINATED BY ',' # 字段分隔符

ENCLOSED BY '"' # 字段值用双引号包裹(避免含逗号的字符串拆分)

LINES TERMINATED BY '\n' # 行分隔符

"""

cursor.execute(backup_query)

print(f"数据表'{table_name}'备份成功,文件路径:{output_path}")

# 验证备份文件行数(与表记录数对比)

cursor.execute(f"SELECT COUNT(*) FROM {table_name}")

table_count = cursor.fetchone()[0]

with open(output_path, 'r', encoding='utf-8') as f:

backup_count = len(f.readlines())

print(f"验证:表记录数={table_count},备份文件行数={backup_count}(一致则备份完整)")

except Error as e:

print(f"数据备份出错:{e}")

# 常见错误:MySQL安全策略禁止导出到指定路径,需修改my.cnf配置(添加secure-file-priv='')

finally:

if connection and connection.is_connected():

cursor.close()

connection.close()

# 执行备份(导出users表到当前目录的users_backup.csv)

if __name__ == "__main__":

backup_table_to_csv(table_name='users', output_path='./users_backup.csv')

运行结果(成功场景)

数据表'users'备份成功,文件路径:./users_backup.csv

验证:表记录数=5,备份文件行数=5(一致则备份完整)

6.4.2 数据恢复(从 CSV 文件导入)

假设users表数据意外丢失,可通过备份的 CSV 文件恢复数据,步骤如下:

前提:清空users表(模拟数据丢失)

def clear_table(table_name):

"""清空表数据(仅用于测试恢复场景)"""

connection = None

try:

connection = mysql.connector.connect(

host='localhost',

user='root',

password='your_password',

database='python_db'

)

connection.autocommit = False

cursor = connection.cursor()

confirm = input(f"确认清空表'{table_name}'的所有数据?(y/n):")

if confirm.lower() == 'y':

cursor.execute(f"DELETE FROM {table_name}")

connection.commit()

print(f"表'{table_name}'数据已清空")

else:

print("已取消清空操作")

except Error as e:

print(f"清空表出错:{e}")

if connection:

connection.rollback()

finally:

if connection and connection.is_connected():

cursor.close()

connection.close()

# 执行清空(测试用)

# clear_table</doubaocanvas>

相关推荐

Postgres是个六边形战士,其他很多工具是没必要的

你只是想部署一个简单应用,一个仪表盘、一个聊天工具,或者个人项目。突然之间,你得启动Redis做缓存,上Elasticsearch做搜索,用Firebase实现实时同步,配置无服务器函数的...

PostgreSQL开发与实战(5)备份管理pg_probackup

作者:太阳一、pg_probackup概述pg_probackup是一款免费的postgres数据库集群备份工具,与其他备份工具相比,它主要有如下一些优势:提供增量备份,增量备份一定程度上可以节省磁...

了解 PostgreSQL 的 MVCC 可见性基本检查规则

1.引言根据VadimMikheev的说法,PostgreSQL的多版本并发控制(MVCC)是一种“在多用户环境中提高数据库性能的高级技术”。该技术要求系统中存在同一数据元组的多个“版本”,...

PostgreSQL初学者需要知道一些技巧

PostgreSQL作为一个优雅而且稳健的数据库越来越被大家所采纳,除了Mysql以外很多人都开始接触并学习PostgreSQL。但是PostgreSQL和传统的一些数据库系统还有有点点小小的鸿沟,有...

Python - 操作 PostgreSQL 数据库的基本方法与代码

本文记录在Python编程中操作PostgreSQL数据库的基本方法与实现代码,包括连接数据库、创建数据表、插入/更新/删除数据表中的记录、函数调用、存储过程调用及事务处理等。获取数据库首先把连接数据...

详解PostgreSQL 12.2时间点恢复 (PITR)

概述PostgreSQL提供了不同的方法来备份和恢复数据库,可以是某一时刻数据库快照的完整备份或增量备份,可以使用SQL转储或文件系统级别的备份,在增量备份的基础上还可以实现基于时间点恢复。这...

PostgreSQL 17即将发布,新功能Top 3

按照计划,PostgreSQL17即将在2024年9月份发布,目前已经发布了第三个Beta版本,新版本的功能增强可以参考ReleaseNotes。本文给大家分享其中3个重大的新...

PostgreSQL从入门到精通教程 - 第39讲:数据库完全恢复

PostgreSQL从小白到专家,是从入门逐渐能力提升的一个系列教程,内容包括对PG基础的认知、包括安装使用、包括角色权限、包括维护管理、、等内容,希望对热爱PG、学习PG的同学们有帮助,欢迎持续关注...

PostgreSQL建库说明_postgresql能创建多少表

1.数据库逻辑结构介绍在一个PostgreSQL数据库系统中,数据库:一个PostgreSQL数据库服务下可以管理多个数据库,当应用连接到一个数据库时,一般只能访问这个数据库中的数据,而不能访问其他...

为什么delete后磁盘空间没有释放而truncate会释放?

背景因项目需求,需要清理一批旧数据,腾出空间给新数据,让同事负责这件事。料想会很顺利,但很快找到我,并告知在postgresql中把一张大的数据表删除掉了,查询表的size并没有改变。我震惊了,问他怎...

PostgreSQL技术内幕6:PostgreSQL索引技术

0.简介本文主要介绍PG的索引技术,包含PG支持的索引类型,语法,查看方式,以及其中B-Tree索引的原理解析和源码解读。1.PG索引类型介绍PG支持多种索引类型:B-tree、Hash、GiST、...

PostGreSQL历史_postgresql序列查询

1973UniversityINGRES(起源于IBMSystemR的一系列文档,MichaelStonebrakerandEugeneWong)1982INGRES1985...

Retool 如何升级主应用 4TB 的 PostgreSQL 数据库

本文最初发布于Retool官方博客。Retool的云托管产品基于一个在微软Azure云中运行的4TB的Postgres数据库。去年秋天,我们把这个数据库从Postgres9.6...

ArcGIS Desktop直连PostgreSQL安装及配置图解(windows)

目录1PostgreSQL11.0安装及配置2psqlODBC安装及配置3PostGIS安装及配置4pgAdmin4使用入门5空间数据导入5.1将PostgreSQL的bin文件路径添加...

基于patroni+etcd打造可自动故障转移的PostgreSQL集群

作者:杭州美创科技有限公司得益于PostgreSQL的开源特性,越来越多的第三方集群管理软件填补了PostgreSQL在集群方面的易用性和可靠性,patroni+etcd提供了一系列的集群管理方案。e...