【问题标题】:first transaction blocking DB (SAVEPOINT and connection lost)第一个事务阻塞数据库(SAVEPOINT 和连接丢失)
【发布时间】:2019-11-21 04:20:42
【问题描述】:

如有必要,我可以缩短我的帖子

我使用 pg-promise 事务来插入“设备”及其所有部分(如系统、磁盘等)。交易有效...但只是第一次。之后,我的服务器无法再与数据库交互(既不能插入也不能选择)。

这是包含这些步骤的 pg-monitor 输出

2019 年 7 月 11 日星期四 14:26:57 GMT+0200 (GMT+02:00):服务器正在监听 9000 14:27:11 连接(隐藏@隐藏);使用次数:0 14:27:11 插入 "public"."roles"("name") values('Test') 返回 * 14:27:11 断开连接(隐藏@隐藏) 14:27:15 连接(隐藏@隐藏);使用次数:1 14:27:15 插入 "public"."roles"("name") values('Test2') 返回 * 14:27:15 断开连接(隐藏@隐藏) 14:27:18 连接(隐藏@隐藏);使用次数:2 14:27:18 tx(插入新设备)/开始 14:27:18 tx(插入新设备):开始 14:27:18 tx(插入新设备):插入“公共”。“设备”(“制造商”)值(“HP”)返回* 14:27:18 tx(Insert-New-Device): 插入“public”.“systems”(“deviceid”,,"distributionid","archid","smanufacturer") values(15,3,2,'Microsoft公司') 返回 * 14:27:18 tx(插入新设备):提交 14:27:18 tx(插入新设备)/结束;持续时间:0.046,成功:真 14:27:18 断开连接(隐藏@隐藏) 14:27:20 连接(隐藏@隐藏);使用次数:3 14:27:20 tx(插入-新设备)/开始 14:27:20 tx(插入新设备):保存点 level_1 14:27:20 错误:SAVEPOINT 只能在事务块中使用 tx(插入新设备):保存点 level_1 14:27:20 tx(插入新设备)/结束;持续时间:0.011,成功:假 14:27:20 断开连接(隐藏@隐藏)

错误

  1. devices.add

    错误:SAVEPOINT 只能在交易块中使用

  2. roles.add

    错误:查询已释放或丢失的连接

编辑:发现问题

问题出在我的存储库中。在 pg-promise-demo 中,每个 repos 都导出类,因此 DB 初始化使用扩展事件中的 new 关键字来创建它们。 我的回购不是课程。我试图将它们更改为类并且它可以工作

之前(不工作)

./db/repos/devices.js

'use strict';

var Database = null, pgp = null, Collections = null;

async function add(params) {
  return Database.tx('Insert-New-Device', async t => {
    let system = null;

    const query = pgp.helpers.insert(params.data.device, Collections.insert) + " RETURNING *";
    let device = await t.one(query);

    // if a system is present, insert with diviceId and return
    if(params.data.system) {
      params.data.system.deviceid = device.deviceid;
      system = await t.systems.InsertOne(params);
    }

    return {device, system};
  })
  .catch(ex => {
    throw ex;
  });
}

function createColumnsets() { /* hidden for brevity */ }

// rpc methods
const expose = {
  'devices.insert': add
}

const DevicesRepository = {
  expose,        // expose methods as "rpc methods"
  InsertOne: add // internal use (by another repo for example : Database.devices.InsertOne())
};

module.exports = (db, pgpLib) => {
  Database = db;
  pgp = pgpLib;
  Collections = createColumnsets();

  return DevicesRepository;
}

./db/index.js.js

'use strict';

const promise = require('bluebird');

const repos = {
  Roles: require('./repos/roles'),
  Systems: require('./repos/systems'),
  Devices: require('./repos/devices')
}
const config = require('./conf');

const initOptions = {
    promiseLib: promise,
    extend(obj, dc) {
        obj.roles = repos.Roles(obj, pgp);
        obj.systems = repos.Systems(obj, pgp);
        obj.devices = repos.Devices(obj, pgp);
    }
};

const pgp = require('pg-promise')(initOptions);
const monitor = require('pg-monitor');
monitor.attach(initOptions);
const db = pgp(config);

const methods = Object.assign({}, db.roles.expose, db.systems.expose, db.devices.expose );

module.exports = {
  methods
}

