这是使用 Neo4j 编写多语句事务的正确方法吗? [英] Is this the proper way to write a multi-statement transaction with Neo4j?

查看:40
本文介绍了这是使用 Neo4j 编写多语句事务的正确方法吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很难理解 Neo4j 中关于交易的文档.他们的文档似乎表明更喜欢以这种方式进行操作,而不是明确声明 tx.commit()tx.rollback().

对于多语句事务和 neo4j-driver,这看起来是最佳实践吗?

const register = async (container, user) =>{const session = driver.session()const 时间戳 = Date.now()const saltRounds = 10const pwd = await utils.bcrypt.hash(user.password, saltRounds)尝试 {//开始注册交易const registerUser = session.writeTransaction(async (transaction) => {const initialCommit = 等待交易.run(`创建(p:人{电子邮件:'${user.email}',电话:'${user.tel}',密码:'${密码}',已创建:'${timestamp}'})返回 p 为人`)const initialResult = initialCommit.records.map((x) => {返回 {id: x.get('Person').identity.low,创建:x.get('Person').properties.created}}).转移()//生成串口const 数据 = `${initialResult.id}${initialResult.created}`const serial = crypto.sha256(data)const finalCommit = 等待交易.run(`匹配(p:人)WHERE p.email = '${user.email}'SET p.serialNumber = '${serial}'返回 p 为人`)const finalResult = finalCommit.records.map((x) => {返回 {serialNumber: x.get('Person').properties.serialNumber,电子邮件:x.get('Person').properties.email,电话:x.get('人').properties.tel}}).转移()//合并两个结果以获得完整的人物数据返回 Object.assign({}, initialResult, finalResult)})//提交或回滚事务返回注册用户.then((提交) => {session.close()返回提交}).catch((回滚) => {console.log(`交易问题:${JSON.stringify(rollback, null, 2)}`)抛出 [`reg1`]})} 捕捉(错误){session.close()抛出错误}}

<块引用>

这是逻辑的简化版本:

const register = (user) =>{const session = driver.session()const performTransaction = session.writeTransaction(async (tx) => {const statementOne = await tx.run(queryOne)const resultOne = statementOne.records.map((x) => x.get('node')).slice()//做一些使用来自 statementOne 的数据的工作const statementTwo = 等待 tx.run(queryTwo)const resultTwo = statementTwo.records.map((x) => x.get('node')).slice()//做最后的处理返回最终结果})返回 performTransaction.then((commit) => {session.close()返回提交}).catch((回滚) => {抛出回滚})}

Neo4j 高手,以上代码是否正确使用了neo4j-driver?

<块引用>

我宁愿这样做,因为它更线性和同步:

const register = (user) =>{const session = driver.session()const tx = session.beginTransaction()const statementOne = await tx.run(queryOne)const resultOne = statementOne.records.map((x) => x.get('node')).slice()//做一些使用来自 statementOne 的数据的工作const statementTwo = 等待 tx.run(queryTwo)const resultTwo = statementTwo.records.map((x) => x.get('node')).slice()//做最后的处理const finalResult = { obj1, ... obj2 }让成功=真如果(成功){tx.commit()session.close()返回最终结果} 别的 {tx.rollback()session.close()返回假}}

很抱歉发了这么长的帖子,但我在任何地方都找不到任何参考资料,因此社区需要这些数据.

解决方案

经过大量工作后,这是我们为多语句事务确定的语法:

  1. 开始会话
  2. 开始交易
  3. 在之后使用 try/catch 块(以在 catch 块中启用适当的范围)
  4. 在 try 块中执行查询
  5. 在 catch 块中回滚

.

 const someQuery = async() =>{const session = Neo4J.session()const tx = session.beginTransaction()尝试 {const 道具 = {一:'鲍勃',二:'爱丽丝'}const tx1 = 等待 tx.run(`MATCH (n:Node)-[r:REL]-(o:Other)WHERE n.one = $props.oneAND n.two = $props.two返回 n 为一,o 为二`, { 道具 }).then((结果) => {返回 {数据: '...'}}).catch((错误) => {throw '第一个查询中的问题.' + e})//使用 tx1 做一些工作常量更新道具 = {_id: 3,四:‘优秀’}const tx2 = 等待 tx.run(`匹配(n:节点)WHERE id(n) = toInteger($updatedProps._id)SET n.four = $updatedProps.four返回 n 为一,o 为二`, { 更新道具 }).then((结果) => {返回 {数据: '...'}}).catch((错误) => {throw '第二个查询中的问题.' + e})//使用 tx2 做一些工作如果(问题)抛出尽快回滚".等待 tx.commitsession.close()返回 Object.assign({}, tx1, { tx2 })}赶上(e){tx.rollback()session.close()抛出'someQuery#' + e}}

我会注意到,如果您将数字传递给 Neo4j,您应该使用 toInteger() 将它们包装在 Cypher Query 中,以便正确解析它们.

我还包括查询参数的示例以及如何使用它们.我发现它稍微清理了代码.

除此之外,您基本上可以根据需要在事务中链接任意数量的查询,但请记住两件事:

  1. Neo4j 在事务期间写锁定所有涉及的节点,因此如果您有多个进程都在同一个节点上执行操作,您将看到一次只有一个进程可以完成事务.我们制定了自己的业务逻辑来处理写入问题,甚至选择不使用事务.到目前为止,它运行良好,在大约 30 秒内编写了 100,000 个节点并创建了 100,000 个关系,分布在 10 个进程中.完成一笔交易所需的时间要长 10 倍.使用 UNWIND 时,我们不会遇到死锁或竞争条件.
  2. 你必须等待 tx.commit() 否则它不会在它破坏会话之前提交.

我的观点是,如果您使用的是 Polyglot(多个数据库)并且需要创建一个节点,然后将文档写入 MongoDB,然后在节点上设置 Mongo ID,则这种类型的事务效果很好.

很容易推理,并根据需要进行扩展.

I am having a hard time interpretting the documentation from Neo4j about transactions. Their documentation seems to indicate preference to doing it this way rather than explicitly declaring tx.commit() and tx.rollback().

Does this look best practice with respect to multi-statement transactions and neo4j-driver?

const register = async (container, user) => {
    const session = driver.session()
    const timestamp = Date.now()

    const saltRounds = 10
    const pwd = await utils.bcrypt.hash(user.password, saltRounds)

    try {
        //Start registration transaction
            const registerUser = session.writeTransaction(async (transaction) => {
            const initialCommit = await transaction
                .run(`
                    CREATE (p:Person {
                        email: '${user.email}',
                        tel: '${user.tel}',
                        pwd: '${pwd}',
                        created: '${timestamp}'
                    })
                    RETURN p AS Person
                `)

            const initialResult = initialCommit.records
                .map((x) => {
                    return {
                        id: x.get('Person').identity.low,
                        created: x.get('Person').properties.created
                    }
                })
                .shift()

            //Generate serial
            const data = `${initialResult.id}${initialResult.created}`
            const serial = crypto.sha256(data)

            const finalCommit = await transaction
                .run(`
                    MATCH (p:Person)
                    WHERE p.email = '${user.email}'
                    SET p.serialNumber = '${serial}'
                    RETURN p AS Person
                `)

            const finalResult = finalCommit.records
                .map((x) => {
                    return {
                        serialNumber: x.get('Person').properties.serialNumber,
                        email: x.get('Person').properties.email,
                        tel: x.get('Person').properties.tel
                    }
                })
                .shift()

            //Merge both results for complete person data
            return Object.assign({}, initialResult, finalResult)
        })

        //Commit or rollback transaction
        return registerUser
            .then((commit) => {
                session.close()
                return commit
            })
            .catch((rollback) => {
                console.log(`Transaction problem: ${JSON.stringify(rollback, null, 2)}`)
                throw [`reg1`]
            })
    } catch (error) {
    session.close()
        throw error
    }
}

Here is the reduced version of the logic:

const register = (user) => {
    const session = driver.session()
    const performTransaction = session.writeTransaction(async (tx) => {

        const statementOne = await tx.run(queryOne)
        const resultOne = statementOne.records.map((x) => x.get('node')).slice()

        // Do some work that uses data from statementOne

        const statementTwo = await tx.run(queryTwo)
        const resultTwo = statementTwo.records.map((x) => x.get('node')).slice()

        // Do final processing

        return finalResult
    })

    return performTransaction.then((commit) => {
           session.close()
           return commit
    }).catch((rollback) => {
            throw rollback
    })
}

Neo4j experts, is the above code the correct use of neo4j-driver ?

I would rather do this because its more linear and synchronous:

const register = (user) => {
    const session = driver.session()
    const tx = session.beginTransaction()

    const statementOne = await tx.run(queryOne)
    const resultOne = statementOne.records.map((x) => x.get('node')).slice()

    // Do some work that uses data from statementOne

    const statementTwo = await tx.run(queryTwo)
    const resultTwo = statementTwo.records.map((x) => x.get('node')).slice()

    // Do final processing
    const finalResult = { obj1, ...obj2 }
    let success = true

   if (success) {
       tx.commit()
       session.close()
       return finalResult
   } else {
       tx.rollback()
       session.close()
       return false
   }
}

I'm sorry for the long post, but I cannot find any references anywhere, so the community needs this data.

解决方案

After much more work, this is the syntax we have settled on for multi-statement transactions:

  1. Start session
  2. Start transaction
  3. Use try/catch block after (to enable proper scope in catch block)
  4. Perform queries in the try block
  5. Rollback in the catch block

.

 const someQuery = async () => {
     const session = Neo4J.session()
     const tx = session.beginTransaction()
     try {
         const props = {
             one: 'Bob',
             two: 'Alice'
         }
         const tx1 = await tx
             .run(`
                 MATCH (n:Node)-[r:REL]-(o:Other)
                 WHERE n.one = $props.one
                 AND n.two = $props.two
                 RETURN n AS One, o AS Two
             `, { props })
             .then((result) => {
                 return {
                     data: '...'
                 }
             })
             .catch((err) => {
                 throw 'Problem in first query. ' + e
             })

         // Do some work using tx1
         const updatedProps = {
             _id: 3,
             four: 'excellent'
         }

         const tx2 = await tx
             .run(`
                 MATCH (n:Node)
                 WHERE id(n) = toInteger($updatedProps._id)
                 SET n.four = $updatedProps.four
                 RETURN n AS One, o AS Two
             `, { updatedProps })
             .then((result) => {
                 return {
                     data: '...'
                 }
             })
             .catch((err) => {
                 throw 'Problem in second query. ' + e
             })

         // Do some work using tx2
         if (problem) throw 'Rollback ASAP.'

         await tx.commit
         session.close()
         return Object.assign({}, tx1, { tx2 })
     } catch (e) {
         tx.rollback()
         session.close()
         throw 'someQuery# ' + e
     }
 }

I will just note that if you are passing numbers into Neo4j, you should wrap them inside the Cypher Query with toInteger() so that they are parsed correctly.

I included examples of query parameters also and how to use them. I found it cleans up the code a little.

Besides that, you basically can chain as many queries inside the transaction as you want, but keep in mind 2 things:

  1. Neo4j write-locks all involved nodes during a transaction, so if you have several processes all performing operations on the same node, you will see that only one process can complete a transaction at a time. We made our own business logic to handle write issues and opted to not even use transactions. It is working very well so far, writing 100,000 nodes and creating 100,000 relationships in about 30 seconds spread over 10 processes. It took 10 times longer to do in a transaction. We experience no deadlocking or race conditions using UNWIND.
  2. You have to await the tx.commit() or it won't commit before it nukes the session.

My opinion is that this type of transaction works great if you are using Polyglot (multiple databases) and need to create a node, and then write a document to MongoDB and then set the Mongo ID on the node.

It's very easy to reason about, and extend as needed.

这篇关于这是使用 Neo4j 编写多语句事务的正确方法吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