从AX4.0本地化SP2凭证系统的一个bug看UserConnection的使用
2008-09-09 22:10
435 查看
去年写的一篇blog,当时忘记发布了,今天又遇到同样的问题,突然想起曾经写过这么一篇文章,今天发布一下。
问题描述:
做N张销售订单,然后在应收账款->期间->销售更新->发票做开票动作,两个财务人员分别在不同的客户端做该动作。
真实的业务场景比如在月底统一开票,然后不同的财务负责处理不同的客户。
OK,死锁了。
问题重现:
由于这个bug是本地化SP2造成的,所以要确认Application Version是4.0.2501.128,总账->设置->参数设置 选中 中国式凭证系统。
然后用代码创建200张销售订单,打开两个客户端同时做应收账款->期间->销售更新->发票 动作,各做100张,嗯,这样差不多应该就死锁了。
问题原因:
通过查看SQL Server的死锁情况,发现NumberSequenceTable这张表被死锁了,可以通过如下存储过程查看死锁的进程:
--查看锁信息
create table #t(req_spid int,obj_name sysname)
declare @s nvarchar(4000)
,@rid int,@dbname sysname,@id int,@objname sysname
declare tb cursor for
select distinct req_spid,dbname=db_name(rsc_dbid),rsc_objid
from master..syslockinfo where rsc_type in(4,5)
open tb
fetch next from tb into @rid,@dbname,@id
while @@fetch_status=0
begin
set @s='select @objname=name from ['+@dbname+']..sysobjects where id=@id'
exec sp_executesql @s,N'@objname sysname out,@id int',@objname out,@id
insert into #t values(@rid,@objname)
fetch next from tb into @rid,@dbname,@id
end
close tb
deallocate tb
select 进程id=a.req_spid
,数据库=db_name(rsc_dbid)
,类型=case rsc_type when 1 then 'NULL 资源(未使用)'
when 2 then '数据库'
when 3 then '文件'
when 4 then '索引'
when 5 then '表'
when 6 then '页'
when 7 then '键'
when 8 then '扩展盘区'
when 9 then 'RID(行 ID)'
when 10 then '应用程序'
end
,对象id=rsc_objid
,对象名=b.obj_name
,rsc_indid
from master..syslockinfo a left join #t b on a.req_spid=b.req_spid
go
drop table #t
AX对表NumberSequenceTable的操作封装到了类NumberSeq等类中,所以我们只要看一下开票这个过程在哪里用到了这些类就应该可以找到问题的答案。
看了一下开票过程分配编码规则的地方,一共有两处:
1.SalesFormLetter->InsertJournal方法会调用allocateNumAndVoucher方法分配编码
2.LedgerVoucherObject类的Post方法。
1.的代码是SYS层的,由于不启用中国式凭证系统没有问题,所以问题应该是2.造成的。
看一下2.中post方法:
if (LedgerParameters::find().ChineseVoucher_CN && ledgerTransList.elements() > 0)
voucher_CN = NumberSeq_Voucher::newGetVoucherFromCode(ledgerVoucherType.NumberSequence,transDate).voucher();
这一行代码最终会调用类NumberSeqget的NumInternal方法:
userConnection = new UserConnection();
userConnection.ttsbegin();
sequenceUpdated = false;
numberSequenceTable.setConnection(userConnection);
select forupdate firstonly numberSequenceTable
index hint SeriesIdx
where numberSequenceTable.NumberSequence == _numberSequenceCode;
this.setCleanupSequence(numberSequenceTable);
ok = this.checkSetUpNum(numberSequenceTable);
if (ok)
if (!ok)
userConnection.ttscommit();
可以看出这段代码用UserConnection开启了一个新的连接,这样做可以将编码分配的逻辑封装在一个单独的比较短的事务里,以免被封装到用户自己的ttsbegin和ttscommit中长时间地锁定NumberSequenceTable造成资源浪费和死锁.
问题出现在本地化代码的类NumberSeq_Voucher的Used方法,该方法对NumberSequenceTable进行了更新操作,但并没有将该操作用UserConnection隔离出一个事务,这样只有当最外层的ttsbegin对应的事务提交后才会释放对NumberSequenceTable表中相关记录的锁定,长时间地占用了NumberSequenceTable,这样外层的代码逻辑处理时间较长,很容易造成死锁。
解决方法:
1.改造一下NumberSeq_Voucher的Used方法,不要让它在代码开始的时候就select forupdate锁定NumberSequenceTable表,这样可以尽量减少死锁的几率;
2.将Used方法的调用与编码的分配的代码封装到一个单独的UserConnection里。
当然如果像如下写法就更是必死无疑了:
static void Lock(Args _args)
{
UserConnection connection;
NumberSequenceTable sequenceTable;
NumberSequenceTable sequenceTable2;
Voucher voucher_cn;
NumberSequenceTable seq
;
ttsbegin;
select firstonly forupdate sequenceTable
where sequenceTable.NumberSequence == '应收帐款_157';
connection = new UserConnection();
connection.ttsbegin();
sequenceTable2.setConnection(connection);
select forupdate sequenceTable2
where sequenceTable2.NumberSequence == '应收帐款_157';
connection.ttscommit();
ttscommit;
}
由于采用了悲观锁,第一个forupdate锁定了NumberSequenceTable的记录,只有等到ttscommit才会释放,于是在由UserConnection开启的一个事务里就痴痴地等着它释放这个锁,可第一个forupdate也够痴情的,它在等着UserConnection运行完,它好commit......
综上所述:对于采用了单独的UserConnection开启事务去处理的表要从一而终,不要再用ttsbegin和ttscommit去控制事务。SYS层的代码还是比较规矩的,没发现NumberSequenceTable脱离UserConnection在外面裸奔的情况。
个人愚见,如有误还望指点。
问题描述:
做N张销售订单,然后在应收账款->期间->销售更新->发票做开票动作,两个财务人员分别在不同的客户端做该动作。
真实的业务场景比如在月底统一开票,然后不同的财务负责处理不同的客户。
OK,死锁了。
问题重现:
由于这个bug是本地化SP2造成的,所以要确认Application Version是4.0.2501.128,总账->设置->参数设置 选中 中国式凭证系统。
然后用代码创建200张销售订单,打开两个客户端同时做应收账款->期间->销售更新->发票 动作,各做100张,嗯,这样差不多应该就死锁了。
问题原因:
通过查看SQL Server的死锁情况,发现NumberSequenceTable这张表被死锁了,可以通过如下存储过程查看死锁的进程:
--查看锁信息
create table #t(req_spid int,obj_name sysname)
declare @s nvarchar(4000)
,@rid int,@dbname sysname,@id int,@objname sysname
declare tb cursor for
select distinct req_spid,dbname=db_name(rsc_dbid),rsc_objid
from master..syslockinfo where rsc_type in(4,5)
open tb
fetch next from tb into @rid,@dbname,@id
while @@fetch_status=0
begin
set @s='select @objname=name from ['+@dbname+']..sysobjects where id=@id'
exec sp_executesql @s,N'@objname sysname out,@id int',@objname out,@id
insert into #t values(@rid,@objname)
fetch next from tb into @rid,@dbname,@id
end
close tb
deallocate tb
select 进程id=a.req_spid
,数据库=db_name(rsc_dbid)
,类型=case rsc_type when 1 then 'NULL 资源(未使用)'
when 2 then '数据库'
when 3 then '文件'
when 4 then '索引'
when 5 then '表'
when 6 then '页'
when 7 then '键'
when 8 then '扩展盘区'
when 9 then 'RID(行 ID)'
when 10 then '应用程序'
end
,对象id=rsc_objid
,对象名=b.obj_name
,rsc_indid
from master..syslockinfo a left join #t b on a.req_spid=b.req_spid
go
drop table #t
AX对表NumberSequenceTable的操作封装到了类NumberSeq等类中,所以我们只要看一下开票这个过程在哪里用到了这些类就应该可以找到问题的答案。
看了一下开票过程分配编码规则的地方,一共有两处:
1.SalesFormLetter->InsertJournal方法会调用allocateNumAndVoucher方法分配编码
2.LedgerVoucherObject类的Post方法。
1.的代码是SYS层的,由于不启用中国式凭证系统没有问题,所以问题应该是2.造成的。
看一下2.中post方法:
if (LedgerParameters::find().ChineseVoucher_CN && ledgerTransList.elements() > 0)
voucher_CN = NumberSeq_Voucher::newGetVoucherFromCode(ledgerVoucherType.NumberSequence,transDate).voucher();
这一行代码最终会调用类NumberSeqget的NumInternal方法:
userConnection = new UserConnection();
userConnection.ttsbegin();
sequenceUpdated = false;
numberSequenceTable.setConnection(userConnection);
select forupdate firstonly numberSequenceTable
index hint SeriesIdx
where numberSequenceTable.NumberSequence == _numberSequenceCode;
this.setCleanupSequence(numberSequenceTable);
ok = this.checkSetUpNum(numberSequenceTable);
if (ok)
if (!ok)
userConnection.ttscommit();
可以看出这段代码用UserConnection开启了一个新的连接,这样做可以将编码分配的逻辑封装在一个单独的比较短的事务里,以免被封装到用户自己的ttsbegin和ttscommit中长时间地锁定NumberSequenceTable造成资源浪费和死锁.
问题出现在本地化代码的类NumberSeq_Voucher的Used方法,该方法对NumberSequenceTable进行了更新操作,但并没有将该操作用UserConnection隔离出一个事务,这样只有当最外层的ttsbegin对应的事务提交后才会释放对NumberSequenceTable表中相关记录的锁定,长时间地占用了NumberSequenceTable,这样外层的代码逻辑处理时间较长,很容易造成死锁。
解决方法:
1.改造一下NumberSeq_Voucher的Used方法,不要让它在代码开始的时候就select forupdate锁定NumberSequenceTable表,这样可以尽量减少死锁的几率;
2.将Used方法的调用与编码的分配的代码封装到一个单独的UserConnection里。
当然如果像如下写法就更是必死无疑了:
static void Lock(Args _args)
{
UserConnection connection;
NumberSequenceTable sequenceTable;
NumberSequenceTable sequenceTable2;
Voucher voucher_cn;
NumberSequenceTable seq
;
ttsbegin;
select firstonly forupdate sequenceTable
where sequenceTable.NumberSequence == '应收帐款_157';
connection = new UserConnection();
connection.ttsbegin();
sequenceTable2.setConnection(connection);
select forupdate sequenceTable2
where sequenceTable2.NumberSequence == '应收帐款_157';
connection.ttscommit();
ttscommit;
}
由于采用了悲观锁,第一个forupdate锁定了NumberSequenceTable的记录,只有等到ttscommit才会释放,于是在由UserConnection开启的一个事务里就痴痴地等着它释放这个锁,可第一个forupdate也够痴情的,它在等着UserConnection运行完,它好commit......
综上所述:对于采用了单独的UserConnection开启事务去处理的表要从一而终,不要再用ttsbegin和ttscommit去控制事务。SYS层的代码还是比较规矩的,没发现NumberSequenceTable脱离UserConnection在外面裸奔的情况。
个人愚见,如有误还望指点。
相关文章推荐
- AX 本地化SP2 中国式凭证系统 学习笔记
- 写给公司的一个Bug需求管理系统,公司一直使用良好
- iOS开发笔记之三十一——日历NSCaledar使用过程中遇到的一个苹果系统bug
- 使用EF6和MVC5实现一个简单的选课系统--启航(1/12)
- ftp部署一个可供centos6、centos7系统使用的yum网络仓库
- 使用 Ansible 在树莓派上构建一个基于 Linux 的高性能计算系统 | Linux 中国
- 如何使用国际开源项目构建一个完整的GIS(地理信息)应用系统
- 【python入门】学习字典的随笔,附使用字典做一个学生管理系统
- 使用winAUTOPWN自动攻击Win系统并反回一个shell
- 使用面向对象思想建立一个学生管理系统
- AX4.0 SP2本地化的问题--不能做供应商财务日志
- Spring MVC在方法参数中使用@ModelAttribute中遇到的一个bug
- 20135239 益西拉姆 linux内核分析 使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
- 如何在cocos2d-x中使用ECS(实体-组件-系统)架构方法开发一个游戏?
- 在系统启动时至少有一个服务或驱动程序产生错误,详细信息,请使用事件查看器查看事件日志
- 一个存在三年的内核 bug 引发大量的容器系统出现网络故障
- linux下收集系统硬盘,内存使用情况以定时邮件方式发给root的一个脚本
- 一个Bug 差点让服务器的文件系统崩溃
- 调用一个系统命令,并读取它的输出值(使用QProcess.readAll)
- NodeJS搭建博客系统(四)使用模版引擎(node+express+ejs做的一个demo)