事例:Sping動態項目,配置定時任務,使用cron表達式時,配置了此定時任務的事務為REQUIRES_NEW,在定時任務最后啟動一個異步線程對定時任務前面入庫數據進行一些處理;
現象為:
在調試狀態下,業務邏輯,最終數據處理結果都正常;
實際環境中:系統定時任務按時執行,但是數據處理結果永遠不正確
排除問題:(1)首先以為代碼異常,查看代碼有沒有邏輯錯誤,
本地斷點調試,遠程斷點調試,數據處理都正常,(心中一群草泥馬飄過);
去除斷點,本地和遠程運行,問題在現;
分析區別:由於斷點;
(2)排除代碼邏輯問題,考慮數據入庫問題,獲取日志,分析日志業務流程運行順序;
通過對日志的分析,發現定時任務是線程pool-4-thread-1,啟動的異步線程是 pool-7-thread-4,發現在異常線程執行時,對定時任務入庫的數據查詢結果返回為空,沒有查到,也就是說,
定時任務入庫的數據沒有被異步線程讀取到,這個是什么原因呢?
找了資料,發現問題出在Spring事務級別上,REQUIRES_NEW代表創建一個新事務,如果存在則暫停當前事務。
而在此例子中出現的原因是此時出現了幻讀,mysql默認的隔離級別為 Repeatable read 重復讀,該級別會產生幻讀現象。簡單來講就是定時任務入庫數據尚未真正入庫(入庫事務有spring事務,數據庫事務,只有當這兩個事務都結束,才代
表數據真正可查),異步任務就在讀取結果了,此時表中是沒有數據的。
知識補充:以下內容來自博客:https://blog.csdn.net/cy_7030/java/article/details/91802949
1數據庫事務的隔離級別:
數據庫事務的隔離級別有4個,由低到高依次為Read uncommitted( 讀未提交)、Read committed(讀提交)、Repeatable read(重復讀)、Serializable(序列化),這四個級別可以逐個解決臟讀、不可重復讀、幻讀這幾類問題。
READ UNCOMMITTED
READ UNCOMMITTED是限制性最弱的隔離級別,因為該級別忽略其他事務放置的鎖。使用READ UNCOMMITTED級別執行的事務,可以讀取尚未由其他事務提交的修改后的數據值,這些行為稱為“臟”讀。這是因為在Read Uncommitted級別下,
讀取數據不需要加S鎖,這樣就不會跟被修改的數據上的X鎖沖突。比如,事務1修改一行,事務2在事務1提交之前讀取了這一行。如果事務1回滾,事務2就讀取了一行沒有提交的數據,這樣的數據我們認為是不存在的。
READ COMMITTED
READ COMMITTED(Nonrepeatable reads)是SQL Server默認的隔離級別。該級別通過指定語句不能讀取其他事務已修改但是尚未提交的數據值,禁止執行臟讀。在當前事務中的各個語句執行之間,其他事務仍可以修改、插入或刪除數據,從而
產生無法重復的讀操作,或“影子”數據。比如,事務1讀取了一行,事務2修改或者刪除這一行並且提交。如果事務1想再一次讀取這一行,它將獲得修改后的數據或者發現這一樣已經被刪除,因此事務的第二次讀取結果與第一次讀取結果不同,因此也
叫不可重復讀。
REPEATABLE READ
REPEATABLE READ是比READ COMMITTED限制性更強的隔離級別。該級別包括READ COMMITTED,並且另外指定了在當前事務提交之前,其他任何事務均不可以修改或刪除當前事務已讀取的數據。並發性低於 READ COMMITTED,因為
已讀數據的共享鎖在整個事務期間持有,而不是在每個語句結束時釋放。比如,事務1讀取了一行,事務2想修改或者刪除這一行並且提交,但是因為事務1尚未提交,數據行中有事務1的鎖,事務2無法進行更新操作,因此事務2阻塞。如果這時候事務1
想再一次讀取這一行,它讀取結果與第一次讀取結果相同,因此叫可重復讀。
REPEATABLE READ隔離級別保證了在相同的查詢條件下,同一個事務中的兩個查詢,第二次讀取的內容肯定包換第一次讀到的內容。
SERIALIZABLE
SERIALIZABLE 是限制性最強的隔離級別,因為該級別鎖定整個范圍的鍵,並一直持有鎖,直到事務完成。該級別包括REPEATABLE READ,並增加了在事務完成之前,其他事務不能向事務已讀取的范圍插入新行的限制。比如,事務1讀取了一
系列滿足搜索條件的行。事務2在執行SQL statement產生一行或者多行滿足事務1搜索條件的行時會沖突,則事務2回滾。這時事務1再次讀取了一系列滿足相同搜索條件的行,第二次讀取的結果和第一次讀取的結果相同。
隔離級別與鎖的關系
在Read Uncommitted級別下,讀操作不加S鎖;
在Read Committed級別下,讀操作需要加S鎖,但是在語句執行完以后釋放S鎖;
在Repeatable Read級別下,讀操作需要加S鎖,但是在事務提交之前並不釋放S鎖,也就是必須等待事務執行完畢以后才釋放S鎖。
在Serialize級別下,會在Repeatable Read級別的基礎上,添加一個范圍鎖。保證一個事務內的兩次查詢結果完全一樣,而不會出現第一次查詢結果是第二次查詢結果的子集。