一,问题发生原因:
New PO单(采购单),填写信息以后, 点击保存,系统生成流水号PO#,客户反应有时会产生重复的PO#。 (该问题属于历史遗留问题)
二,问题探索:
测试人员进行功能测试, 并没有这种现象的发生, 该问题在客户环境上出现频率也较低,
初步判定这是一个并发问题,
判定之后需要做的一件事情, 就是如何去重现这个问题, 以验证我们的判定是正确的:
1, 使用LoadRunner 模拟生成PO单的操作,并发点设置在Save 按钮处。
经过尝试, LoadRunner对于Remoting 方式的程序支持程序不理想,无法进行新增PO的录制
2, 直接设置 PO主表的PO#,为 unique(不可重复),使用do{} while()等类似语句,尝试生成流水号并插入数据表,如果插入不成功会重新生成一遍插入。
这样做的问题是:
A, 会导致插入失败,提示信息容易导致客户不满,
B, 会导致一些用户执行时间过长, 影响效率,
C, 该办法只是最快解决问题的一个办法,为备用办法
3, 直接验证New PO功能所调用的SP ,验证其并发是否有问题
由于不知道有没有性能测试工具可以进行SP的并发测试, 考虑到了自己写测试程序, 生成多个线程,每个线程并发调用SP的形式来模拟SP的并发运行。
三,方案验证:
考虑到SP执行的时间比较短, 而生成线程是采用循环方式生成, 毕竟还是有先后顺序,为了使并发情况产生的更加明显, 特意让每一个线程都连续调用三次SP,(次数可以自己设置, 这里使用三次主要是考虑到生成的PO#也不要太多, 否则不容易看清楚),
界面元素:
User Number: 预计生成的线程个数。(Edit box)
Purno:最终生成的PO#(ListView)
During:Sp执行需要花费的时间,顺便检查线程阻塞情况。(ListView)
主要代码如下:
for(int i= 0; i < num; i++)
{
ThreadStart start = new ThreadStart(CreatePONumber);
Thread th = new Thread(start);
th.IsBackground = true;
th.Start();
}
CreatePONumber 为执行SP的函数,函数主要内容如下:
for(int i = 0; i < 3; i++)
{
DateTime startDate = DateTime.Now;
object ōbj = cmd.ExecuteScalar();
DateTime endDate = DateTime.Now;
if (obj != null && obj != DBNull.Value)
{
ListViewItem lvi = new ListViewItem();
lvi.Text = obj.ToString();
lvi.SubItems.Add(endDate.Subtract(startDate).TotalMilliseconds.ToString());
listView1.Items.Add(lvi);
}
运行结果: 果然生成了重复的 PO#,并且重复的形式如下:
19998,19999, 20000,20002,20002,20003 (在20000之后没有出现20001)
重现成功!
四,解决问题:
经过分析, 发现是因为SP中的 一个语句顺序错误引起的, 错误语句流程如下:
Update SysData set PoNo=PoNo+1 where sysid='ABS'
select PoNumber=PoNo from sysdata where sysid='ABS'
- 说明: PO最大number号会保存在 SysData 表中。 SP中先Update 了这个Data 表中的PO#, 让PO+1,再查询这个最新的PO#, 最后我们会将查询到的PO新number, insert 到PO主表中
分析之后发现,在进行update的时候因为会有排它锁, 在进行并发操作时, 前一个线程还没有来得及进行select语句, 后一个线程又开始update了, 于是会造成 前后两个线程都select 了后面的那个Po Number,导致出现PO number重复
修改之后部分语句如下:
BEGIN TRAN
DECLARE @PONumber int
select @PoNumber=PoNo from sysdata (tablockx) where sysid='ABS'
IF @@ERROR <> 0 GOTO ErrorHandle
SET @PONumber = @PONumber + 1
IF @@ERROR <> 0 GOTO ErrorHandle
Update SysData set PoNo=PoNo+1 where sysid='ABS'
IF @@ERROR <> 0 GOTO ErrorHandle
COMMIT TRAN
SELECT @PONumber AS PONumber
RETURN
ErrorHandle:
ROLLBACK TRAN
SELECT PoNumber = 0
五,回测:
经过回测,功能正常, 没有发现有重复PO出现, 且 SP执行耗用时间前后对比 差不多, 没有引发效率问题。
结束语:
这是一个没有使用性能测试工具的简单性能测试实例, 这里分享给大家,希望可以对于各位今后的工作有所帮助。