JPA2:使用本机查询的死锁

时间:2014-07-17 03:45:11

标签: java mysql multithreading eclipselink jpa-2.0

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

    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){}
    }

我的persistance.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或更多。

请让我知道如何解决这个问题。任何帮助表示赞赏。

3 个答案:

答案 0 :(得分:0)

根据您的描述,所有线程仅执行此只读查询。由于“锁定”而非“死锁”,您可能体验的内容可能更长。两者都是不同的东西,锁定本身不是问题,除非它当然是持续很长时间。锁定是数据库固有的,可提供一致的数据视图,并且存在不同类型/级别的锁。您可以阅读更多相关信息here。粗略看一下你的SQL是常用的选择,所以我也没有看到任何锁定的原因。要获取有关锁定的信息,请参阅this 一旦你确定它的查询需要花费时间,你可以使用EXPLAIN来微调你的查询。 Here是一篇很棒的文章。也许合适的索引对你也有帮助。

但是为了清除您的怀疑,您已经拥有优秀的工具,您可以在MySQL命令提示符下运行以下

SHOW ENGINE INNODB STATUS\g

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

答案 1 :(得分:0)

以下是遇到此问题的人的解决方案。连接不会被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();
        }
    }

答案 2 :(得分:0)

根据上面给出的信息,我可以猜出以下内容:

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

2.-如果在后续周期内重用持久性单元MyTable,则仍可以管理生成的查询JPATest对象。如果您不需要更新生成的对象,则make sure to detach them

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

4.-最后但并非最不重要的一点是,如果你不需要重复读取等等,你可以通过adjusting the isolation level对死锁进行限制较少。