那一天,咱們作了一個尖尖的堡壘,今天卻爆了本身的菊

前言

       你可能以爲這是一遍很扯淡的文章,可是做者會用下面的內容告訴你..java

問題描述

       我院(醫院)已經開通了自助機掛號繳費業務,自助機和我院核心系統通訊方式採用Webservice方式。今天(流水帳日記形式),兩位患者懷揣着輕鬆的心情來看病,他們在自助機作了以下操做(話說圖片會變形)web

一、他們一塊兒來到了自助機前,當時醫院數據庫Sqlserver2008出現卡頓死鎖狀況spring

二、用身份證在自助機辦了一張卡sql

三、他們給本身診療卡充值500元數據庫

四、順帶在自助機上掛了一個號,準備去找醫生看病安全

五、去找醫生期間,醫生因爲沒有找到該患者掛號信息,也未找到該診療卡信息,因而聯繫信息科app

六、信息科工程師查詢,該患者卡號caoliu1024信息不存在,也不存在繳費信息,也不存在掛號信息ide

 

問題分析

  1.   這兩名患者確實作了辦卡、充值、掛號操做,而且手持憑條,證實他們沒有騙人
  2.   乍一看,哇,這個很是像事務回滾了,由於當時確實死鎖,當時工程師給我反饋說他沒有去       kill,數據庫自動恢復了(他笑了笑,我以爲不像,這個不理會了)
  3.   暫且假設是事務被回滾,首先檢查webservice服務端(已經經過日誌肯定,服務端返回給自助機都是成功,患者信息很明確,和沒有發生什麼同樣)是否用了事務代碼,代碼雖然不是我開發,可是看java web項目仍是比較輕車熟路,用了spring ,可是檢查配置文件裏面並無配置事務
  4.  因而檢查代碼,咱們都是使用webservice 底層去調用存儲過程,代碼以下(全部代碼都並不是我寫,由乙方承包):
// resDataStr是返回成功與否的標記
String resDataStr = (String) getJdbcTemplate().execute(sql, new CallableStatementCallback() {
			public Object doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
                //他們比較聰明,想拿一個鏈接,在執行完過程以後,再去查一下,確保數據沒問題
				Connection conn = jdbcTemplate.getDataSource().getConnection();
				//.. 
                //..
				cs.setQueryTimeout(5);
				rs = cs.executeQuery();
				
				..
				
				if (lstNeedChk.contains(procName))
				{
					//..
		            //此處爲檢查剛剛寫進去的數據
                    //若是沒數據就返回失敗
				}
			// ....
			
		
		});
  • 值得注意的是:他們在doInCallableStatement中作了檢查數據是否真的寫進去的校驗操做,看似確實很保險,哇,安全性很高,很流逼

       5.  先拋開有沒有開啓事務,我假設他開啓了,可是你寫數據用的cs.executeQuery,校驗數據是從    jdbcTemplate從新getConnection,那麼這兩個Connection有多是同一個鏈接嗎 ?若是是同一個connection,而且事務隔離級別若是是 read committed,那這個check是沒有用的,由於他們可能就是在同一個事務中.check確定能夠查詢到他剛剛本身插入的數據,即便未提交sqlserver

       6.  單獨寫了一個junit Test看看connection是否是同一個connection,代碼以下:性能

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath*:spring-application.xml"})
public class JdbcTemplateTest {
	@Resource
	private JdbcTemplate jdbcTemplate;
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Test
	public void testGetUser() throws Exception {
		jdbcTemplate.execute("call my_function()",new CallableStatementCallback() {
			@Override
			public Object doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
				Connection conn = jdbcTemplate.getDataSource().getConnection();
				Connection conn2 = jdbcTemplate.getDataSource().getConnection();
				System.out.println(conn.getTransactionIsolation());
				System.out.println(conn == conn2);
				System.out.println(conn == cs.getConnection());
				return null;
			}
			
		});
	}
}
  •  結果打印出來的兩個 false,表示每次從jdbcTemplate獲取到的connection都是新拿的,並無作一些線程級別共享connection的控制,而且 getTransationIsolation=4 也就是 TRANSACTION_REPEATABLE_READ,可重複讀,很正常。我同時把dbcp和c3p0都試了一下,效果仍是同樣
  • 結論:connection沒有公用,不存在共用同一個事務,而且我看了connection的getAutoCommit屬性是爲true,更加肯定webservice服務端並無使用事務(由於他們把事務所有交給了存儲過程)

      7.  檢查存儲過程(sqlserver過程一直沒接觸過,不過仍是拿過來看了一下),隨意挑選了一個好比充 值的過程,代碼以下:

         

  • 大概意思是先查一下這個患者信息有沒建檔,診療卡信息是否存在(後來我排查確實沒有數據),當有患者信息的時候就執行了下面的操做:

       

  • 嗯,看名字是在寫日誌,很謹慎,而後接下來就是寫充值流水記錄了

 

  • 多餘代碼不看,根據自助機的日誌看,這邊其實都已經走成功了,數據也插了,日誌也寫了。

