在 Node.js 中处理回滚的 MySQL 事务
Handling rollbacked MySQL transactions in Node.js
几天来我一直在处理一个问题,我真的希望你能帮助我。
这是一个基于 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;
}
});
};
我的问题是,同时发生的 运行 请求彼此锁定了很长时间,而我的请求 returns 在很长一段时间(~60 秒)之后。
我希望我能解释的清楚明白,你能给我一些解决方案。
这可能不是您问题的直接答案,但也许通过查看您遇到此问题的原因也会有所帮助。
1) doSomething() 有什么作用?不管怎样,我们可以在那里做一些改进吗?
首先,花费 60 秒的交易是可疑的。如果您将 table 锁定这么长时间,则很可能应该重新审视设计。给定一个典型的数据库操作运行 10 - 100 毫秒。
理想情况下,所有的数据准备都应该在事务之外完成,包括从数据库中读取数据。并且交易应该真的只用于交易操作。
2) 是否可以使用 mysql 存储过程?
是的,mysql 的存储过程未编译,与 Oracle 的 PL/SQL 一样。但它仍然在数据库服务器上 运行。如果您的应用程序真的很复杂,并且在该事务中包含数据库和您的节点应用程序之间的大量反向和强制网络流量,并且考虑到有这么多层 javascript 调用,它可能真的会减慢速度。如果 1) 不能为您节省很多时间,请考虑使用 mysql 存储过程。
显然,这种方法的缺点是很难在 nodejs 和 mysql 中维护代码。
如果1)和2)肯定不行,可以考虑某种流控或者排队工具。要么您的应用程序确保第二个请求在第一个请求完成之前不会执行,或者您有一些第 3 方排队工具来处理该请求。看来您在 运行 这些请求中不需要任何并行性。
死锁的主要原因是数据库设计不当。如果没有关于您的数据库设计以及哪些确切查询可能会或可能不会相互锁定的更多信息,则不可能为您的问题提供特定的解决方案。
不过我可以给你一个通用的advice/approach来解决这个问题:
- 我会确保您的数据库至少 normalized 符合 第三范式 或者,如果这还不够的话,甚至更进一步。可能有工具可以为您自动执行此过程。
除了减少死锁的可能性之外,这还有助于保持数据的一致性,这总是一件好事。
- 让您的交易尽可能少。如果您要向表中插入新行并相应地更新其他表,您可能希望使用触发器而不是另一个 SQL 语句来执行此操作。这同样适用于读取行和值。这些事情可以在您的交易之前或之后完成。
选择正确的隔离级别。可能的隔离级别是:
READ_UNCOMMITTED
READ_COMMITTED
REPEATABLE_READ
可序列化
Sequelize 的 official documentation 介绍了如何自行设置隔离级别和 lock/unlock 事务。
正如我所说,如果不进一步了解您的数据库和查询设计,这就是我现在能为您做的一切。
希望这有帮助。
几天来我一直在处理一个问题,我真的希望你能帮助我。
这是一个基于 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;
}
});
};
我的问题是,同时发生的 运行 请求彼此锁定了很长时间,而我的请求 returns 在很长一段时间(~60 秒)之后。
我希望我能解释的清楚明白,你能给我一些解决方案。
这可能不是您问题的直接答案,但也许通过查看您遇到此问题的原因也会有所帮助。
1) doSomething() 有什么作用?不管怎样,我们可以在那里做一些改进吗?
首先,花费 60 秒的交易是可疑的。如果您将 table 锁定这么长时间,则很可能应该重新审视设计。给定一个典型的数据库操作运行 10 - 100 毫秒。
理想情况下,所有的数据准备都应该在事务之外完成,包括从数据库中读取数据。并且交易应该真的只用于交易操作。
2) 是否可以使用 mysql 存储过程?
是的,mysql 的存储过程未编译,与 Oracle 的 PL/SQL 一样。但它仍然在数据库服务器上 运行。如果您的应用程序真的很复杂,并且在该事务中包含数据库和您的节点应用程序之间的大量反向和强制网络流量,并且考虑到有这么多层 javascript 调用,它可能真的会减慢速度。如果 1) 不能为您节省很多时间,请考虑使用 mysql 存储过程。
显然,这种方法的缺点是很难在 nodejs 和 mysql 中维护代码。
如果1)和2)肯定不行,可以考虑某种流控或者排队工具。要么您的应用程序确保第二个请求在第一个请求完成之前不会执行,或者您有一些第 3 方排队工具来处理该请求。看来您在 运行 这些请求中不需要任何并行性。
死锁的主要原因是数据库设计不当。如果没有关于您的数据库设计以及哪些确切查询可能会或可能不会相互锁定的更多信息,则不可能为您的问题提供特定的解决方案。
不过我可以给你一个通用的advice/approach来解决这个问题:
- 我会确保您的数据库至少 normalized 符合 第三范式 或者,如果这还不够的话,甚至更进一步。可能有工具可以为您自动执行此过程。
除了减少死锁的可能性之外,这还有助于保持数据的一致性,这总是一件好事。 - 让您的交易尽可能少。如果您要向表中插入新行并相应地更新其他表,您可能希望使用触发器而不是另一个 SQL 语句来执行此操作。这同样适用于读取行和值。这些事情可以在您的交易之前或之后完成。
选择正确的隔离级别。可能的隔离级别是:
READ_UNCOMMITTED
READ_COMMITTED
REPEATABLE_READ
可序列化Sequelize 的 official documentation 介绍了如何自行设置隔离级别和 lock/unlock 事务。
正如我所说,如果不进一步了解您的数据库和查询设计,这就是我现在能为您做的一切。
希望这有帮助。