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 元” 为例,需执行两步操作:
- 用户 A(name='张三更新')余额减 100
- 用户 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...
- 一周热门
- 最近发表
-
- Postgres是个六边形战士,其他很多工具是没必要的
- PostgreSQL开发与实战(5)备份管理pg_probackup
- 了解 PostgreSQL 的 MVCC 可见性基本检查规则
- PostgreSQL初学者需要知道一些技巧
- Python - 操作 PostgreSQL 数据库的基本方法与代码
- 详解PostgreSQL 12.2时间点恢复 (PITR)
- PostgreSQL 17即将发布,新功能Top 3
- PostgreSQL从入门到精通教程 - 第39讲:数据库完全恢复
- PostgreSQL建库说明_postgresql能创建多少表
- 为什么delete后磁盘空间没有释放而truncate会释放?
- 标签列表
-
- ps图案在哪里 (33)
- super().__init__ (33)
- python 获取日期 (34)
- 0xa (36)
- super().__init__()详解 (33)
- python安装包在哪里找 (33)
- linux查看python版本信息 (35)
- python怎么改成中文 (35)
- php文件怎么在浏览器运行 (33)
- eval在python中的意思 (33)
- python安装opencv库 (35)
- python div (34)
- sticky css (33)
- python中random.randint()函数 (34)
- python去掉字符串中的指定字符 (33)
- python入门经典100题 (34)
- anaconda安装路径 (34)
- yield和return的区别 (33)
- 1到10的阶乘之和是多少 (35)
- python安装sklearn库 (33)
- dom和bom区别 (33)
- js 替换指定位置的字符 (33)
- python判断元素是否存在 (33)
- sorted key (33)
- shutil.copy() (33)