说下问题背景:
某日,在公司测试环境,修改完一段代码运行后,频繁遇到接口卡住的问题,且迟迟没有响应,通过jstack打印出堆栈,发现没有任何用户线程的状态是WATING状态或是BLOCKED状态,则说明线程间没有死锁或活锁状态。
开始分析:
通过jstack日志分析出了一段可疑的线程,虽然他是RUNNABLE活跃状态,但其实是在进行IO流读写操作。熟悉操作系统的应该知道:IO读写操作时,线程在操作系统级别是休眠状态,但JVM级别线程状态仍然是RUNNABLE状态。
顺便附上线程在操作系统级别的状态和在JVM级别的状态持续图:
操作系统级别:
JVM级别:
我们再来看一下对应的线程堆栈:
从堆栈上可以看出,线程正在请求数据库,但数据库迟迟没有响应,处于IO阻塞状态,此时线程状态处于RUNNABLE,验证了我们之前的说法。
数据库查询操作为什么会迟迟没有响应?
数据库没有响应的问题一般的以下几种场景(可能罗列不全):
1、使用了悲观锁机制查询,如行锁,表锁等机制等待锁释放。
2、数据库读写机制,比如sqlserver读写机制有:读已提交,读快照等方式。
3、需要索引的数据量太大,需要很长时间才能响应。
通过分析代码1、3项基本可以直接排除,那么很可能就是读写机制出了问题。
对应的数据库是sqlserver,我们可以使用命令:
DBCC Useroptions
查询读写模式。
发现隔离模式是读已提交。读已提交就是说:我们要查询的数据如果被其他事务修改,则必须要等待事务释放后数据库才会响应。
通过观察代码发现,我们应用在修改了这条记录后事务并没有立即提交,而是又新开启了一个小的事务,再次去查询这条记录。最终导致外层事物修改后没有提交,内嵌事务查询在等待外层事务提交。而这是同一个线程,外层事务永远不会提交了,内嵌事务也永远等不来了,引发死锁血案。
解决方案,修改数据库的隔离级别吧,可以改成读快照的方式,这样如果事务没有提交,将会读事务开启前的数据,不会等待外层事务释放。
修改快照方式代码如下:
ALTER DATABASE [databaseName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE ;
ALTER DATABASE [databaseName] SET ALLOW_SNAPSHOT_ISOLATION ON
ALTER DATABASE [databaseName] SET READ_COMMITTED_SNAPSHOT ON;
ALTER DATABASE [databaseName] SET MULTI_USER;
再次验证隔离级别是否成功: