那一天,咱们作了一个尖尖的堡垒,今天却爆了本身的菊

前言

       你可能以为这是一遍很扯淡的文章,可是做者会用下面的内容告诉你..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)虽然看起来很酷,可是殊不知道其真正的风险

  • 知其然不知其因此然,存在风险

  • 响应前言,做者会用上面的内容告诉你,内容很扯淡

相关文章
相关标签/搜索