【问题标题】:Knex: Timeout acquiring a connection. The pool is probably full. Are you missing a .transacting(trx) call? Best practices for using Knex.TransactionKnex:获取连接超时。游泳池可能已经满了。你错过了一个 .transacting(trx) 电话吗?使用 Knex.Transaction 的最佳实践
【发布时间】:2020-09-07 14:18:44
【问题描述】:

在处理具有多个表和多个数据库操作的大型应用程序时,很难跟踪正在发生的事务。为了解决这个问题,我们首先传递了一个 trx 对象。

事实证明这很混乱。

例如:

async getOrderById(id: string, trx?: Knex.Transaction) { ... }

根据调用getOrderById 的函数,它要么传递trx 对象,要么不传递。上述函数将使用trx,如果它不为null。

一开始这似乎很简单,但它会导致错误,如果您在一个函数中进行事务并调用另一个不使用事务的函数,knex 将与著名的Knex: Timeout acquiring a connection. The pool is probably full. 挂起

async getAllPurchasesForUser(userId: string) {
  ..
  const trx = await knex.transaction();
  try {
    ..
    getPurchaseForUserId(userId); // Forgot to make this consume trx, hence Knex timesout acquiring connection.
    ..
}

基于此,我假设这不是最佳实践,但如果 Knex 开发团队的人可以发表评论,我会很高兴。

为了改进这一点,我们正在考虑改用knex.transactionProvider(),无论我们在哪里执行数据库操作,都可以在整个应用程序中访问它。

网站上的例子似乎不完整:

// Does not start a transaction yet
const trxProvider = knex.transactionProvider();

const books = [
  {title: 'Canterbury Tales'},
  {title: 'Moby Dick'},
  {title: 'Hamlet'}
];

// Starts a transaction
const trx = await trxProvider();
const ids = await trx('catalogues')
  .insert({name: 'Old Books'}, 'id')
books.forEach((book) => book.catalogue_id = ids[0]);
await  trx('books').insert(books);

// Reuses same transaction
const sameTrx = await trxProvider();
const ids2 = await sameTrx('catalogues')
  .insert({name: 'New Books'}, 'id')
books.forEach((book) => book.catalogue_id = ids2[0]);
await sameTrx('books').insert(books);

在实践中,我是这样考虑使用它的:

SingletonDBClass.ts:

const trxProvider = knex.transactionProvider();
export default trxProvider;

Orders.ts

import trx from '../SingletonDBClass';
..
async getOrderById(id: string) {
  const trxInst = await trx;
  try {
    const order = await trxInst<Order>('orders').where({id});
    trxInst.commit();
    return order;
  } catch (e) {
    trxInst.rollback();
    throw new Error(`Failed to fetch order, error: ${e}`);
  }
}
..

我理解正确吗?

另一个实际需要事务的示例函数:

async cancelOrder(id: string) {
  const trxInst = await trx;
  try {
    trxInst('orders').update({ status: 'CANCELED' }).where({ id });
    trxInst('active_orders').delete().where({ orderId: id });
    trxInst.commit();
  } catch (e) {
    trxInst.rollback();
    throw new Error(`Failed to cancel order, error: ${e}`);
  }
}

有人可以确认我是否理解正确吗?更重要的是,如果这是做到这一点的好方法。或者有没有我遗漏的最佳实践?

感谢您的帮助 knex 团队!

【问题讨论】:

    标签: node.js postgresql typescript knex.js


    【解决方案1】:

    没有。您不能让全局单例类为您的所有内部函数返回事务。否则,您会尝试始终对尝试在应用程序中执行不同操作的所有并发用户使用相同的事务。

    此外,当您一旦提交/回滚提供者返回的事务时,它将不再适用于其他查询。交易提供者只能给你单笔交易。

    事务提供程序在以下情况下很有用来自游泳池。

    做你的事情的好方法是传递事务或一些请求上下文或用户会话,以便每个并发用户可以拥有自己的单独事务。

    例如:

    async cancelOrder(trxInst, id: string) {
      try {
        trxInst('orders').update({ status: 'CANCELED' }).where({ id });
        trxInst('active_orders').delete().where({ orderId: id });
        trxInst.commit();
      } catch (e) {
        trxInst.rollback();
        throw new Error(`Failed to cancel order, error: ${e}`);
      }
    }
    

    根据调用 getOrderById 的函数,它要么传递一个 trx 对象,要么不传递。上面的函数如果不为null,就会使用trx。

    一开始这似乎很简单,但它会导致错误,如果您在一个函数中的事务中并调用另一个不使用事务的函数,knex 将挂起著名的 Knex:超时获取连接.池可能已满。

    我们通常这样做的方式是,如果 trx 为 null,则查询会抛出错误,因此您需要显式传递 knex / trx 才能执行该方法,并且在某些方法中实际上需要传递 trx .

    无论如何,如果您真的想在默认情况下强制所有内容在会话中通过单个事务,您可以创建 API 模块,为每个用户会话创建一个使用事务初始化的 API 实例:

    const dbForSession = new DbService(trxProvider);
    
    const users = await dbForSession.allUsers();
    

    .allUsers() 做了类似return this.trx('users'); 的事情

    【讨论】:

      猜你喜欢
      • 2023-03-14
      • 1970-01-01
      • 1970-01-01
      • 2019-04-13
      • 1970-01-01
      • 2018-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多