死锁后数据丢失 - SQL Server 2008、Ruby on Rails、Phusion Passenger、Linux、FreeTDS
我遇到了一个神秘问题,该问题导致我负责的 Ruby on Rails 内部网应用程序中的数据丢失。如果这不是严格意义上的编程问题,我深表歉意——至少我维护了应用程序的 Ruby 代码。迄今为止,该问题已在两年内发生了 3 次。
环境:
- Linux - 红帽企业服务器 5.2
- Apache 2 网络服务器 (httpd-2.2.3-11.el5_2.4.rpm)
- Phusion 乘客 2.2.15
- Ruby 1.8.7,Rails 2.3.8,带有宝石:
- actionmailer (2.3.8)
- 动作包 (2.3.8)
- 活动记录(2.3.8)
- activerecord-sqlserver-适配器 (2.3.8)
- 主动资源(2.3.8)
- 主动支持 (2.3.8)
- 阿卡米 (1.2.0)
- 建设者(3.0.0)
- 异常通知(2.3.3.0)
- 快速线程(1.0.7)
- 玉 (0.4.6)
- httpi (1.1.1)
- 哑剧类型 (1.16)
- nokogiri (1.4.4)
- 海苔 (1.1.3)
- 乘客 (2.2.15)
- 机架 (1.1.0)
- 导轨 (2.3.8)
- 耙子 (0.8.7)
- ruby-net-ldap (0.0.4)
- rubyjedi-actionwebservice (2.3.5.20100714122544)
- 萨翁 (1.1.0)
- 芥末 (2.5.1)
- will_paginate (2.3.14)
- SQL Server 2008 数据库服务器
- 通过 ActiveRecord 访问数据库
- 数据库驱动:freetds-0.82、unixODBC-2.3.0.tar.gz、ruby-odbc-0.99991.tar.gz
症状:
- 请求锁定数据库资源的用户操作涉及死锁情况。
- SQL Server 通过杀死死锁所涉及的进程来解决死锁,这样至少其中一些进程可以成功完成。
- 在 Rails 应用程序端,死锁导致未处理的异常(我通过 exception_notification gem 得到通知)
- 死锁之后,活动 Rails 进程的数量在增加(这触发了我们监控系统的另一个通知),进程似乎挂起
- 发生这种情况的原因尚不清楚。这些进程似乎挂在数据库操作中(根据 Rails 日志)。通常我会期望 SQL Server 的死锁解决功能不会留下阻塞进程。
- 在前两种情况下,我重新启动了 Web 服务器作为对异常/挂起进程的反应。在第三种情况下(我正在度假),没有人对通知做出反应,但周末运行的 cronjob 显然也停止了进程(通过触摸“restart.txt”通过乘客软重启,效果相同)
- 网络服务器重启后,用户报告数据丢失。在 Web 服务器重新启动之前,从用户的角度来看,数据已按预期处理。与我们通信的其他系统中的 Rails 日志和数据似乎表明事务已正确提交。Web 服务器重新启动后,突然间,自死锁发生以来的所有数据库更改都丢失了。例如,我们有一个“users”表,其中有一个“last_access”列,该列在每个用户操作时都会更新。Web 服务器重新启动后,最新的“last_access”值是一天前的。所有事务似乎都丢失了,只有 @@IDENTITY 值继续使用数据丢失之前设置的值。
- 我从我们的 IT(维护数据库服务器)那里收到的信息似乎表明所有丢失的数据库操作都是一个巨大事务的一部分,该事务缺少最终的 COMMIT。当然,我期望的是每个 Rails 用户操作都会运行一个或多个单独的事务,但 SQL Server 事务日志将所有操作显示为一个巨大事务的一部分。
在我看来,好像发生了这样的事情:
- 其中一个涉及的组件(例如 Phusion Passenger、FreeTDS、SQL Server)中的错误导致并行运行的 Rails 进程共享数据库连接,并且可能还导致进程挂起。
- 涉及的进程之一是在事务中并在提交之前挂在某处
- 由于其他进程共享相同的连接(正如我假设的那样),它们也在同一个事务中
- 由于进程共享连接,因此用户能够看到数据更改(在 Web 服务器重新启动之前),即使 COMMIT 处于未决状态。
- Web 服务器重新启动迫使连接中止并回滚事务。
这有意义吗?我想知道是否有人有类似的经历或提示我可以进一步研究。我怀疑Passenger中的一个错误可能已经分叉了数据库连接的文件描述符,但我无法重现它。乘客似乎在每个分叉上正确地创建了新的数据库连接。
我正在考虑将数据库的隔离模型更改为“读取提交的快照”以减少死锁的数量,但我知道这并不能解决根本原因,而且这可能会给我带来其他问题。