2013-06-27 136 views
1

由于遗留代码问题,我需要手动计算唯一索引,并且在将新行插入数据库时​​不能使用auto_increment。交易开始时的锁定表

问题是多个客户端(不同机器)的多个插入可以同时发生。因此,当当前事务处于活动状态时,我需要锁定具有最高ID的行被其他事务读取。或者,我可以锁定整个表从任何读取。在这种情况下,时间不是问题,因为写入/读取非常少见(< 1 op /秒)

它试图将隔离级别设置为8(Serializable),但是随后MySQL会引发DeadLockException。有趣的是,确定下一个ID的SELECT仍然完成,这与我对可序列化的理解相矛盾。

同时将LockMode设置为PESSIMISTIC_READ的select,似乎没有帮助。

public void insert(T entity) { 
    EntityManager em = factory.createEntityManager(); 
    try { 
     EntityTransaction transaction = em.getTransaction(); 
     try { 
      transaction.begin(); 

      int id = 0; 
      TypedQuery<MasterDataComplete> query = em.createQuery(
        "SELECT m FROM MasterDataComplete m ORDER BY m.id DESC", MasterDataComplete.class); 
      query.setMaxResults(1); 
      query.setLockMode(LockModeType.PESSIMISTIC_READ); 
      List<MasterDataComplete> results = query.getResultList(); 
      if (!results.isEmpty()) { 
       MasterDataComplete singleResult = results.get(0); 

       id = singleResult.getId() + 1; 
      } 

      entity.setId(id); 

      em.persist(entity); 
      transaction.commit(); 
     } finally { 
      if (transaction.isActive()) { 
       transaction.rollback(); 
      } 
     } 
    } finally { 
     em.close(); 
    } 
} 

有些话到应用程序:
这是Java的独立,运行在其连接到同一个数据库服务器的多个客户端,它应该与多个DB服务器上运行(任何地方的Sybase,Oracle,MySQL等.. )

目前我剩下的唯一想法就是做插入操作,并捕获ID已经在使用时发生的异常,然后重试。这可以工作,因为我可以假定该列设置为主键/唯一。

+0

你有选择通过它实现一个服务并暴露数据库吗? –

+0

不可悲。整个架构是一片混乱(最多15年),所有客户端都直接连接到数据库。 当我现在要实现它时,我会选择一个连接到数据库和瘦客户端的中央服务器,它只需要从服务器请求并显示数据。但不幸的是,客户有超过20万的LOC,并且完全重写它不是一种选择。 :( – ssindelar

+0

每次生成一个唯一标识符怎么样?由基于时间的部分加上随机部分组成,或者加密安全的唯一标识符。没有事务隔离或乐观锁定问题,并且易于实现。 – Grzegorz

回答

0

的问题是,与PESSIMISTIC_READ你阻止别人UPDATE该行最高的ID。如果你想阻止其他的选择你需要使用PESSIMISTIC_WRITE

我知道这似乎很奇怪,因为你不会更新该行.. ..但如果你想要其他块,而执行一个SELECT时,你应该碱灰和说:“干草都.. ..我读这个行,并将更新它“..所以他们将不被允许读取该行数据库引擎认为你会在提交之前修改它。

SERIALIZABLE本身根据文档将所有普通SELECT语句转换为SELECT ...锁定在共享模式中,所以不会超过您明确已经在做的事情。