在Node.js中处理回滚的MySQL事务

9

我已经遇到一个问题几天了,真的希望你能帮助我。

这是一个基于node.js的API,使用sequelize连接MySQL

在某些API调用时,代码会启动SQL事务,锁定某些表,如果我同时发送多个请求到API,就会出现LOCK_WAIT_TIMEOUT错误。

var SQLProcess = function () {
    var self = this;
    var _arguments = arguments;

    return sequelize.transaction(function (transaction) {
            return doSomething({transaction: transactioin});
        })
        .catch(function (error) {
            if (error && error.original && error.original.code === 'ER_LOCK_WAIT_TIMEOUT') {

                return Promise.delay(Math.random() * 1000)
                    .then(function () {
                        return SQLProcess.apply(self, _arguments);
                    });

            } else {
                throw error;
            }
        });
};

我的问题是,同时运行的请求会相互锁定很长时间,我的请求需要等待很久才能返回结果(约60秒)。

希望我能够清晰明了地解释问题,您可以给我一些解决方案吗?


这不是node.js或者sequelize的问题,而是来自MySQL的错误信息1205 Lock wait timeout exceeded; try restarting transaction。你应该查看MySQL服务器中的事务和它们的状态。 - Vlad Churakov
我非常清楚为什么会出现这个错误。我正在寻找解决方案,以便能够处理它。 - Adam
只有一种方法可以了解发生了什么,那就是 SHOW ENGINE INNODB STATUS\G。根据我的经验,在MySQL中我多次遇到死锁事务。 - Vlad Churakov
不清楚你期望得到什么样的答案。你所说的“处理”是什么意思?你想要消除锁定(在这种情况下,你的doSomething做了什么很重要)?还是你想要重复你的事务(在这种情况下,为什么不从你的处理程序再次运行doSomething?或者你的catch语句不起作用,你想让它起作用?我有一个与死锁相关的大量随机笔记列表在这里,现在看起来对我来说很困惑,但是请查看一下,也许你会找到一些有用的东西。 - Borys Serebrov
2个回答

3
这可能不是你问题的直接答案,但是通过了解为什么会出现这个问题也许会有所帮助。
1)那个 doSomething() 做了些什么?我们能否做一些改进?
首先,一个需要 60 秒的事务是可疑的。如果你锁定一个表那么长时间,那么设计应该重新审视。通常数据库操作运行时间在 10 到 100 毫秒之间。
理想情况下,所有数据准备工作都应该在事务外完成,包括从数据库中读取的数据。事务应该真正只用于事务性操作。
2)是否可以使用 mysql 存储过程?
确实,mysql 的存储过程不像 Oracle 的 PL/SQL 那样编译。但它仍然在数据库服务器上运行。如果你的应用程序非常复杂,并且在事务中存在大量的数据库和节点应用程序之间的网络流量,考虑到有如此多层的 JavaScript 调用,它可能会真正地减慢速度。如果第一种方法并不能节省很多时间,则考虑使用 mysql 存储过程。
这种方法的缺点显然是更难在 node.js 和 mysql 中维护代码。
如果第一种和第二种方法肯定行不通,你可以考虑某种流程控制或排队工具。要么你的应用程序确保第二个请求在第一个完成之前不会进行,要么你有一些第三方排队工具来处理它。似乎你并不需要在运行这些请求时进行任何并行操作。

谢谢!排队工具成功了!我已经设置了一个“bluebird-queue”,现在所有的请求都不会阻塞地运行。 - Adam

2
死锁的主要原因是数据库设计不良。如果没有有关您的数据库设计和哪些确切的查询可能会相互锁定的进一步信息,就无法为您的问题提供具体解决方案。
但是,我可以给出一个解决此问题的一般建议/方法:
  • 确保您的数据库至少符合第三范式的规范化,或者如果还不足够,甚至进一步。可能有工具可以自动完成此过程。

    除了减少死锁的可能性外,这也有助于保持数据的一致性,这总是件好事。
  • 使您的事务尽可能简洁。如果您正在向表中插入新行并相应地更新其他表,则可能希望使用触发器而不是另一个SQL语句来执行此操作。读取行和值也适用相同的方法,这些事情可以在事务之前或之后完成。
  • 选择正确的隔离级别。可能的隔离级别包括:

    READ_UNCOMMITTED
    READ_COMMITTED
    REPEATABLE_READ
    SERIALIZABLE

    Sequelize的官方文档描述了如何自己设置隔离级别和锁定/解锁事务。



正如我所说,没有关于您的数据库和查询设计的进一步见解,这就是我现在能为您做的全部。
希望这可以帮助您。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接