可是,當我去回頭查這些數據

  • 可是,當我根據患者診療卡號來查這些數據的時候,這些數據根本就不存在!其中包括,診療卡信息(ic_register)、患者基本信息(mz_patient_mi)、(ic流水)ic_account_record,日誌表,沒有!所有都沒有!關於這我的的信息一條都沒有!沒有錯,就是這麼詭異!
  • 咱們先考慮下這個患者的心情:「哇,他們窗口排那麼長的隊,還據說醫院系統卡了,我在自助機上操做很順暢,一點問題都沒有,1分鐘就操做完了~!我去看醫生去咯!!」嗯,差很少應該是這樣子
  • 當他去看醫生,他遇到了這些問題:
  1. 醫生看不到他剛剛掛號的名字
  2. 跑去找導診護士在系統裏面查,導診系統未查到患者信息
  3. 跑去ic卡中心諮詢,沒有您的卡信息(也就是廢卡)
  4. 跑去收費處查詢,沒有您的繳費信息
  •   對,他們當時兩個就懵逼!誰可曾知道,他拿着診療卡、拿着繳費發票、拿着掛號憑條,而後他在咱們醫院系統的數據卻  消失得無影無蹤! 固然,就如新聞裏同樣患者要大吵大鬧了
  •  固然,此時咱們乙方工程師立馬出場,由於比較詭異,查詢時間可能耗時比較長,二話不說先把患者信息補上、卡信息補上、費用補上!OK,完事

我很震驚

     對,這只是一個UC標題通用模板,我借鑑一下!我檢查了一下sqlserver事務隔離級別

dbcc useroptions

    查到的是read committed,沒問題!不一樣事務間commit以後,纔會被別的事務看到,這個很正常不過了。

    當我在網上漫無目的的查詢sqlserver數據無端丟失數據時,我看到了這麼一句話,這句話也是問題的最關鍵緣由:

nolock means READ UNCOMMITTED
  • 沒有錯,通過測試確實發現了這麼一個問題,使用nolock以後,總體sql性能有所提高,並且還不鎖表,多好(當初因爲咱們醫院常常死鎖,因此他們寫sql都要求帶上nolock關鍵字)

歸根結底這個NOLOCK爆了一下咱們的菊花

  • 咱們爲了追求業務系統正常運行,咱們加上了nolock
  • 咱們爲了解決死鎖頻繁問題,咱們加上了nolock
  • 咱們加了nolock,nolock給我帶來了什麼?

沒有錯,又是黑體字,NOLOCK將我原來事務隔離級別是可重複讀變成了 read uncommitted,也就是說,這些進程都還沒提交的數據,其餘進程均可以查到了

  • 而當時因爲系統卡死致使數據庫事務commit卡主了,不少數據雖然已經寫表,可是並未提交成功,而是卡在那裏,然而正巧,患者操做速度快,患者在充值的時候會反查患者信息表有數據,掛號的時候反查ic卡和患者信息數據(他的充值和患者信息都已經寫表,只是並未提交。因爲nolock緣由,不一樣的進程仍是把這個未提交的數據給查出來了),發現全部數據一塊兒都他媽的正常得666
  • 通過神祕微笑的工程師kill以後,各個進程獲得釋放,該回滾的回滾了,回滾了,回滾了(那兩個患者信息回滾了!!
  •  那兩位患者就像一張動態圖同樣,站在那裏..

結論

  • 不少特性(如nolock)雖然看起來很酷,可是殊不知道其真正的風險

  • 知其然不知其因此然,存在風險

  • 響應前言,做者會用上面的內容告訴你,內容很扯淡

相關文章
相關標籤/搜索