那一天,我们做了一个尖尖的堡垒,今天缺爆了自己的菊


声明:本文转载自https://my.oschina.net/u/2461727/blog/995505,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

前言

       你可能觉得这是一遍很扯淡的文章,但是作者会用下面的内容告诉你..

问题描述

       我院(医院)已经开通了自助机挂号缴费业务,自助机和我院核心系统通信方式采用Webservice方式。今天(流水账日记形式),两位患者怀揣着轻松的心情来看病,他们在自助机做了如下操作(话说图片会变形)

1、他们一起来到了自助机前,当时医院数据库Sqlserver2008出现卡顿死锁情况

2、用身份证在自助机办了一张卡

3、他们给自己诊疗卡充值500元

4、顺带在自助机上挂了一个号,准备去找医生看病

5、去找医生期间,医生由于没有找到该患者挂号信息,也未找到该诊疗卡信息,于是联系信息科

6、信息科工程师查询,该患者卡号caoliu1024信息不存在,也不存在缴费信息,也不存在挂号信息

 

问题分析

  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肯定可以查询到他刚刚自己插入的数据,即使未提交

       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 userptions

    查到的是read committed,没问题!不同事务间commit之后,才会被别的事务看到,这个很正常不过了。

    当我在网上漫无目的的查询sqlserver数据无故丢失数据时,我看到了这么一句话,这句话也是问题的最关键原因:

nolock means READ UNCOMMITTED
  • 没有错,经过测试确实发现了这么一个问题,使用nolock之后,整体sql性能有所提升,而且还不锁表,多好(当初由于我们医院经常死锁,所以他们写sql都要求带上nolock关键字)

归根结底这个NOLOCK爆了一下我们的菊花

  • 我们为了追求业务系统正常运行,我们加上了nolock
  • 我们为了解决死锁频繁问题,我们加上了nolock
  • 我们加了nolock,nolock给我带来了什么?

没有错,又是黑体字,NOLOCK将我原来事务隔离级别是可重复读变成了 read uncommitted,也就是说,你们都还没提交的数据,其他人都可以查到啦!!啦!!!!

  • 而当时由于系统卡死导致数据库事务commit卡主了,很多数据虽然已经写表,但是并未提交成功,而是卡在那里,然而正巧,患者在充值的时候会反查信息表,发现有数据一起都他妈的正常得666
  • 经过神秘微笑的工程师kill之后,各个进程得到释放,该回滚的回滚了,回滚了,回滚了(那两个患者信息回滚了!!
  •  那两位患者就像一张动态图一样,站在那里,一阵秋风扫过几片落叶......

结论

  • 很多特性(如nolock)虽然看起来很酷,但是却不知道其真正的风险

  • 知其然不知其所以然,存在风险

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

本文发表于2017年06月22日 16:36
(c)注:本文转载自https://my.oschina.net/u/2461727/blog/995505,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 1863 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1