现在(正常工作)

devices.js

'use strict';

class RolesRepository {
  constructor(db, pgp) {
    this.Database = db;
    this.pgp = pgp;

    this.Collections = createColumnsets(pgp);

    this.expose = {
      'roles.insert': this.InsertOne.bind(this)
    }
  }

  makeInsertQuery(role) {
    return this.pgp.helpers.insert(role, this.Collections.insert);
  }

  async InsertOne(params) {
    let query = this.makeInsertQuery(params.data);
    if(params.return) query += " RETURNING *";

    return this.Database.any(query)
                    .then(data => { return data; })
                    .catch(ex => { throw ex; });
  }
}

function createColumnsets(pgp) { /* hidden for brevity */ }

module.exports = RolesRepository

./db/index.js

'use strict';
const promise = require('bluebird');

//const repos = require('./repos'); // ./repos/index.js
const repos = {
  Roles: require('./roles'),
  Systems: require('./systems'),
  Devices: require('./devices'),
};
const config = { /* hidden */ };

const initOptions = {
    promiseLib: promise,
    extend(obj, dc) {
        obj.roles = new repos.Roles(obj, pgp);
        obj.systems = new repos.Systems(obj, pgp);
        obj.devices = new repos.Devices(obj, pgp);
    }
};

const pgp = require('pg-promise')(initOptions);
const monitor = require('pg-monitor');
monitor.attach(initOptions);

const db = pgp(config);

// expose db methods as rpc call
const methods = Object.assign({},
    db.roles.expose,
    db.systems.expose,
    db.devices.expose,
);

module.exports = {
  methods
}

【问题讨论】:

  • 您是如何设法让库在事务之外执行保存点的?您的代码没有显示它。图书馆肯定不会这样做,这将违反所有逻辑。如果您可以创建步骤/示例来重现问题,那么您应该针对 pg-promise 库打开一个问题。
  • 我不认为我要求库这样做(我不想这样做)我可以在我的帖子中添加这些文件:服务器创建、数据库初始化、repos 方法展示,...或者我可以直接打开一个问题并提供它们吗?
  • 好吧,你已经设法破坏了库中多年来一直保持 100% 运行的部分。这就是为什么我很好奇你是怎么做到的。一定是非常不寻常的事情。因为库的逻辑绝对不允许它在事务之外执行保存点。

标签: node.js transactions pg-promise


【解决方案1】:

我不相信您显示的是完整的代码,因为您遇到的问题类型在 pg-promise 事务级别内是不可能的。它不能在事务之外执行SAVEPOINT

但是,有一种破解它的方法,它可能会以这种方式破坏库,我高度怀疑这是你所做的,不知何故......

当我们使用方法task 执行任务或通过方法tx 执行事务时,该方法会创建一个临时连接上下文,它会将其作为回调参数提供给您以执行查询。

就是这样完成的,所以当回调结束时,上下文会自动销毁。如果您以某种方式在回调函数之外公开该上下文,并开始针对它执行查询,那么您会破坏连接上下文逻辑。

打破它的一种方法是执行一个使用上下文的异步函数,而不是在回调完成时完成它。然后您可能会遇到这种类型的错误 - Querying against a released or lost connection,它告诉您上下文已消失/已释放,而您仍在尝试对其执行查询。

您需要确保不要在回调函数之外使用连接上下文,因为它在那里不可用,并且以这种方式使用它的结果可能无法预测。

【讨论】:

  • 我不会在tx 回调之外“导出”临时连接上下文。它是完整的代码(用于数据库交互),服务器直接调用 repo 方法并给它们请求的参数。我可以为您提供 pg-promise 初始化。我缩短了代码给你,我们不需要“rpc 服务器”部分来重现。无论如何,我都会将其与测试脚本一起使用。我可以在我要打开的 github 问题中给你一个 zip 吗?
  • 我发现问题出在哪里:我没有在我的 repo 中使用类(我用完整的 device.js repo 文件编辑了我的帖子)
  • @Firlfire 我相信通过不遵循存储库模式,您仍然以某种方式设法滥用连接上下文。因为否则不可能让图书馆像这样行为不端。
猜你喜欢
  • 2013-10-07
  • 2015-05-08
  • 1970-01-01
  • 2013-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-27
  • 1970-01-01
相关资源
最近更新 更多