使用Prisma $transaction时触发Maximum call stack size exceeded错误
问题:Prisma事务中触发“Maximum call stack size exceeded”错误
原本使用JavaScript+Prisma操作PostgreSQL时,无事务的公司及关联起重机更新功能正常。引入Prisma $transaction 保证原子性后,执行时卡顿约1分钟,随后抛出“Maximum call stack size exceeded”错误,且数据库几乎为空,不存在大量更新操作。使用Prisma版本为9.8.1,相关代码如下:
const updateCompany = async (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { return next( new HttpError('Invalid inputs passed, please check your data.', 422), ); } const companyId = req.params.id; const { name } = req.body; try { const company = await companyRepo.getCompanyById(companyId); if (!company) { return next(new HttpError('Company not found.', 404)); } // Update company name company.name = name; delete company.updatedAt; // Start the transaction const result = await prisma.$transaction(async (tx) => { const updatedCompany = await companyRepo.updateCompanyById( companyId, company, tx, ); const companyWithSitesAndCranes = await companyRepo.getCompanyWithSitesAndCranesById(companyId, tx); // update each crane we got from the company const sites = companyWithSitesAndCranes.sites; for (const site of sites) { for (const crane of site.cranes) { await craneRepo.updateCraneById(crane.id, { identityName: `${updatedCompany.name}-${site.name}-${crane.name}`, tx, }); } } return updatedCompany; }); // // without transaction // const updatedCompany = await companyRepo.updateCompanyById( // companyId, // company, // ); // const companyWithSitesAndCranes = // await companyRepo.getCompanyWithSitesAndCranesById(companyId); // // update each crane we got from the company // const sites = companyWithSitesAndCranes.sites; // for (const site of sites) { // for (const crane of site.cranes) { // await craneRepo.updateCraneById(crane.id, { // identityName: `${updatedCompany.name}-${site.name}-${crane.name}`, // }); // } // } return res.status(200).json(result); // return res.status(200).json(updatedCompany); } catch (err) { const error = new HttpError( err.message || 'Updating company failed.', err.code || 500, ); next(error); } }; const getCompanyWithSitesAndCranesById = (id, prismaClient = prisma) => { return prismaClient.company.findUnique({ where: { id: id, }, include: { sites: { include: { cranes: true, }, }, }, }); }; const updateCompanyById = (id, company, prismaClient = prisma) => prismaClient.company.update({ where: { id: id }, data: company }); const updateCraneById = (id, crane, prismaClient = prisma) => prismaClient.crane.update({ where: { id: id }, data: crane });
此前搜索得知该错误多与递归相关,但代码中无递归逻辑,求问题原因及解决方法。
解决方案
错误原因
核心问题是调用craneRepo.updateCraneById时参数传递错误:你把事务客户端tx作为更新数据对象的属性传入,而非作为第三个独立参数传递。
updateCraneById的定义明确接收三个参数:id、更新数据crane、可选的Prisma客户端prismaClient。但你在事务中的调用把tx放到了更新数据对象里,当Prisma处理data参数时,会递归遍历对象的所有属性来生成SQL语句,而tx内部包含大量循环引用的属性(比如与主Prisma客户端的关联、内部状态),导致递归遍历无限循环,最终触发栈溢出。
修复步骤
将tx作为第三个独立参数传递给craneRepo.updateCraneById,修改事务中的循环代码:
// 事务内的循环部分修改为: const sites = companyWithSitesAndCranes.sites; for (const site of sites) { for (const crane of site.cranes) { await craneRepo.updateCraneById( crane.id, { identityName: `${updatedCompany.name}-${site.name}-${crane.name}` }, tx // 正确:作为第三个参数传递事务客户端 ); } }
额外优化(可选)
如果起重机数量较多,可使用Promise.all批量执行更新,提升事务内的执行效率:
const updatePromises = []; for (const site of sites) { for (const crane of site.cranes) { updatePromises.push( craneRepo.updateCraneById( crane.id, { identityName: `${updatedCompany.name}-${site.name}-${crane.name}` }, tx ) ); } } await Promise.all(updatePromises);
内容的提问来源于stack exchange,提问作者Nir Pikan
相关产品推荐
相关产品推荐

