第一个事务阻塞数据库(SAVEPOINT和连接丢失)

时间:2019-07-11 12:34:52

标签: node.js transactions pg-promise

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

我使用pg-promise事务插入“设备”及其所有部分(例如系统,磁盘等)。交易有效... 但仅是第一次。之后,我的服务器将无法再与数据库进行交互(无论是插入还是选择)。

这是具有以下步骤的pg-monitor输出

Thu Jul 11 2019 14:26:57 GMT+0200 (GMT+02:00) : server is listening on 9000
14:27:11 connect(hidden@hidden); useCount: 0
14:27:11 insert into "public"."roles"("name") values('Test') RETURNING *
14:27:11 disconnect(hidden@hidden)
14:27:15 connect(hidden@hidden); useCount: 1
14:27:15 insert into "public"."roles"("name") values('Test2') RETURNING *
14:27:15 disconnect(hidden@hidden)
14:27:18 connect(hidden@hidden); useCount: 2
14:27:18 tx(Insert-New-Device)/start
14:27:18 tx(Insert-New-Device): begin
14:27:18 tx(Insert-New-Device): insert into "public"."devices"("smanufacturer") values('HP') RETURNING *
14:27:18 tx(Insert-New-Device): insert into "public"."systems"("deviceid","distributionid","archid","smanufacturer") values(15,3,2,'Microsoft Corporation') RETURNING *
14:27:18 tx(Insert-New-Device): commit
14:27:18 tx(Insert-New-Device)/end; duration: .046, success: true
14:27:18 disconnect(hidden@hidden)
14:27:20 connect(hidden@hidden); useCount: 3
14:27:20 tx(Insert-New-Device)/start
14:27:20 tx(Insert-New-Device): savepoint level_1
14:27:20 error: SAVEPOINT can only be used in transaction blocks
         tx(Insert-New-Device): savepoint level_1
14:27:20 tx(Insert-New-Device)/end; duration: .011, success: false
14:27:20 disconnect(hidden@hidden)

错误

  1. devices.add
      

    错误:SAVEPOINT只能在事务块中使用

  2. roles.add
      

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

编辑:发现了问题

问题出在我的存储库中。在pg-promise-demo中,每个存储库都导出类,因此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
}

1 个答案:

答案 0 :(得分:0)

我不认为您显示的是完整的代码,因为在pg-promise交易级别中,您所遇到的问题类型是不可能的。它无法在事务外执行SAVEPOINT

但是,有一种方法可以破解它,这可能会破坏这种库,而且我高度怀疑这是您所做的,以某种方式...

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

以这种方式完成,因此,当回调完成时,上下文将自动销毁。而且,如果您以某种方式在回调函数之外公开了该上下文,并开始对其执行查询,则会破坏连接上下文逻辑。

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

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