关于Nodejs处理事务的问题
关于Nodejs处理事务的问题
最近在写一个网站需要用到事务,node-mysql 是支持事务的,也没有问题,如下:
// 从链接池里得到connection
pool.getConnection(function(err, connection) {
if(err) {
console.error('mysql 链接失败');
return callback(err, null);
}
// 开始事务
connection.beginTransaction(function(err) {
if(err) {
throw err;
}
async.parallel([
function(callback) {
connection.query("执行sql,比如是添加任务", callback);
},
function(callback) {
connection.query("执行sql,添加任务时间区段", callback);
}
], function(err, result) {
if(err) {
connection.rollback(function() {
throw err;
});
return ;
}
// 提交事务
connection.commit(function(err) {
if (err) {
connection.rollback(function() {
throw err;
});
}
console.log('success!');
});
});
});
});
我们知道,使用的事务的时候必须所有的操作都在一个connection 上,
在这段代码里插入了一条任务,和任务的时间区段 但是生产的代码不可能是这个样子,对任务和时间区段的操作都会有个子的service,
mysqlUtil.beginTransaction(function(err) {
if(err) {
throw err;
}
async.parallel([
function(callback) {
taskService.add(task, callback);
},
function(callback) {
segment.add(segment, callback);
}
], function(err, result) {
if(err) {
mysqlUtil.rollback(function() {
throw err;
});
return ;
}
mysqlUtil.commit(function(err) {
if (err) {
mysqlUtil.rollback(function() {
throw err;
});
}
console.log('success!');
});
});
});
每一个service 都会自己从pool 里自己得到connection,问题就出现了,我没有办法保证让启动是的connection 和service 使用的connection是一个,各位大牛有什么好的办法吗?
在 Node.js 中处理数据库事务时,确保所有操作都在同一个连接上是非常重要的。如果每个服务都从连接池中获取自己的连接,那么事务将无法正确地跨这些连接进行管理。为了解决这个问题,可以采用以下几种方法:
方法一:传递当前连接
最直接的方法是在调用服务时传递当前的数据库连接。这样,所有服务都可以使用同一个连接来执行事务中的操作。
示例代码:
const mysql = require('mysql');
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'testdb'
});
function beginTransaction(connection, callback) {
connection.beginTransaction(function(err) {
if (err) {
return callback(err);
}
callback(null, connection);
});
}
function commit(connection, callback) {
connection.commit(function(err) {
if (err) {
connection.rollback(function() {
throw err;
});
}
callback(err);
});
}
function rollback(connection, callback) {
connection.rollback(function() {
callback(new Error('Transaction failed and rolled back'));
});
}
function taskService(connection, task, callback) {
connection.query("INSERT INTO tasks SET ?", task, callback);
}
function segmentService(connection, segment, callback) {
connection.query("INSERT INTO segments SET ?", segment, callback);
}
pool.getConnection(function(err, connection) {
if (err) {
console.error('MySQL 连接失败');
return callback(err, null);
}
beginTransaction(connection, function(err, connection) {
if (err) {
rollback(connection, function() {
throw err;
});
return;
}
async.parallel([
function(callback) {
taskService(connection, { name: 'Task 1' }, callback);
},
function(callback) {
segmentService(connection, { start: '2023-10-01', end: '2023-10-31' }, callback);
}
], function(err, results) {
if (err) {
rollback(connection, function() {
throw err;
});
return;
}
commit(connection, function(err) {
if (err) {
rollback(connection, function() {
throw err;
});
} else {
console.log('事务成功完成');
}
});
});
});
// 释放连接
connection.release();
});
方法二:使用事务中间件
另一种方法是使用事务中间件,例如 knex
或 sequelize
,它们提供了更高级的事务管理功能。
示例代码(使用 Knex):
const knex = require('knex')({
client: 'mysql',
connection: {
host: 'localhost',
user: 'root',
password: 'password',
database: 'testdb'
}
});
async function main() {
try {
await knex.transaction(async (trx) => {
await knex('tasks').insert({ name: 'Task 1' }).transacting(trx);
await knex('segments').insert({ start: '2023-10-01', end: '2023-10-31' }).transacting(trx);
});
console.log('事务成功完成');
} catch (err) {
console.error('事务失败', err);
}
}
main();
通过以上方法,你可以确保事务中的所有操作都在同一个连接上进行,从而保证事务的一致性和完整性。
可以显式地从 node-mysql 中取出一个 connection 来的吧?
不是无法得到connection,而是无法保证使用connection是同一个,不知道如何做,完全没思路 又不想每一个service都带一个connection的参数
事务推荐使用 bearcat-dao 对 node-mysql 进行了 AOP 事务封装
bearcat-dao 里面通过在 dao 方法显示的传递一个 transactionStatus 来保证事务是在同一个 connection 里面的
bearcat-dao 看起来还是有些复杂,能不能不用它就可以帮我把问题解决了呢
一个事务里的sql是顺序执行的 虽然没有用过node-mysql,但一看到async.parallel,而且两条sql不是互相嵌套在回调函数里,就知道这是并行执行的,就不会在一个事务里,也就不会在一个connection里
这段代码是有点问题,假设是顺序执行的,也同样不在同一个connection,service 会各自从连接池里得到connection
楼主你好,我也碰到了这个问题,想问一下您是怎么解决的?
有一个方法是, mysqlUtil获取一个connection, 然后调用service的时候将此connection作为参数传递,利用这个connection执行sql操作,不知道可不可行?
还有就是设置一个单例的connection,然后在需要事务操作的时候,获取这个单例使用
我不明白你们怎么会有这样的问题,在service中,首先拿联接,然后再把联接传到module中拿数据。不管拿多少数据,都是一个联接 From Noder
你们处理mysql数据库事务都用这种方式吗,如果事务中还有逻辑,或者前面一个sql执行的结果是后面一个sql执行的条件,这样的怎么处理呢,说简单点:一个包含事务的存储过程,怎么用node来做,也用这种方式吗?
刚刚封装了一个操作mysql的库,基于node-mysql. 支持事务,每一步的返回值可以用作后面一步的条件。 可用于generator/yield,async/await 戳这里
在处理事务时,确保所有操作都在同一个连接上是非常重要的。这可以通过传递连接对象来实现。下面是具体的解决方案:
- 传递连接对象:在开始事务时,将连接对象传递给需要使用该连接的服务函数。
示例代码
const mysql = require('mysql');
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'testdb'
});
function beginTransaction(connection, callback) {
connection.beginTransaction(function(err) {
if (err) {
return callback(err);
}
callback(null, connection);
});
}
function commit(connection, callback) {
connection.commit(function(err) {
if (err) {
return callback(err);
}
callback();
});
}
function rollback(connection, callback) {
connection.rollback(function() {
callback();
});
}
function addTask(task, connection, callback) {
connection.query("INSERT INTO tasks SET ?", task, callback);
}
function addSegment(segment, connection, callback) {
connection.query("INSERT INTO segments SET ?", segment, callback);
}
pool.getConnection(function(err, connection) {
if (err) {
console.error('mysql 链接失败');
return callback(err, null);
}
beginTransaction(connection, function(err, conn) {
if (err) {
rollback(conn, function() {
throw err;
});
return;
}
async.parallel([
function(callback) {
addTask({ name: 'Task 1' }, conn, callback);
},
function(callback) {
addSegment({ start_time: '2023-01-01', end_time: '2023-01-31' }, conn, callback);
}
], function(err, results) {
if (err) {
rollback(conn, function() {
throw err;
});
return;
}
commit(conn, function(err) {
if (err) {
rollback(conn, function() {
throw err;
});
} else {
console.log('success!');
}
});
});
});
});
解释
- beginTransaction:用于开启事务。
- commit:用于提交事务。
- rollback:用于回滚事务。
- addTask 和 addSegment:这两个函数接收连接对象作为参数,并使用该连接对象执行SQL语句。
- pool.getConnection:获取连接后,立即开启事务,并将连接传递给后续的服务函数。
通过这种方式,可以确保所有操作都在同一个连接上进行,从而保证事务的完整性。