产品文档 前端技术 后端技术 编程语言 数据库 人工智能 大数据云计算 运维技术 操作系统 数据结构与算法 Java C++语言 Python PHP

runtimeexception,runtimeexception怎么解决

首页>>技术文档>>产品文档

  

  数据库runtimeexception的死锁

  数据库的死锁的表现主要是runtimeexception,某一个程序需要取访问数据库中的某一行或者某一个字段的时候,而其他程序正在执行带锁的访问(比如修改数据),那么这个进程就会等待,当等了很久锁还没有解除的话就会锁超时,报告一个系统错误,拒绝执行相应的SQL操作。

  最近刚好是我日常值班,在查询线上问题的时候,日志中会偶尔报出一些

  java.lang.RuntimeException: org.springframework.dao.DeadlockLoserDataAccessException: ### Error updating database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; tryrestarting transaction### The error may involve defaultParameterMap

  首先一眼看到发现就是死锁,并且报的就是各种sql死锁了,有报插入语句的,也有报查询语句的。对于insert语句报死锁还能想到,可是对于select语句报死锁,就有点迷惑了。所以针对案发现场,逐个来排查问题,那么今天就主要将遇到的几个特别的case重点阐述下,也是这次解决死锁的核心点。

  这次案例分析主要的流程就是: 案发现场 →分析经过→解决方式

  数据库唯一键为空导致的死锁

  1 insert语句的唯一键缺失导致的死锁

  1)案发现场

  2017-06-21.10:52:15.490 ERROR [http-bio-8080-exec-51] d.s.Statement.statementLogError:149 [] {conn-10039, pstmt-7757099} execute error. insert into tb_call_index_record(business_no,mall_user_id,status) values ( ?,?,?)com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction

  2)分析经过

  首先这是一条insert语句报出的DeadlockLoserDataAccessException,那么首先想到的就是多个线程同时插入导致的吗?

  看了我们系统的业务逻辑实现,这是一条单纯的插入,并且木有做事务处理。

  然后排查了我们nbmp系统日志那个时间点的日志,发现也木有同一个时刻有相同的sql插入呢。其他系统更不会有,因为这个sql的操作只能在我们nbmp系统使用的。

  最后我们看了下 建表语句,其中有一条索引如下:

  表的创建语句中有个唯一索引:UNIQUE KEY `uniq_taskno` (`task_no`),

  task_no是唯一索引,但是刚才的insert语句中木有task_no,所以就会导致task_no是空的,则该表就有一个为空的索引的一条记录。

  那么在缺失的task_no的时候,就必然导致duplicate的现象,导致请求锁超时,从而报出了这个异常。其实觉得应该报的是Duplicate异常,但是最终现象就是报了DeadlockLoserDataAccessException。

  3)解决方式

  查明了原因,是因为插入的时候唯一索引的那个字段为空导致的。所以根据业务场景分析,可以容忍在taskNo为空的时候,不做插入操作。

  提前做了过滤,然后发布,线上则再也没有发生这个insert的DeadlockLoserDataAccessException。

  2 insert语句的相同唯一键并发插入导致的死锁

  1)案发现场

  2017-06-22.11:15:10.903ERROR [http-bio-8080-exec-67] c.q.i.n.w.a.c.c.CallCenterController.action:198java.lang.RuntimeException: org.springframework.dao.DeadlockLoserDataAccessException: ### Error updating database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; tryrestarting transaction### The error may involve defaultParameterMap### The error occurred whilesetting parameters### SQL: insert into tb_call_out_record(task_no,sn,ctime) values(?,?,?)

  2)分析经过

  同样是一条insert插入,但是又报死锁的现象。根据上面的分析,先check下是否有事务,以及表的创建结构。发现了这个表其中task_no是唯一键。由于事先在网上查询过一段话:

runtimeexception,runtimeexception怎么解决

  当并发插入时,出现duplicate异常时,mysql会默认加上S锁,这样就出现了一种情况,多个线程对同一个位置持有S锁,每个线程都去这个位置争抢X锁,S和X锁两者是互斥关系,所以出现循环等待,死锁就此产生。

  然后查询了日志,发现了两个重要的线索:

  /----------->A2017-06-21.13:51:27.341 INFO [http-bio-8080-exec-16] c.q.i.n.w.a.c.c.CallCenterController.action:143 [] [] []input={"callId":"3525233","callTaskNo":"8a1ef78f4bd","callEvent":3,"callType":0,"ivrCallId":"","ivrCallTaskNo":""}//---------->B2017-06-21.13:51:27.400 INFO [http-bio-8080-exec-3] c.q.i.n.w.a.c.c.CallCenterController.action:143 [] [] input={"callId":"3525233","callTaskNo":"8a1ef78f4bd","callEvent":3,"callType":0,"ivrCallId":"","ivrCallTaskNo":""}

  这两条日志的内容是完全一样的,并且是相隔了几十毫秒而已。于是顺着这两个线程的请求轨迹,我顺藤摸瓜的跟踪到另外一个系统中。

  发现了一个重大问题,线程A最终insert成功了,业务处理OK了,但是线程B在insert的时候报错了,也就是报DeadlockLoserDataAccessException。则可以推断出在A线程与B线程同时插入数据库的时候,由于产生的唯一键是相同的,线程A插入成功,但是线程B去插入的时候,报了duplicate,则就会持有S锁的等待,等待A释放这个锁,最终锁超时了,导致报上面的异常。

  在我们业务中,首先这两个完全相同的请求是不应该发生的,最终为了避免这种问题,于是做了去重处理。

  3)解决方式

  在我们的controller层做去重,对于同一个事件做去重,这里的去重主要是针对这种间隔时间太短的(毫秒内的),如果时间相隔太久,其实应该是做幂等性的,类似于在插入的时候先check下是否有相同的记录,如果有则不做插入。最终修改完后发布,问题则再也木有复现了。

  jdbc的参数bug导致的select死锁

  1)案发现场

  上面的insert语句死锁还能理解,可是又生了一些让人匪夷所思的问题,线上有些select语句也报了死锁了。譬如以下:

  java.lang.RuntimeException: org.springframework.dao.DeadlockLoserDataAccessException: ### Error querying database. Deadlock found when trying to get lock; tryrestarting transaction### The error may involve defaultParameterMap### The error occurred whilesetting parameters### SQL: select * from tb_allocation where user = ?

  2)分析经过

  select语句死锁,首先我们想的是,会不会因为这个select的时候,刚好有个update操作或insert语句导致的呢。但是我们看这些select语句的表,这些表的update或insert的频率非常低。

  于是我们最终请教了dba专业人士帮忙解答,最终呢,dba给我们一个建议,发现我们的jdbc中有个参数:useServerPrepStmts=true,而经过查阅发现,这个mysql5.6的时候,在使用这个参数的时候,会偶尔出现一些bug,并且官方已经有这个公告的。

  3)解决方式

  在dba的建议下,我们修改了我们好几个系统的jdbc连接参数,将useServerPrepStmts=true这个配置去掉了。然后发布上线,最终线上死锁的问题,再也木有出现了。

  总结

runtimeexception,runtimeexception怎么解决

在遇到死锁的时候,多check下是否有事务,是否有并发执行的,并且分析下 表的创建结构,是否有唯一键,因为唯一键有时候也会导致死锁的,在执行不当的时候。

多在案发线上查找线索,主要是看能否发现重复请求的。

mysql5.6的jdbc连接参数useServerPrepStmts=true是个官方bug,所以建议不要用这个参数在mysql5.6的情况下。

上一篇: linux查看操作系统,linux属于什么操作系统

下一篇: xcode教程,xcode教程做一个app