2013-04-17 47 views
0

我支持爲其客戶端提供某種文件系統的真實世界的Java應用程序(Web服務)。單個文件系統樹的所有元數據都保存在數據庫中。現在,當「太多」併發更新發生在給定的樹上時,由於隱式行級寫入鎖,基礎數據庫事務會發生死鎖情況。樹節點上的JPA併發更新導致事務死鎖

我交叉閱讀Joe Celko's Trees and Hierarchies in SQL for Smarties以及給定實體模型的結果是如何在SQL中表示樹的最簡單情況,稱爲鄰接列表。儘管我喜歡Celko先生的嵌套集合模式,但我擔心這在JPA中很難實現,即使頻繁插入也會導致大量數據重組開銷。

正在使用的數據庫是MySQL,使用的庫包括Spring-Data-JPA和Hibernate 4.1.7。

由於原始代碼非常複雜,我提取了一個最小的測試用例。看看下面。

這是表示樹節點的實體:

@Entity 
public class Node extends AbstractPersistable<Integer> { 

    private static final Logger logger = LoggerFactory.getLogger(Node.class); 

    @ManyToOne 
    private Node parent; 

    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true) 
    private Set<Node> children = new LinkedHashSet<Node>(); 

    @Column(nullable = false) 
    @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentInstantAsMillisLong") 
    private Instant timeStamp = Instant.now(); 

    @Version 
    private Long version; 

    public Node addChild(Node child) { 
     child.setParent(this); 
     children.add(child); 
     touch(); 
     return this; 
    } 

    public void touch() { 
     doTouch(Instant.now()); 
    } 

    private void doTouch(Instant time) { 
     logger.info("touching {} to {}", this, time); 
     this.timeStamp = time; 
     if (parent != null) { 
      parent.doTouch(time); 
     } 
    } 
} 

這是我測試的情況下,模擬在樹上併發更新:

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(classes = SpringConfig.class) 
public class NodeServiceIntegrationTest { 

    @Inject 
    private NodeRepository repository; 

    @Inject 
    private NodeService service; 

    private Random random; 

    @Before 
    public void setUp() throws Exception { 
     this.random = new Random(); 
    } 

    @Test 
    public void testRecursiveUpdate() throws Exception { 
     int depth = random.nextInt(10) + 1; 
     Node root = new Node(); 
     final Node leaf = createHierarchy(root, depth); 
     root = repository.save(root); 

     int threadCount = 50; 
     Callable<Node> addChild = new Callable<Node>() { 
      @Override 
      public Node call() throws Exception { 
       return service.addChild(leaf.getId(), new Node()); 
      } 
     }; 
     List<Callable<Node>> tasks = Collections.nCopies(threadCount, addChild); 
     ExecutorService executorService = Executors.newFixedThreadPool(threadCount); 
     List<Future<Node>> futures = executorService.invokeAll(tasks); 
     List<Node> resultList = new ArrayList<Node>(futures.size()); 
     for (Future<Node> future : futures) { 
      resultList.add(future.get()); 
     } 
     // todo assert something... ;) 
    } 

    private Node createHierarchy(Node root, int depth) { 
     int count = random.nextInt(20) + 1; 
     for (int i = 0; i < count; i++) { 
      Node child = new Node(); 
      root.addChild(child); 
      if (depth > 0 && random.nextBoolean()) { 
       return createHierarchy(child, depth - 1); 
      } 
     } 
     return Iterables.get(root.getChildren(), count - 1); 
    } 

} 

這provoces同樣的錯誤我在看生產代碼太:

org.springframework.dao.CannotAcquireLockException: Deadlock found when trying to get lock; try restarting transaction; SQL [n/a]; nested exception is org.hibernate.exception.LockAcquisitionException: Deadlock found when trying to get lock; try restarting transaction 
    at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:639) 
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:104) 
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:516) 
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754) 
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723) 
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:394) 
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) 
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622) 
    at a.e.treetest.service.NodeService$$EnhancerByCGLIB$$98fc01a8.addChild(<generated>) 
    at a.e.treetest.service.NodeServiceIntegrationTest$1.call(NodeServiceIntegrationTest.java:54) 
    at a.e.treetest.service.NodeServiceIntegrationTest$1.call(NodeServiceIntegrationTest.java:51) 
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) 
    at java.util.concurrent.FutureTask.run(FutureTask.java:138) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918) 
    at java.lang.Thread.run(Thread.java:662) 

所以我的問題是是否有更好的方法來r在SQL數據庫中存在樹,以及支持頻繁插入而不引發死鎖的好方法。還是我不得不接受在併發更新時可能會發生死鎖,我應該考慮自動重試原始操作?

回答

0

嘗試使用Nested Set Model - 我從來沒有用它

Hibernate implementation

+0

我在基於您發佈的鏈路上實現嘗試中間遇到僵局的問題。由於似乎沒有更多的答案進來,我會接受你的未經考驗。謝謝。 – aeisele