如何正确处理锁等待超时?

3
我正在运行一个导入作业,之前一直运行得很好,但几天前实体数量急剧增加后,出现了锁等待超时的问题。应用程序然后重试,但由于我再次调用了 em.getTransaction().begin() ,所以会抛出异常。
为解决这个问题,我将 innodb_lock_wait_timeout 更改为120,并将批处理大小降低到50个实体。
我无法确定如何在代码中正确处理所有这些。我不想因为锁定而使整个导入失败。你会如何处理?有没有示例代码?或者有其他想法?请尽情发挥! 我的BatchPersister:
public class BatchPersister implements Persister {

    private final static Log log = getLog(BatchPersister.class);
    private WorkLogger workLog = WorkLogger.instance();

    private static final int BATCH_SIZE = 500;

    private int persistedObjects;
    private long startTime;
    private UpdateBatch batch;
    private String dataSource;


    public BatchPersister(String dataSource) {
        this.dataSource = dataSource;        
    }

    public void persist(Persistable obj) {

        persistedObjects++;
        logProgress(100);

        if (batch == null)
            batch = new UpdateBatch(BATCH_SIZE, dataSource);

        batch.add(obj);

        if (batch.isFull()) {
            batch.persist();
            batch = null;
        }
    }
}

UpdateBatch

public class UpdateBatch {

    private final static Log log = LogFactory.getLog(UpdateBatch.class);
    private WorkLogger workLogger = WorkLogger.instance();

    private final Map<Object, Persistable> batch;
    private final EntityManager em;
    private int size;

    /**
     * Initializes the batch and specifies its size.
     */
    public UpdateBatch(int size, String dataSource) {
        this.size = size;
        batch = new LinkedHashMap<Object, Persistable>();
        em = EmFactory.getEm(dataSource);
    }    

    public void persist() {
        log.info("Persisting " + this);
        em.getTransaction().begin();    
        persistAllToDB();
        em.getTransaction().commit();

        WorkLog batchLog = new WorkLog(IMPORT_PERSIST, IN_PROGRESS);
        batchLog.setAffectedItems(batch.size());
        workLogger.log(batchLog);
        em.close();
   }

/**
  * Persists all data in this update batch
  */
    private void persistAllToDB() {
        for (Persistable persistable : batch.values())
            em.persist(persistable);
        }

        @Override
        public String toString() {
            final ArrayList<Persistable> values = new ArrayList<Persistable>(batch.values());
            Persistable first = values.get(0);
            Persistable last = values.get(values.size() - 1);
            return "UpdateBatch[" +
                first.getClass().getSimpleName() + "(" + first.getId() + ")" +
                " - " +
                last.getClass().getSimpleName() + "(" + last.getId() + ")" +
                "]";
         }
    }
}
2个回答

1

解决方案1。 不要使用JPA,它并不是为大规模数据库操作而设计的。由于您可以访问自己的DataSource并手动管理事务,因此没有任何阻止您使用普通的SQL语句。

解决方案2。 与持久性上下文一级缓存相关的性能问题可能会导致每个持久化实体都保存在该缓存中,当该缓存变得庞大时,它可能会影响性能(主要是内存)

为了改善这种情况,请将hibernate.jdbc.batch_size属性(或等效属性,如果您未使用Hibernate实现的JPA)设置为20左右 - 这样查询将以20个查询包的形式发送到数据库。

其次,每20个操作清除一次持久性上下文,强制与数据库同步。

private void persistAllToDB() {
    int counter = 0;
    for (Persistable persistable : batch.values())
        em.persist(persistable);
        counter++;
        if(counter % 20 == 0){
           em.flush();
           em.clear();
        }
    }
}

解决方案3. 调整MySQL InnoDB引擎[http://dev.mysql.com/doc/refman/5.1/en/insert-speed.html,http://dev.mysql.com/doc/refman/5.0/en/innodb-tuning.html]。如果您的表有大量索引,可能会影响插入性能。

这是我的猜测,希望对您有所帮助。


0

Pitor已经提出了几个选项。我想指出的是,他的“解决方案2”的一个变体是利用Hibernate StatelessSession API而不是使用Session和清除。

然而,你应该考虑的另一件事是,事务是一组语句,这些语句预计会全部成功或失败。如果你有一堆语句,其中一个中间失败了,而你希望所有前面的语句都是持久的,那么你不应该将它们组合在一个单独的事务中。正确地将你的语句分组在事务中。通常,在Hibernate中启用jdbc批处理是一个好主意;它通常会导致更有效率的数据库通信。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接