您的位置:首页 > 编程语言 > Java开发

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

2017-06-22 16:52 183 查看
摘要: 关于java,关于sqlserver,关于事务隔离级别的问题,问题初级,高手绕道

前言

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

问题描述

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



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

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

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

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

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

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

问题分析

这两名患者确实做了办卡、充值、挂号操作,并且手持凭条,证明他们没有骗人

乍一看,哇,这个非常像事务回滚了,因为当时确实死锁,当时工程师给我反馈说他没有去 kill,数据库自动恢复了(他笑了笑,我觉得不像,这个不理会了)

暂且假设是事务被回滚,首先检查webservice服务端(已经通过日志确定,服务端返回给自助机都是成功,患者信息很明确,和没有发生什么一样)是否用了事务代码,代码虽然不是我开发,但是看java web项目还是比较轻车熟路,用了spring ,但是检查配置文件里面并没有配置事务

于是检查代码,我们都是使用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分钟就操作完了~!我去看医生去咯!!”嗯,差不多应该是这样子

当他去看医生,他遇到了这些问题:

医生看不到他刚刚挂号的名字

跑去找导诊护士在系统里面查,导诊系统未查到患者信息

跑去ic卡中心咨询,没有您的卡信息(也就是废卡)

跑去收费处查询,没有您的缴费信息

对,他们当时两个就懵逼!谁可曾知道,他拿着诊疗卡、拿着缴费发票、拿着挂号凭条,然后他在我们医院系统的数据却 消失得无影无踪! 当然,就如新闻里一样患者要大吵大闹了

当然,此时我们乙方工程师立马出场,因为比较诡异,查询时间可能耗时比较长,二话不说先把患者信息补上、卡信息补上、费用补上!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)虽然看起来很酷,但是却不知道其真正的风险

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

响应前言,作者会用上面的内容告诉你,内容很扯淡
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java SQL Server 事务;
相关文章推荐