当前位置: 首页 > news >正文

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

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

引言:事务的“超能力”与“坑”在编程的世界里,事务(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. 其他任何非数据库的耗时或不确定操作****核心原则:事务应该尽可能短——只做必要的数据库读写操作,然后立即提交。其他操作都放在事务外部。记住,事务持有锁,锁就是资源,资源被长时间占用就是灾难。最后,如果你曾经把发送邮件、计算斐波那契数列、或者等待用户点击按钮放进事务里,别担心,我们都犯过这样的错误。重要的是从中学到经验,写出更健壮的代码。希望这篇文章能帮你避开这些坑,让你的事务更高效、更安全!

http://www.cnnetsun.cn/news/3667215.html

相关文章:

  • ChatPicMigrator4QQNT:3步完成QQ聊天记录图片视频迁移的终极方案
  • 3分钟上手Sketch批量文本替换神器:告别繁琐手动修改
  • 华为防火墙产品介绍和工作原理介绍、注意问题
  • 免费获取9大网盘真实下载链接:网盘直链下载助手终极指南
  • 艾尔登法环终极调试工具:如何掌控交界地的每一个秘密
  • GetQzonehistory:如何快速找回QQ空间全部历史说说的完整教程
  • GEO AI技术在成都本地化SEO营销中的实践应用
  • BG3ModManager终极指南:打造完美博德之门3模组体验
  • 动态协作网络在医学影像血管分割中的应用与优化
  • 基于LSTM的游戏AI动作序列预测:从时序模型到实战集成
  • 3分钟快速下载电子课本:国家中小学智慧教育平台资源获取全攻略
  • DSP/BIOS PIP模块深度解析:生产者-消费者模型与实时数据流管理
  • 终极指南:Keras实现的DenseNet如何突破图像识别性能极限?
  • PaddlePaddle工业视觉检测系统实战:装配制造智能化升级
  • 掌纹识别系统开发:从RandomForest到移动端部署
  • 终极窗口大小强制调整工具:3分钟掌握Window Resizer,让你的桌面焕然一新
  • AI简历优化工具:提升考研党求职成功率的关键
  • 动态输入处理优化:提升AI模型实时性能的三大策略
  • Windows上3分钟快速安装Android应用:APK-Installer完整指南
  • 国家中小学智慧教育平台电子课本下载终极指南:快速获取教材PDF的完整解决方案
  • 如何在10分钟内构建专业信用评分卡?Python评分卡工具scorecardpy完全指南
  • 免费文档下载革命:kill-doc如何帮你轻松获取30+平台的学习资料
  • 基于yolo进行深度学习的机动车车牌检测
  • VC++与MFC实战:从零开发电话本程序,掌握Windows桌面应用开发精髓
  • Unity多屏开发避坑指南:数据驱动配置与动态屏幕管理
  • OmenSuperHub终极评测:暗影精灵笔记本性能解放的完全指南
  • 3分钟快速上手:如何用ES-Client轻松管理Elasticsearch集群
  • 如何轻松迁移旧版QQ图片视频到新版QQ NT
  • Claude3.7智能绘制技术路线图的高效方案
  • 为什么说 Edge/Serverless 是最适合独立开发者的开发方式?