几天过去了,我在一个使用Glassfish - EJB3和Mysql InnoDB的Java应用程序上遇到了死锁问题。
配置: Mysql InnoDB: ver14.12disrige5.0.51a,用于使用readline5.2的debian gnu (i486)
应用服务器: Glassfish v2.1
使用EJB3 - JPA - Hibernate的持久性
为了简单起见,我有一个带有servlet的SOA系统,用于处理用户对服务的订阅、登录、注销、支付和注册等.-石英作业系统(cron触发器),处理这些服务的每日减少、“低信用”警告的产生、付款的确认等。
我的问题是:在负载测试期间,到处都是死锁(10万用户模拟-- 30次请求/秒)
返回的堆栈示例:
Message ID:
Could not synchronize database state with session org.hibernate.exception.LockAcquisitionException
Complete Message:
Could not execute JDBC batch update at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:105) at
org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66) at
org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275) at
org.hibernate.jdbc.AbstractBatcher.prepareStatement(AbstractBatcher.java:114) at
org.hibernate.jdbc.AbstractBatcher.prepareStatement(AbstractBatcher.java:109) at
org.hibernate.jdbc.AbstractBatcher.prepareBatchStatement(AbstractBatcher.java:244) at
org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2382) at
org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2335) at
org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2635) at
org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:115) at
org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279) at
org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:263) at
org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168) at
org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321) at
org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:64) at
org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:996) at
org.hibernate.impl.SessionImpl.list(SessionImpl.java:1141) at
org.hibernate.impl.QueryImpl.list(QueryImpl.java:102) at
org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:67) at
net.xxx.server.dao.impl.PaymentDAOImpl.listPaymentsByStateAndCompany(PaymentDAOImpl.java:270)注意结尾,这是我做的代码:net.xxx.server.dao.impl.PaymentDAOImpl.listPaymentsByStateAndCompany(PaymentDAOImpl.java:270)
这个函数:
private static final String QUERY_FOR_PAYMENTS_BY_STATE_AND_COMPANY = " FROM " + Payment.class.getName()
+ " p WHERE p.serviceDefinition.company=:company"
+ " AND p.state = :state";
@SuppressWarnings("unchecked")
public List<Payment> listPaymentsByStateAndCompany(Company company,Constants.PaymentState state) {
List<Payment> payments = this.getEntityManager()
.createQuery(QUERY_FOR_PAYMENTS_BY_STATE_AND_COMPANY)
.setParameter("state",state.ordinal())
.setParameter("company",company)
.getResultList();
return payments;
}当它不是负载测试时,这个函数工作得非常好,例如,我们每5秒就有一个请求。
在负载测试期间,我们有高频率运行的作业(例如每5秒一次)。
我不仅得到了这个错误,还得到了其他一些作业的错误(仍然是死锁)!
MYSQL上的:
死锁的例子:
------------------------
LATEST DETECTED DEADLOCK
------------------------
090428 12:21:11
*** (1) TRANSACTION:
TRANSACTION 0 14286818, ACTIVE 0 sec, process no 21872, OS thread id 802850 starting index read
mysql tables in use 1, locked 1
LOCK WAIT 13 lock struct(s), heap size 1024, undo log entries 2
MySQL thread id 298, query id 11843357 localhost 127.0.0.1 root Updating
/* */ update service set balance=40.0, company_id=2, last_on='2009-04-28 12:19:55', modified_by='server', modified_on='2009-04-28 12:21:11', service_definition_id=3, state=1, subscriber_id=13578, valid_until='2010-02-22 12:13:52' where service_id=693
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 62 n bits 176 index `PRIMARY` of table `xxx/service` trx id 0 14286818 lock_mode X locks rec but not gap waiting
Record lock, heap no 98 PHYSICAL RECORD: n_fields 12; compact format; info bits 0
0: len 8; hex 80000000000002b5; asc ;; 1: len 6; hex 000000d9faa0; asc ;; 2: len 7; hex 0000000cc91e70; asc p;; 3: len 4; hex 00001c42; asc B;; 4: len 8; hex 80001245aad4e363; asc E c;; 5: len 6; hex 736572766572; asc server;; 6: len 8; hex 80001245aad4e3c9; asc E ;; 7: len 1; hex 81; asc ;; 8: len 8; hex 80001247f200df08; asc G ;; 9: len 8; hex 8000000000000002; asc ;; 10: len 8; hex 8000000000000003; asc ;; 11: len 8; hex 800000000000350a; asc 5 ;;
*** (2) TRANSACTION:
TRANSACTION 0 14286798, ACTIVE 1 sec, process no 24963, OS thread id 393239 starting index read, thread declared inside InnoDB 500
mysql tables in use 1, locked 1
17 lock struct(s), heap size 1024, undo log entries 16
MySQL thread id 253, query id 11843359 localhost 127.0.0.1 root Updating
/* */ update payment set credit=1.0, currency='EUR', modified_by='9999900092', modified_on='2009-04-28 12:21:11', payment_definition_id=7, price=1.0, service_definition_id=3, state=0, subscriber_id=13578, transaction_id=11463 where payment_id=15914
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 62 n bits 176 index `PRIMARY` of table `xxx/service` trx id 0 14286798 lock mode S locks rec but not gap
Record lock, heap no 47 PHYSICAL RECORD: n_fields 12; compact format; info bits 0
0: len 8; hex 8000000000000286; asc ;; 1: len 6; hex 000000d9ffce; asc ;; 2: len 7; hex 0000000cc90683; asc ;; 3: len 4; hex 0000f841; asc A;; 4: len 8; hex 80001245aad4e3b2; asc E ;; 5: len 6; hex 736572766572; asc server;; 6: len 8; hex 80001245aad4e3ff; asc E ;; 7: len 1; hex 81; asc ;; 8: len 8; hex 80001245d450fed8; asc E P ;; 9: len 8; hex 8000000000000002; asc ;; 10: len 8; hex 8000000000000003; asc ;; 11: len 8; hex 80000000000034db; asc 4 ;;事务隔离
我在网上读到了关于事务隔离的东西。
在glassfish上,我们可以设置事务隔离级别,我把它放在读-未提交。
它不起作用,然后我在mysql中设置了相同的级别:
mysql> SELECT @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED |
+-----------------------+
1 row in set (0.00 sec)
mysql> SELECT @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set (0.00 sec)SVP能告诉我问题出在哪里吗?我真的不知道!
顺便说一下,我在网上见过,您可以为每个请求选择您的事务隔离级别.是否可以直接为JPA上的方法设置事务隔离级别?因为我认为只有那些进行全局数据更新(比如减少15000服务)的作业才应该被读取--我错了吗?
发布于 2009-05-06 03:00:39
我对你的问题没有确切的答案,但这可能有助于你缩小范围。
死锁可能发生在任何事务隔离级别上,因为innodb将对更新设置锁,甚至在“未提交的读取”上设置锁。
您可以使用以下简单的场景来测试这一点:
CREATE TABLE locktest (a INT(11), b INT(11), PRIMARY KEY (a)) ENGINE=INNODB;
INSERT INTO locktest VALUE (1, 1);
INSERT INTO locktest VALUE (2, 1);然后,打开两个mysql控制台(C1和C2),然后按照以下顺序运行这些命令:
C1> BEGIN;
C2> BEGIN;
C1> UPDATE locktest SET b = b + 1 WHERE a = 1;
C2> UPDATE locktest SET b = b + 1 WHERE a = 2;
C1> UPDATE locktest SET b = b + 1 WHERE a = 2;
C2> UPDATE locktest SET b = b + 1 WHERE a = 1;您将看到C2上出现死锁,即使在读未提交的情况下,C1也会成功完成。如果您检查引擎日志,您将看到类似的报告。
如果您删除了表上的主键,那么命令甚至会在更早的时候阻塞,这是因为如果有一个覆盖正在设置锁的查询的索引,则无害数据库锁定工作得更好。
回到你的问题上。
您应该检查以死锁结束的事务中涉及的所有查询,并确保存在适当的索引。如果MySQL必须做一个完整的表扫描,它将结束锁定超过它的需要。
这些提示帮助我解决了应用程序中的一些死锁。防止死锁的一个好方法是设置带有"SELECT . FOR UPDATE“的写锁以锁定某些父行。
因此,例如,如果您有多个事务试图更新某些特定的客户数据,您可以发出一个"SELECT id从customer WHERE id=123 for UPDATE",此时它们将按顺序等待,而不是结束彼此需要的锁。
发布于 2010-10-25 16:37:38
对于使用Oracle数据库的Java应用程序,我也遇到了类似的问题。
我发现数据库缺少外键上的索引,这导致数据库锁定了比要求更多的行,并导致在高度并发的测试中出现死锁。
这是一篇很好的诊断文章。这篇文章的大部分内容都是甲骨文特有的,但有些东西也适用于其他关系数据库:http://www.oratechinfo.co.uk/deadlocks.html。
发布于 2009-05-31 17:34:47
您应该想当然地认为,无论您做什么,任何事务数据库都会发生死锁。您应该优雅地处理它们,并重复失败的事务一些固定次数(3通常是可以的)-应该有一个参数在某个地方的玻璃鱼负责。
https://stackoverflow.com/questions/797883
复制相似问题