我正在使用带有节点的sequelize来创建一个简单的更新端点,该端点将进行几个查询:
const update = async (req: any, res: any, next: any) => {
const t = await db.sequelize.transaction();
// const t2 = await db.sequelize.transaction();
try {
const model = {
...req.body,
user_id: req.user.id,
};
let isPause = false;
if (model.description && model.description == 'operation_types.pause') isPause = true;
const data = await db.Operation.scope(
isPause ? {} : { method: ['companyScope', req] }
).findByPk(req.params.id, {
hooks: false,
include: [
{
model: db.Task,
as: 'task',
},
],
});
if (!data) throw new ResolveError(ResponsesEnums.NOT_FOUND, 404);
await data.update(model, { transaction: t });
const company = await db.Company.findByPk(req.user.company_id ?? data.task.company_id);
const fileUpload = new FileUploadJob();
if (req.files && req.files.length > 0) {
for (const file of req.files) {
fileUpload.addJob(
JobsNamesEnums.UPLOAD_FILE,
{ entity: data, file, company },
{ timeout: 120000 }
);
}
}
let resourceId;
if (req.body.resource_id) resourceId = req.body.resource_id;
if (req.resource_id) resourceId = req.resource_id;
if (model.operation && model.operation.resource_id)
resourceId = model.operation.resource_id;
if (resourceId) {
const resource = await db.Resource.findByPk(resourceId);
if (!resource) throw new ResolveError(ResponsesEnums.NOT_FOUND, 404);
if (model.lat && model.lng) {
await resource.update(
{ lat: parseFloat(model.lat), lng: parseFloat(model.lng) },
{ transaction: t }
);
}
const activation = await resource.sendHandleActivation();
if (activation && !activation.operation_id) {
const activationModel = await db.ResourceActivation.findByPk(activation.id);
await activationModel.update({ operation_id: data.id });
} else if (!activation) {
throw new ResolveError('Impossible to activate the resource', 400);
}
}
// If model.stop and has an linked_operation_id, update the linked operation
if (req.body.stop && model.stop && data.linked_operation_id) {
const linkedOperation = await db.Operation.findByPk(data.linked_operation_id);
if (linkedOperation) {
await linkedOperation.update({ stop: model.stop });
}
}
await t.commit();
return res.send(data);
} catch (error: any) {
logError(error);
await t.rollback();
return next(error);
}
};
这里的问题是,如果我将事务传递给我的操作.update和resource.update,它将花费> 50秒的时间来执行,但如果我避免传递它,则需要花费< 1s. This is to say that the queries are pretty basic and don't take much to execute them.
有人有解决办法吗?
您遇到的问题是,当传递给
operation.update
和 resource.update
时,事务花费的时间比预期长(超过 50 秒),这可能与 Sequelize 如何处理池中的事务和连接以及潜在的锁定或连接有关。资源争用问题。根据您的情况,以下是一些可能的原因和解决方案:
Sequelize 使用连接池来高效管理数据库连接。当您使用事务时,每个事务都需要池中的专用连接。如果您的池大小较小或并发事务过多,则可能会导致延迟,因为 Sequelize 必须等待可用连接。
您可以尝试增加 Sequelize 配置中的连接池大小以允许更多并发事务:
const sequelize = new Sequelize({
// other options...
pool: {
max: 20, // Increase this value based on your server's capacity
min: 0,
acquire: 30000, // Wait up to 30 seconds for a connection
idle: 10000, // Release idle connections after 10 seconds
},
});
如果您的应用程序正在运行许多正在等待可用连接的并发事务,这将很有帮助。
Sequelize 支持托管事务,它根据是否发生错误自动处理提交或回滚事务。这可以简化您的代码,并通过减少手动事务管理来潜在地提高性能。
您可以重构代码以使用托管事务,而不是手动启动和提交/回滚事务:
const update = async (req: any, res: any, next: any) => {
try {
const result = await db.sequelize.transaction(async (t) => {
const model = {
...req.body,
user_id: req.user.id,
};
let isPause = false;
if (model.description && model.description == 'operation_types.pause') isPause = true;
const data = await db.Operation.scope(
isPause ? {} : { method: ['companyScope', req] }
).findByPk(req.params.id, {
hooks: false,
include: [
{
model: db.Task,
as: 'task',
},
],
transaction: t, // Ensure this query uses the transaction
});
if (!data) throw new ResolveError(ResponsesEnums.NOT_FOUND, 404);
await data.update(model, { transaction: t });
const company = await db.Company.findByPk(req.user.company_id ?? data.task.company_id);
const fileUpload = new FileUploadJob();
if (req.files && req.files.length > 0) {
for (const file of req.files) {
fileUpload.addJob(
JobsNamesEnums.UPLOAD_FILE,
{ entity: data, file, company },
{ timeout: 120000 }
);
}
}
let resourceId;
if (req.body.resource_id) resourceId = req.body.resource_id;
if (req.resource_id) resourceId = req.resource_id;
if (model.operation && model.operation.resource_id)
resourceId = model.operation.resource_id;
if (resourceId) {
const resource = await db.Resource.findByPk(resourceId);
if (!resource) throw new ResolveError(ResponsesEnums.NOT_FOUND, 404);
if (model.lat && model.lng) {
await resource.update(
{ lat: parseFloat(model.lat), lng: parseFloat(model.lng) },
{ transaction: t }
);
}
const activation = await resource.sendHandleActivation();
if (activation && !activation.operation_id) {
const activationModel = await db.ResourceActivation.findByPk(activation.id);
await activationModel.update({ operation_id: data.id }, { transaction: t });
} else if (!activation) {
throw new ResolveError('Impossible to activate the resource', 400);
}
}
if (req.body.stop && model.stop && data.linked_operation_id) {
const linkedOperation = await db.Operation.findByPk(data.linked_operation_id);
if (linkedOperation) {
await linkedOperation.update({ stop: model.stop }, { transaction: t });
}
}
return data; // Return the updated data
});
return res.send(result);
} catch (error) {
logError(error);
return next(error);
}
};
事务可以引入行级锁或表级锁,具体取决于它们的使用方式。如果多个查询尝试在不同事务中同时访问或修改相同的行,则可能会导致争用和延迟。
如果您怀疑锁定导致速度变慢,可以尝试使用行级锁 (
FOR UPDATE
) 或乐观并发控制来避免不必要的争用。
例如,您可以在获取行时显式锁定行:
const data = await db.Operation.scope(
isPause ? {} : { method: ['companyScope', req] }
).findByPk(req.params.id, {
hooks: false,
include: [
{
model: db.Task,
as: 'task',
},
],
lock: t.LOCK.UPDATE, // Lock the row for update
transaction: t,
});
这确保一次只有一个事务可以修改该行。
如果您使用嵌套事务(即在现有事务内启动新事务),可能会导致性能问题。通过确保所有查询共享相同的事务对象,确保您不会无意中嵌套事务。
在您的情况下,您应该将相同的
transaction
对象 (t
) 传递给需要成为同一事务一部分的所有查询:
await data.update(model, { transaction: t });
await resource.update({ lat, lng }, { transaction: t });
// Other queries using the same `t` object...
您可以通过访问 Sequelize 的池属性来监控连接池中正在使用的连接数量:
console.log('Connections in use:', sequelize.connectionManager.pool.size);
console.log('Available connections:', sequelize.connectionManager.pool.available);
这将帮助您确定在峰值负载期间是否耗尽了可用连接。
最后,确保您的数据库查询得到优化。如果某些查询由于复杂的连接或频繁查询的列上缺少索引而变慢,则无论它们是否在事务内,它们都将花费更长的时间。
id
、user_id
、company_id
等列上添加索引,这些列在WHERE
子句中经常使用。EXPLAIN
)。主要问题似乎与 Sequelize 如何处理事务和连接池有关。以下是您可以采取的操作摘要:
通过实施这些建议,您应该能够解决使用事务时更新端点的性能问题。