Sequelize 事务非常慢

问题描述 投票:0回答:1

我正在使用带有节点的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.

有人有解决办法吗?

node.js typescript transactions sequelize.js
1个回答
0
投票

您遇到的问题是,当传递给

operation.update
resource.update
时,事务花费的时间比预期长(超过 50 秒),这可能与 Sequelize 如何处理池中的事务和连接以及潜在的锁定或连接有关。资源争用问题。根据您的情况,以下是一些可能的原因和解决方案:

1。连接池和事务争用

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
  },
});

如果您的应用程序正在运行许多正在等待可用连接的并发事务,这将很有帮助。

2。托管交易

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);
    }
};

3.减少锁定和争用

事务可以引入行级锁或表级锁,具体取决于它们的使用方式。如果多个查询尝试在不同事务中同时访问或修改相同的行,则可能会导致争用和延迟。

解决方案:使用行级锁或乐观并发控制

如果您怀疑锁定导致速度变慢,可以尝试使用行级锁 (

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,
});

这确保一次只有一个事务可以修改该行。

4。避免嵌套事务

如果您使用嵌套事务(即在现有事务内启动新事务),可能会导致性能问题。通过确保所有查询共享相同的事务对象,确保您不会无意中嵌套事务。

在您的情况下,您应该将相同的

transaction
对象 (
t
) 传递给需要成为同一事务一部分的所有查询:

await data.update(model, { transaction: t });
await resource.update({ lat, lng }, { transaction: t });
// Other queries using the same `t` object...

5。监控连接池使用情况

您可以通过访问 Sequelize 的池属性来监控连接池中正在使用的连接数量:

console.log('Connections in use:', sequelize.connectionManager.pool.size);
console.log('Available connections:', sequelize.connectionManager.pool.available);

这将帮助您确定在峰值负载期间是否耗尽了可用连接。

6。优化数据库查询

最后,确保您的数据库查询得到优化。如果某些查询由于复杂的连接或频繁查询的列上缺少索引而变慢,则无论它们是否在事务内,它们都将花费更长的时间。

解决方案

  • id
    user_id
    company_id
    等列上添加索引,这些列在
    WHERE
    子句中经常使用。
  • 使用数据库提供的查询优化工具(例如 MySQL/PostgreSQL 中的
    EXPLAIN
    )。

结论

主要问题似乎与 Sequelize 如何处理事务和连接池有关。以下是您可以采取的操作摘要:

  1. 增加连接池大小以处理更多并发请求。
  2. 使用托管事务来简化处理。
  3. 调查潜在的锁定问题并在适当的情况下使用行级锁。
  4. 确保您不会无意中使用嵌套事务
  5. 监控您的连接池使用情况。
  6. 优化数据库查询以获得更好的性能。

通过实施这些建议,您应该能够解决使用事务时更新端点的性能问题。

© www.soinside.com 2019 - 2024. All rights reserved.