news 2026/7/26 17:42:11

那些年不该放到事务中的操作,你实现过哪些

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
那些年不该放到事务中的操作,你实现过哪些

那些年不该放到事务中的操作,你实现过哪些

引言:事务的“超能力”与“坑”在编程的世界里,事务(Transaction)就像是一个魔法盒子,它能让一系列操作要么全部成功,要么全部失败。开发者在处理数据库操作时,很容易陷入一个误区:把所有操作都塞进事务里,以为这样数据就安全了。但现实是,事务不是万能药,有些操作放在事务中不仅会降低性能,甚至可能引发死锁、数据不一致等问题。今天,我们就来聊聊那些“不该放到事务中的操作”,看看你曾经是不是也踩过这些坑。我会用通俗易懂的语言,配合真实代码示例,帮你避开这些雷区。## 1. 什么是事务?为什么会有“不该放”的操作?先简单回顾一下事务的基本概念。事务是数据库管理系统执行过程中的一个逻辑单位,它满足ACID特性(原子性、一致性、隔离性、持久性)。简单说,事务保证了一组操作要么都做完,要么都不做。但问题来了:事务会持有锁,占用数据库连接资源。如果你把耗时操作(比如网络请求、文件写入、大量计算)放在事务里,就会导致事务长时间运行,影响并发性能,甚至引发死锁。## 2. 不该放的操作一:远程网络调用### 为什么不能放?远程网络调用(如调用外部API、发送短信、邮件通知)通常是不确定性的——你无法控制外部服务的响应时间或可用性。如果放在事务中,事务会一直等待网络响应,导致数据库连接被长时间占用。更糟的是,如果外部服务超时,事务会回滚,但外部调用可能已经执行成功了(比如已经发了短信),导致数据不一致。### 代码示例(错误示范)pythonimport timeimport randomdef send_email(user_email, content): """模拟发送邮件,可能随机超时""" time.sleep(random.uniform(0.1, 2)) # 模拟网络延迟 if random.random() < 0.2: # 20%概率失败 raise Exception("邮件服务超时") print(f"邮件已发送到 {user_email}")def create_user_transaction(db, username, email): """错误:把网络调用放在事务里""" try: db.execute("BEGIN TRANSACTION") # 数据库操作 db.execute("INSERT INTO users (username, email) VALUES (?, ?)", (username, email)) # 网络调用(不应该放在事务里!) send_email(email, "欢迎注册") db.execute("COMMIT") print("用户创建成功") except Exception as e: db.execute("ROLLBACK") print(f"事务回滚,原因:{e}")# 假设运行这个函数,如果邮件发送失败,用户数据被回滚,但用户没收到错误提示### 正确做法pythondef create_user_safe(db, username, email): """正确:先提交事务,再发邮件""" try: db.execute("INSERT INTO users (username, email) VALUES (?, ?)", (username, email)) db.commit() # 先提交事务 # 事务外发送邮件 send_email(email, "欢迎注册") print("用户创建成功") except Exception as e: db.rollback() print(f"数据库操作失败,原因:{e}")原则:网络调用放在事务之外,先提交数据库,再尝试外部操作。如果外部失败,可以通过补偿机制(如重试、人工处理)解决。## 3. 不该放的操作二:大量数据计算或文件操作### 为什么不能放?假设你在事务中执行一个循环,读取大量数据并进行复杂的数学计算,或者写文件。这些操作本身与数据库无关,但会延长事务持续时间。数据库连接池中的连接会被长时间占用,导致其他请求无法获得连接,最终表现为系统响应变慢。### 代码示例(错误示范)pythondef process_large_data_transaction(db): """错误:在事务中处理大量数据计算""" db.execute("BEGIN TRANSACTION") try: # 从数据库读取大量数据 rows = db.execute("SELECT * FROM large_table").fetchall() # 复杂计算(不应该放在事务里!) results = [] for row in rows: # 模拟耗时计算 result = sum(row[i] ** 2 for i in range(len(row))) results.append(result) # 写文件 with open("output.txt", "w") as f: for r in results: f.write(f"{r}\n") # 更新数据库 db.execute("UPDATE large_table SET processed = 1") db.execute("COMMIT") print("处理完成") except Exception as e: db.execute("ROLLBACK") print(f"事务回滚,原因:{e}")### 正确做法pythondef process_large_data_safe(db): """正确:先在事务外读取和计算,再在事务中更新""" # 1. 先读取数据(不需要事务) rows = db.execute("SELECT * FROM large_table").fetchall() # 2. 在内存中计算(不需要事务) results = [] for row in rows: result = sum(row[i] ** 2 for i in range(len(row))) results.append(result) # 3. 写文件(不需要事务) with open("output.txt", "w") as f: for r in results: f.write(f"{r}\n") # 4. 最后开启事务更新数据库 db.execute("BEGIN TRANSACTION") try: db.execute("UPDATE large_table SET processed = 1") db.execute("COMMIT") print("更新完成") except Exception as e: db.execute("ROLLBACK") print(f"更新失败,原因:{e}")原则:事务只负责数据库的原子性操作,计算和文件操作分离出去。如果计算过程中出错,只需要重新计算,不需要回滚数据库。## 4. 不该放的操作三:用户交互或长时间等待### 为什么不能放?有些开发者会在事务中等待用户输入(比如确认对话框),这是极其危险的做法。事务会一直持有锁,如果用户去吃午饭了,数据库连接就会一直挂着,导致死锁或连接池耗尽。### 代码示例(错误示范)pythondef process_order_with_user_confirm(db, order_id): """错误:在事务中等待用户确认""" db.execute("BEGIN TRANSACTION") try: # 锁定订单 db.execute("UPDATE orders SET status = 'locked' WHERE id = ?", (order_id,)) # 等待用户确认(愚蠢的做法!) user_input = input("请确认订单?(y/n): ") if user_input == 'y': db.execute("UPDATE orders SET status = 'confirmed' WHERE id = ?", (order_id,)) db.execute("COMMIT") else: db.execute("ROLLBACK") except Exception as e: db.execute("ROLLBACK")### 正确做法pythondef process_order_with_separate_steps(db, order_id): """正确:把事务和用户交互分开""" # 第一步:在事务外获取用户确认 user_input = input("请确认订单?(y/n): ") # 第二步:根据用户输入开启事务 db.execute("BEGIN TRANSACTION") try: if user_input == 'y': db.execute("UPDATE orders SET status = 'confirmed' WHERE id = ?", (order_id,)) db.execute("COMMIT") print("订单已确认") else: db.execute("ROLLBACK") print("订单已取消") except Exception as e: db.execute("ROLLBACK")原则:永远不要在事务中等待用户输入或任何不确定时长的操作。事务应该短平快。## 5. 不该放的操作四:发送消息队列消息### 为什么不能放?消息队列(如RabbitMQ、Kafka)通常用于异步处理。如果把发送消息放到事务里,当消息队列服务不可用时,事务会回滚,但消息可能已经被发送了(或者消息队列有重试机制导致重复消费)。### 代码示例pythonimport pikadef send_message_to_queue(message): """模拟发送消息到RabbitMQ""" connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() channel.basic_publish(exchange='', routing_key='order_created', body=message) connection.close()def create_order_with_message_transaction(db, order_data): """错误:把消息发送放在事务里""" db.execute("BEGIN TRANSACTION") try: db.execute("INSERT INTO orders (customer, amount) VALUES (?, ?)", (order_data['customer'], order_data['amount'])) # 发送消息(不应该放在事务里!) send_message_to_queue(f"Order created: {order_data}") db.execute("COMMIT") except Exception as e: db.execute("ROLLBACK") print(f"事务回滚,原因:{e}")### 正确做法pythondef create_order_with_message_safe(db, order_data): """正确:先提交事务,再发送消息""" db.execute("BEGIN TRANSACTION") try: db.execute("INSERT INTO orders (customer, amount) VALUES (?, ?)", (order_data['customer'], order_data['amount'])) db.execute("COMMIT") # 事务外发送消息 send_message_to_queue(f"Order created: {order_data}") print("订单创建成功,消息已发送") except Exception as e: db.execute("ROLLBACK") print(f"事务失败,原因:{e}")原则:消息发送属于异步操作,应该放在事务之外。如果发送失败,可以通过重试或人工处理。## 总结经过上面的分析,我们可以看到,事务的本质是保护数据库的原子性操作,而不是万能的任务管理器。不该放到事务中的操作包括:1.远程网络调用(API、邮件、短信)2.大量数据计算或文件操作3.用户交互或长时间等待4.发送消息队列消息5. 其他任何非数据库的耗时或不确定操作****核心原则:事务应该尽可能短——只做必要的数据库读写操作,然后立即提交。其他操作都放在事务外部。记住,事务持有锁,锁就是资源,资源被长时间占用就是灾难。最后,如果你曾经把发送邮件、计算斐波那契数列、或者等待用户点击按钮放进事务里,别担心,我们都犯过这样的错误。重要的是从中学到经验,写出更健壮的代码。希望这篇文章能帮你避开这些坑,让你的事务更高效、更安全!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/26 17:41:11

