java - InnoDB 数据库锁定会阻塞线程

标签 java mysql multithreading hibernate

我正在处理的项目遇到一个非常奇怪的问题。如果有人能在这里为我指明正确的方向,我将不胜感激。

//设置

有多个 Web 服务器,负载均衡器位于它们前面。服务器正在处理可能来自多个部分的请求,并且部分可以由不同的服务器处理。这些多部分请求应合并为一个事务,一旦收到所有部分,该事务就会继续进行。 进行最终处理的服务器并不重要,但只有一台服务器可以完成。接收先前部分的其他服务器应该只标记接收到的部分,存储数据并立即给出响应。

现在我使用数据库表来处理节点之间的同步。 基本思想是,当服务器获取一部分时,它会尝试使用请求中附带的事务 ID 来获取锁。这是通过尝试将行插入到以 txid 作为主键的 Lock 表来完成的。如果插入成功,该服务器将获取锁并处理它收到的部分,将其存储到数据库中,检查是否已收到其他部分,如果没有收到,则立即返回响应。

//问题

我遇到的问题是线程似乎随机锁定数据库,从而卡住整个处理。我已经对其进行了调试,发现在多个请求同时处理的情况下,它们只是在尝试获取锁时陷入困境,并最终在 30 秒后超时。第一个请求中很少有可能会得到处理,也可能不会被处理,这似乎是随机的,但即使是 7 个并发请求也会阻塞数据库。

对我来说,这应该不会陷入困境,而且我还没有什么想法。

//信息

我正在使用带有 InnoDB 引擎的 MySQL。服务器运行 Java 代码,Hibernate 用作 ORM 层来访问数据库。

锁定表:

CREATE TABLE `lock` (
    `id` varchar(255) NOT NULL,
    `expiryDate` datetime DEFAULT NULL,
    `issueDate` datetime DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

id 是用于组合各个部分的事务 ID。

我有一个管理锁访问的基本界面。

public interface LockProviderDao {

    public boolean lock(String id);
    public boolean unlock(String id);
}

以及使用 Hibernate 访问数据库的该类的实现。

@Override
public boolean lock(String id) {
    Session session = this.sessionFactory.openSession();
    Lock lock = new Lock(id);
    Transaction tx = null;
    boolean locked = false;

    try {
        // Try to lock
        tx = session.beginTransaction();

        session.save(lock);
        tx.commit();
        locked = true;

    } catch(Exception e) {
        if(tx != null) {
            tx.rollback();
        }

    } finally {
        session.close();
    }

    return locked;
}

@Override
public boolean unlock(String id) {
    Session session = this.sessionFactory.openSession();
    boolean status = true;
    Transaction tx = null;
    try {
        Lock lock = (Lock) session.load(Lock.class, id);

        tx = session.beginTransaction();
        session.delete(lock);
        tx.commit();
    } catch(Exception e) {
        if(tx != null) {
            tx.rollback();
        }
        status = false;

    } finally {
        session.close();
    }

    return status;
}

看起来很简单。这是进行处理的代码。该线程已经打开了一个 Hibernate session ,因此在锁定和解锁方法中打开的 session 是一个嵌套 session (如果这有什么区别的话)。

int counter = 0;
boolean lockAcquired = false;
do {
    // Try to acquire the lock
    lockAcquired = this.lockProviderDao.lock(txId);

    if (!lockAcquired) {
        // Didn't get it try a bit later
        try {
            Thread.sleep(defaultSleepPeriod);
        } catch (Exception e) {
        }

        if (counter >= defaultSleepCycles) {
            return;
        }
        counter++;
    }

} while (!lockAcquired);

// DO THE PROCESSING HERE ONCE LOCK ACQUIRED

// Release the lock
this.lockProviderDao.unlock(txId);

最佳答案

我会插入数据后锁定。这意味着,您必须将算法更改为如下所示:

  1. 开始交易
  2. 将片段插入数据库
  3. 提交交易
  4. 开始交易
  5. 如果不等于预期的片段计数,则计算插入/退出的片段数量
  6. 插入一行,表示将处理片段(例如您的锁定行)。如果失败,则片段已被处理或正在处理(=回滚)
  7. 提交交易
  8. 开始交易
  9. 读取片段(并验证它们是否仍然存在)
  10. 处理片段
  11. 删除锁和片段(验证它们仍然存在)
  12. 提交交易

如果您需要提高可靠性,您有以下三种选择: 1.使用JMS配合JTA来控制程序流程 2. 如果已收到所有部件,但处理尚未开始或已停止,让您的客户端轮询服务器状态并开始处理 3. 创建一个调度程序,如果适用相同的条件,则开始处理

关于java - InnoDB 数据库锁定会阻塞线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25264473/

相关文章:

Mysql前3个计数值

mysql - onDelete 级联不适用于 "one to one"- Sequelize.js

c++ - std::thread 类方法错误

c++ - 多线程线程切换问题

java - 超出范围异常错误

java - Android日历,获取事件ID

mysql - 表中不存在键列,但我发誓它确实存在

java - 如何通知一个线程,另一个线程已经完成了它的执行?

java - 为什么我们需要多个构造函数来在 Java 中定义二叉树?

java - 使用构建器模式最多设置一次值