Multithreading “圣杯”;行被另一个事务更新或删除(或未保存的值映射不正确)";

Multithreading “圣杯”;行被另一个事务更新或删除(或未保存的值映射不正确)";,multithreading,grails,gorm,database-concurrency,Multithreading,Grails,Gorm,Database Concurrency,Grails(2.3.7)中似乎没有明确的并发问题解决方案。我尝试了所有的建议,但当我增加并发线程的数量时,以下代码总是失败: package simpledb import grails.transaction.Transactional import groovy.transform.Synchronized import org.apache.commons.logging.LogFactory @Transactional class OwnerService { priva

Grails(2.3.7)中似乎没有明确的并发问题解决方案。我尝试了所有的建议,但当我增加并发线程的数量时,以下代码总是失败:

package simpledb

import grails.transaction.Transactional
import groovy.transform.Synchronized
import org.apache.commons.logging.LogFactory

@Transactional
class OwnerService {
    private static final myLock1 = new Object()
    private static final myLock2 = new Object()

    @Synchronized('myLock1')
    static public saveOwner(def ownerName) {
        def ownerInstance = null
        Owner.withNewTransaction {
            ownerInstance = Owner.findOrCreateByName(ownerName)
            ownerInstance.save(failOnError: true, flush: true)
        }
        ownerInstance
    }

    @Synchronized('myLock2')
    static public associateDog(def ownerId, def dogId) {
        def lockedOwnerInstance
        Owner.withNewTransaction {
            lockedOwnerInstance = Owner.lock(ownerId)
            def lockedDogInstance = Dog.lock(dogId)
            lockedOwnerInstance.addToDogs(lockedDogInstance)
            lockedOwnerInstance.save(failOnError: true, flush: true)
        }
        lockedOwnerInstance
    }
}
它在“def lockedDogInstance=Dog.lock(dogId)”行失败:

上述设计非常简单,因为主人和狗之间存在多对多关系:

狗类:

package simpledb

class Dog {
    String name
    Breed breed = null
    Integer age = null
    static hasMany = [owners: Owner]
    static belongsTo = Owner
    static mapping = { owners lazy: false }
    static constraints = {
        name blank: false, nullable: false, unique: true
        breed nullable: true
        age nullable: true
    }
}
所有者类别:

package simpledb

class Owner {
    String name;
    static hasMany = [dogs: Dog]
    static mapping = { dogs lazy: false }
    static constraints = {
    }
}
仅供参考-数据库是MySQL


有什么建议吗?

好的,这里有很多建议,我打赌大部分都可以处理掉。因此,与其试图修复它,不如让我们将其降至最低限度,然后从那里开始:

  • 您的服务方法不应该是静态的
  • 您的服务已经是事务性的,因此
    withNewTransaction()
    可以运行。你也不需要冲洗
  • 不需要同步服务方法
  • 您不需要锁定
    Dog
    ,因为您没有更改它(将它添加到
    Owner.dogs
    只会在联接表中创建一条记录)
  • 通过这些更改,您的服务最终会变成这样:

    package simpledb
    
    import grails.transaction.Transactional
    import org.apache.commons.logging.LogFactory
    
    @Transactional
    class OwnerService {
    
        def saveOwner(def ownerName) {
            def ownerInstance = Owner.findOrCreateByName(ownerName)
    
            ownerInstance.save(failOnError: true)
            ownerInstance
        }
    
        def associateDog(def ownerId, def dogId) {
            def ownerInstance = Owner.lock(ownerId)
            def dogInstance = Dog.read(dogId)
    
            ownerInstance.addToDogs(dogInstance)
            ownerInstance.save(failOnError: true)
            ownerInstance
        }
    }
    

    看看你能走多远。您甚至可以删除所有者锁。

    除了@Emmanuel Rosa所说的之外,如果有太多的并发更新发生,您是否可以确保在保存之前调用“刷新”(关于所有者)?(方法)

    但是,联接表的添加不应该受到这些问题的影响。只有当一些狗试图被“重新添加”到相同的主人身上时,它才可能导致问题


    另一种方法(不是在本例中,而是在本例中)如果只更新一列或两列,则可以使用纯SQL。

    感谢@Emmanuel Rosa的回复。我做了您推荐的更改(这是我开始做的)。但是,我继续收到与前面列出的相同的错误消息。与我上面列出的代码中偶尔出现的线程相比,这次多个线程在此错误上失败。不知何故,当涉及到并发数据库修改时,Grails无法按文档所述工作。您的应用程序的哪些方面使多个线程能够尝试更新同一个域模型?应用程序本质上是接受带有JSON负载的REST请求,并将其持久化到数据库中。但是,在创建记录之前,我们需要检查数据是否已经存在,在这种情况下,需要更新数据。我正在使用JMeter测试代码,因为它允许模拟多线程调用。但是,如果您发出REST POST来创建记录,并且由于记录已经存在而失败,那么它应该以失败作为响应,以便客户机可以返回并发出PUT来更新它。如果由于记录更改导致PUT失败,则客户端应重新尝试PUT;最多n次,然后最终放弃。我应该进一步阐述。不同的客户端同时发出多个请求。如果json负载包含数据库中不存在的某个键,则需要创建一行,并使用对该行的引用来填充其他表。如果键已经存在,则检索引用并填充其他表。希望有帮助。
    package simpledb
    
    import grails.transaction.Transactional
    import org.apache.commons.logging.LogFactory
    
    @Transactional
    class OwnerService {
    
        def saveOwner(def ownerName) {
            def ownerInstance = Owner.findOrCreateByName(ownerName)
    
            ownerInstance.save(failOnError: true)
            ownerInstance
        }
    
        def associateDog(def ownerId, def dogId) {
            def ownerInstance = Owner.lock(ownerId)
            def dogInstance = Dog.read(dogId)
    
            ownerInstance.addToDogs(dogInstance)
            ownerInstance.save(failOnError: true)
            ownerInstance
        }
    }