ChatPicMigrator4QQNT:3步完成QQ聊天记录图片视频迁移的终极方案

ChatPicMigrator4QQNT&#xff1a;3步完成QQ聊天记录图片视频迁移的终极方案 【免费下载链接】ChatPicMigrator4QQNT 项目地址: https://gitcode.com/gh_mirrors/ch/ChatPicMigrator4QQNT 还在为QQ升级到新版NT版本后聊天记录中的图片视频丢失而烦恼吗&#xff1f;Chat…

作者头像 李华
网站建设 2026/7/26 17:33:49

3分钟上手Sketch批量文本替换神器:告别繁琐手动修改

3分钟上手Sketch批量文本替换神器&#xff1a;告别繁琐手动修改 【免费下载链接】Sketch-Find-And-Replace Sketch plugin to do a find and replace on text within layers 项目地址: https://gitcode.com/gh_mirrors/sk/Sketch-Find-And-Replace 还在为Sketch设计稿中…

作者头像 李华
网站建设 2026/7/26 17:33:07

华为防火墙产品介绍和工作原理介绍、注意问题

链接&#xff1a;【干货】你真的搞清楚了吗&#xff1f;原来这才是华为防火墙~https://baijiahao.baidu.com/s?id1704423133169398694&wfrspider&forpc 感谢分享&#xff0c;仅供参考。 注意问题 连接调试防火墙&#xff1a;只有防火墙左下角的0口&#xff0c;才能…

作者头像 李华
网站建设 2026/7/26 17:33:06

免费获取9大网盘真实下载链接:网盘直链下载助手终极指南

免费获取9大网盘真实下载链接&#xff1a;网盘直链下载助手终极指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天…

作者头像 李华
网站建设 2026/7/26 17:32:36

艾尔登法环终极调试工具:如何掌控交界地的每一个秘密

艾尔登法环终极调试工具&#xff1a;如何掌控交界地的每一个秘密 【免费下载链接】Elden-Ring-Debug-Tool Debug tool for Elden Ring modding 项目地址: https://gitcode.com/gh_mirrors/el/Elden-Ring-Debug-Tool 想要彻底探索《艾尔登法环》的每一个角落吗&#xff1…

作者头像 李华
网站建设 2026/7/26 17:31:58

GetQzonehistory:如何快速找回QQ空间全部历史说说的完整教程

GetQzonehistory&#xff1a;如何快速找回QQ空间全部历史说说的完整教程 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否曾想找回那些消失在时间里的QQ空间说说&#xff1f;那些记…

作者头像 李华