【问题标题】:JPA2: deadlock using native queryJPA2:使用本机查询的死锁
【发布时间】:2014-07-17 03:45:11
【问题描述】:

我正在尝试从表中获取记录。所以我使用以下本机查询(自连接)来获取。我在 Windows、EclipseLink JPA2 和 MySQL 上使用 Eclipse IDE。

    select * from mytable as detail join (select max(timestamp) as maxtimestamp from mytable where id=" + theUser.getId() + " group by hnumber order by maxtimestamp limit " + <offset> + "," + <iTotalRecords> + ") as topconv on detail.timestamp=topconv.maxtimestamp order by detail.timestamp

我遇到了多线程带来的死锁问题。我不确定是JPA死锁还是MySQL死锁。

我有以下 pojo 课程

    @Entity
    @Table(name="mytable")
    public class MyTable {

        @Id
        @GeneratedValue(strategy=GenerationType.SEQUENCE)
        @Column(name="mytableid")
        private long lMyTableId;

        @ManyToOne
        @JoinColumn(name="id", referencedColumnName="id")
        private User user; //this object has id, name and so on fields.

        @Column(name="timestamp", length=15, nullable=false)
        private String strTimestamp;

        @Column(name="hnumber", nullable=true)
        private String hNumber;

        //Getters and Setters.

    }

这是获取记录的方法

    public List<MyTable> fetchRecords(User theUser, int iTotalRecords, long offset) throws Exception {
        if(null == theUser) {
            throw new Exception("Invalid input.");
        }

        EntityManager entityManager = getEntityManager();
        if(false == entityManager.getTransaction().isActive()) {
            entityManager.getTransaction().begin();
        }

        try {
            Query query = entityManager.createNativeQuery("select * from mytable as detail join (select max(timestamp) as maxtimestamp from mytable where id=" + theUser.getId() + " group by hnumber order by maxtimestamp limit " + offset + "," + iTotalRecords + ") as topconv on detail.timestamp=topconv.maxtimestamp order by detail.timestamp", MyTable.class);
            @SuppressWarnings("unchecked")
            List<MyTable> resultList = query.getResultList();
            return resultList;
        } catch(Throwable th) {
            throw new Exception(th.getMessage());
        } finally {
            closeEntityManager();
        }
    }

获取和关闭EntityManager

    private EntityManagerFactory _entityManagerFactory = Persistence.createEntityManagerFactory("JPATest");
    protected EntityManager getEntityManager() {
        _entityManager = _entityManagerFactory.createEntityManager();
        return _entityManager;
    }

    protected void closeEntityManager() {
        try {
            if(null != _entityManager) {
                _entityManager.close();
            }
        } catch(Throwable th){}
    }

我的 persistence.xml 如下所示:

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
        <persistence-unit name="JPATest">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

    <class>com.company.service.db.pojo.User</class>
    <class>com.company.service.db.pojo.MyTable</class>

    <properties>
    <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"></property>
    <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/mydb"></property>
    <property name="javax.persistence.jdbc.user" value="root"></property>
    <property name="javax.persistence.jdbc.password" value="root"></property>

    <!-- EclipseLink should create the database schema automatically   -->
    <property name="eclipselink.ddl-generation" value="create-or-extend-tables" /> 
    <property name="eclipselink.ddl-generation.output-mode" value="database" />
    <property name="eclipselink.id-validation" value="NULL"></property>
    <property name="eclipselink.logging.level" value="FINE"/>
    <property name="javax.persistence.lock.timeout" value="1000"/>
    <property name="eclipselink.target-database" value="MySQL"/>
    </properties>

    </persistence-unit>
    </persistence>

我的应用程序支持多线程。

为了复制死锁,我运行了 100 个线程,每个线程执行 fetchRecords 方法。每次提取的记录接近200条或更多。

请告诉我如何解决这个问题。任何帮助表示赞赏。

【问题讨论】:

  • 症状是什么,是什么让您认为这是“JPA2 死锁”?
  • 我怀疑,但不是 100% 确定。
  • 你检查了什么?当您怀疑存在死锁时,您可以做的最简单的事情是对 Java 进程进行线程转储,并检查线程在等待什么。这应该会给你一些提示,而不是在没有任何证据的情况下怀疑。
  • @AdrianShum - 从哪里获得 java 进程的线程转储?来自任务管理器?
  • 取决于您运行的平台。在 Windows 中,按 Control-Break 应该可以解决问题。在 Unix 环境下,您可以通过 kill -QUIT &lt;pid&gt;kill -3 &lt;pid&gt; 发送 QUIT 信号来实现。在JDK的更高版本中,它还包括一个名为jstack的工具,可以通过jstack &lt;pid&gt;获取进程ID的线程转储。

标签: java mysql multithreading eclipselink jpa-2.0


【解决方案1】:

根据您的描述,所有线程都只执行这个只读查询。由于“锁定”而不是“死锁”,您可能遇到的可能是更长的时间。两者都是不同的东西,锁定本身不是问题,除非它当然是长时间的。锁定是数据库固有的,以提供一致的数据视图,并且有不同类型/级别的锁定。你可以阅读更多关于它的信息here。粗略地看一下,您的 SQL 是一个常见的选择,所以我也看不出任何锁定的理由。有关锁的信息,您可以参考this 一旦您确定它是需要时间的查询,您就可以使用 EXPLAIN 来微调您的查询。 Here 是一篇很棒的文章。也许适当的索引也会对您有所帮助。

不过,为了消除您的怀疑,您已经有了一个很棒的工具,您可以在 MySQL 命令提示符下运行以下命令

SHOW ENGINE INNODB STATUS\g

这将为您提供有关死锁的信息(如果有)。

【讨论】:

  • 我在 MySQL 上没有看到任何锁。
  • 我认为线程是在 org.eclipse.persistence.sessions.server 包的 acquireConnection() 方法中等待数据库连接。这个 releaseConnection() 连接没有被触发。
【解决方案2】:

这是为遇到此问题的人提供的解决方案。 JPA 不会关闭连接是否已激活。这意味着以下我必须注释代码才能开始交易。

    public List<MyTable> fetchRecords(User theUser, int iTotalRecords, long offset) throws Exception {
        if(null == theUser) {
            throw new Exception("Invalid input.");
        }

        EntityManager entityManager = getEntityManager();
        //if(false == entityManager.getTransaction().isActive()) {
        //  entityManager.getTransaction().begin();
        //}

        try {
            Query query = entityManager.createNativeQuery("select * from mytable as detail join (select max(timestamp) as maxtimestamp from mytable where id=" + theUser.getId() + " group by hnumber order by maxtimestamp limit " + offset + "," + iTotalRecords + ") as topconv on detail.timestamp=topconv.maxtimestamp order by detail.timestamp", MyTable.class);
            @SuppressWarnings("unchecked")
            List<MyTable> resultList = query.getResultList();
            return resultList;
        } catch(Throwable th) {
            throw new Exception(th.getMessage());
        } finally {
            closeEntityManager();
        }
    }

【讨论】:

    【解决方案3】:

    根据上面给出的信息,我可以猜到以下几点:

    1.- 代码有begin(),但缺少commit() 或等同于释放事务,正如您指出的@User12111111。但是,由于您选择在解决方案中将其删除,因此请确保您位于包含管理的服务器中。如果你没有,还有更多:

    2.- 如果持久性单元JPATest 在后续循环中被重用,则生成的查询MyTable 对象可能仍被管理。如果您不需要更新生成的对象,那么make sure to detach them

    3.- 此外,当查询锁定太多行时,锁定会升级到孔表(出于性能原因)。这可能会在您认为不应该的地方产生死锁。如果您不需要行锁定,那么make the query it read-only,正如@Shailendra 所指出的那样。

    4.- 最后但同样重要的是,如果您不需要重复读取等,您可以通过adjusting the isolation level 简单地减少对死锁的限制。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-03
      • 1970-01-01
      相关资源
      最近更新 更多