SqlServer 可更新订阅中在订阅库并发获取最大单据号测试及解决法案!

说明:

很多交易的系统都需要单据号,而单据号按顺序并发获取又是一件头疼的事!~

一般单据号格式为:前缀+日期+编号(如:KK20150501000001)

现在模拟测试,数据库中的单据号作为一张表处理。

数据库单据号表(Billnumber)存储每个账号及其最大的单据号总共一条记录

单据号是组合而成的字符串,后6位为序号。

获取单据号的存储过程:

EXEC [dbo].[GetBillnumber_Test]@Account=‘Account‘,@Billnumber NVARCHAR(20)OUTPUT

存储过程功能:传递用户账号,返回用户的最大单据号。

存储过程内部逻辑:(查询和更新都使用了聚集索引查找)

a. 表中找出该账号的单据号

b. 如果单据号存在,截取后位字符转数字加1生成新的单据号返回,并更新这条记录为最新单据号

c. 如果单据号不存在,则为这账号第一次插入新的单据号,并返回该单据号

但是并发操作时,就可能发生堵塞以及死锁。当前数据库的事务级别为读提交(read
committed)。

通常的操作是,存储过程内部逻辑查询表中的单据号时(如上a.),加上锁并保持锁到事务结束。我这里为WITH(UPDLOCK,HOLDLOCK),网上还有一种这样加法WITH
(XLOCK,PAGLOCK) ,这种加锁方法会把整页数据都不能访问。

为了方便在SQLqueryStress测试,我把存储过程改为:

EXEC [dbo].[GetBillnumber_Test]@Account=‘Account‘

即不返回单据号,而在存储过程末尾把单据号抛出,让SQLqueryStress能够获取查看单据号

DECLARE
@RE NVARCHAR(100)

DECLARE
@billNumber NVARCHAR(100)--已赋值的单据号(赋值过程不描述)

SET @RE=N‘[billNumber=‘+@billNumber+N‘] 
[SPID=‘+CONVERT(VARCHAR(10),@@SPID)+N‘]‘

RAISERROR (@RE,16,1,@billNumber) --抛出

打开2个SQLQuerySrees窗口,设置好连接参数:

第一个窗口:打开1个连接,每个连接执行500次;账号为A

第二个窗口:打开1个连接,每个连接执行500次;账号为A

数据库中开启死锁跟踪:

--  打开死锁跟踪

dbcc traceon(1222,-1)

dbcc tracestatus

【执行测试】

快速执行第一个,第二个SQLQuerySrees窗口(点击“GO”)

执行完成后,点击SQLQuerySrees日志查看:

测试结果:

发现单据号都是递增的,只是中间有跳跃,因为另一个进程也在获取单据号。查看SqlServer日志,并没有看到死锁!

打开几个SQLqueryStress测试也是一样。结果是正常和符合的!

但是!问题来了!~当数据库中创建了可更新订阅,并且在订阅数据库操作,就会产生死锁!~

对该表创建可更新订阅:

与上面一样,的步骤,对订阅数据库的单据表进行单据号的获取操作。

在执行SQLQuerySrees时,同时也快速返回到数据库,查看锁情况。参考脚本:SqlServer 查看当前锁请求情况的一个脚本

可以看到,一个spid在对表BillNumberRecord更新时,也触发将数据输入插入到同步队列表中。对单据表使用的是范围所RangeS_U和RangeX_X。这会儿查看SqlServer日志,

3个参与死锁的存储过程机器堵塞的语句:

sp_MSdel_dboBillNumberRecord:(死锁牺牲品)

delete [dbo].[BillNumberRecord]

where [Guid]=@pkc1and[Account]
=@pkc2andmsrepl_tran_version=@msrepl_tran_version

trg_MSsync_upd_BillNumberRecord:

update [dbo].[BillNumberRecord]setmsrepl_tran_version=@c7

where  [Guid]
= @c1 and
[Account] = @c2

EXEC[dbo].[GetBillnumberBySID_Test]:

操作为:更新单据表的单据号列为最大单据号(此处语句就不显示了)

资源列表如下:

--  deadlockvictim=processe988e0

resource-list

keylock
objectname= BillNumberRecordindexname=聚集索引id=locke1193c0mode=RangeX-X

owner-list

owner id=processcdac70mode=RangeX-X

waiter-list

waiter id= processe988e0mode=UrequestType=wait

keylock
objectname= BillNumberRecordindexname=主键(非聚集索引)id=lock58d986c0mode=U

owner-list

owner id= processe988e0mode=U

waiter-list

waiter id=processcdac70mode=UrequestType=wait

接下来在数据库中执行查看执行计划,到底处理了哪些语句:

EXEC [dbo].[GetBillnumber_Test]@Account=‘Account‘

结果如下,按存储过程内部逻辑:

a. 首先查找这个账号当前的单据号(聚集索引查找)(UPDLOCK,HOLDLOCK)

b.单据号存在,生成一个新的最大单据号,更新回表中(聚集索引查找和更新)

c.扫描伪表inserted(聚集索引),在触发器trg_MSsync_upd_BillNumberRecord中

d. 扫描伪表deleted(聚集索引),在触发器trg_MSsync_upd_BillNumberRecord中

e. 触发器trg_MSsync_upd_BillNumberRecord执行更新msrepl_tran_version,(索引查找,更新聚集索引)

f.最后将同步的事务和命令插入到队列表中

上面能出现索引查找的,只有trg_MSsync_upd_BillNumberRecord了。使用主键非聚集索引(guid,account)查找,更新聚集索引(account)。

这算是设计上的问题,作为同步中的主键,最好也是聚集索引,表主键,在同步触发中是不会更改的,而同步是经常触发更改的,主键又不是聚集索引,将频繁地更改。但是现在数据已经同步中,不能更改了,只能使用其他方法。而没有同步的时候,并发是正常的,没出现死锁。就没要更改原来获取单据号的存储过程了,也没有什么可以改了。

既然是下面这个语句引起的,那就想办法更改索引。

update [dbo].[BillNumberRecord]setmsrepl_tran_version=@c7

where  [Guid]
= @c1 and
[Account] = @c2

最后解决死锁方法是:

更改同步触发器[dbo].[trg_MSsync_upd_BillNumberRecord]

将触发器内的更改语句注释。

update[dbo].[BillNumberRecord] set msrepl_tran_version = @c7

where  [Guid] = @c1 and [Account]= @c2

设置新的更新方法,并强制加上聚集索引。这样就能使用聚集索引查找,并且是聚集索引更新!

update bsetmsrepl_tran_version=@c7

from [dbo].[BillNumberRecord]asbwith(index=IX_BillNumberRecord)

where  [Guid]
= @c1 and
 [Account]=
@c2

测试的时候打开了十几个SQLQuerySrees,除了几个是其他账号,其余都是相同账号。

这里要说明一下,为什么打开十几个。

因为我们要模拟十几个账号同时操作。SQLQuerySrees虽然有两个选择设置——迭代次数和线程数。

迭代次数:是重复执行的次数

线程数:是创建多少个新的数据库session连接

迭代次数和线程数只是模拟并发,为了模拟在某一时刻同时操作才设置更多。虽然打开不同线程,但是似乎还是顺序创建。

这是某一刻查看的锁资源情况,如下图

此刻只有spid=199
的会话正在获取单据号,使用的是范围锁 RangX_X,并且对表加上意向排他锁(只能查询不能更改),而其他线程都是在查询单据号并想获取锁RangS_U,因此都在等待。结果再日志中是不会再出现死锁了!!~同步也是正常的!~而如果没有更改触发器,出现了死锁,并且要处理死锁,执行时间反而更长。

至此,同步中并发获取最大单据号的测试就到这吧。

时间: 2024-10-11 23:12:15

SqlServer 可更新订阅中在订阅库并发获取最大单据号测试及解决法案!的相关文章

SQLServer 可更新订阅数据在线架构更改(增加字段)方案

之前一直查找冲突发布和订阅数据不一致的原因,后来发现多少数据库升级引起,因为一直以来都是在发布数据库增加字段,订阅也会自动同步.在此时如果订阅队列有数据,这些数据将丢失.参考上一篇说明:SQLServer 可更新订阅数据冲突的一个原因 .当在发布数据库增加一个字段时,系统同步存储过程和触发器都会重新生成,这会导致仍在队列中的数据无法正常同步.订阅队列中的命令将因"同步"后消失,代理有可能出错,但也会自动回复正常!~ 这周测试了一些方法,最终算是确定一个方案可行的,虽然麻烦和耗时. 首先

SqlServer 可更新订阅队列读取器代理错误:试图进行的插入或更新已失败

原文:SqlServer 可更新订阅队列读取器代理错误:试图进行的插入或更新已失败 今天发现队列读取器代理不停地尝试启动但总是出错: 其中内容如下: 队列读取器代理在连接"PublicationServer"上的"pubDB"时遇到错误"试图进行的插入或更新已失败, 原因是目标视图或者目标视图所跨越的某一视图指定了 WITH CHECK OPTION, 而该操作的一个或多个结果行又不符合 CHECK OPTION 约束.". 请确保正确定义了分发

SqlServer 可更新订阅升级字段队列数据丢失原因

原文:SqlServer 可更新订阅升级字段队列数据丢失原因 之前简单描述过数据冲突发生的原因:SQLServer可更新订阅数据冲突的一个原因 ,但具体内部原理是怎么丢失的还不清楚,今天补充说明.可更新订阅,在订阅数据库操作数据,数据实时同步到发布数据库中. 经测试,有3种情况会导致订阅队列的数据丢失: 1.更改字段类型 2.增加删除字段 3.表对象发布 另:添加默认值约束,存储过程,函数正常!订阅队列数据不丢失!(触发器未设置同步) 具体模拟步骤如下: 1.在发布中添加表 2.停止队列读取器代

SqlServer 禁止架构更改的复制中手动修复使在发布和订阅中分别增加字段同步

由于之前的需要,禁止了复制架构更改,以至在发布中添加一个字段,并不会同步到订阅中,而现在又在订阅中添加了一个同名字段,怎么使这发布和订阅的两个字段建立同步关系呢? 下面就测试更改:此次发布类型为事务复制的可更新订阅,其他类型的发布没有测试. 首先建立事务复制的可更新订阅,建立好之后. 在发布创建一张测试表: CREATE TABLE [dbo].[DemoTab]( [Guid] [uniqueidentifier] NOT NULL, [SID] [varbinary](85) NOT NUL

SQLServer 可更新订阅数据冲突的一个原因

可更新订阅为什么有冲突? 可更新订阅中,当升级增加一个字段时,通常在发布服务器的发布数据库中增加,对表增加字段后,发布自动同步到订阅数据库中(复制架构更改=true).但是,如果此时在订阅数据库进行DML操作,数据将不会同步到发布表中:这些差异数据在订阅表中如果一直未进行DML 操作,也就不会再次同步到发布中,存在差异. 复制配置环境: 可更新订阅事务复制 发布和订阅冲突都以订阅为准 使用排队更新 在订阅操作 冲突测试结果(以下为: 当数据存在不一致的情况下,对订阅再次操作会引起冲突,冲突策略会

在Unity3D中基于订阅者模式实现事件机制

??各位朋友,大家好,欢迎大家关注我的博客,我是秦元培,我的博客地址是http://qinyuanpei.com.今天博主想和大家分享的是在Unity3D中基于订阅者模式实现消息传递机制,我们知道Unity3D中默认提供了一种消息传递机制SendMessage,虽然SendMessage使用起来的确非常简单,可是它的这种简单是建立在付出一定的代价的基础上的.经常有朋友提及不同的模块间如何进行通信的问题,可能答案最终会落到单例模式.委托和事件机制这些关键词上,在这种情况下本文所探讨的内容可能会帮助

用postal.js在AngularJS中实现订阅发布消息

用postal.js在AngularJS中实现event bus 用postal.js在AngularJS中实现event bus 理想状态下,在一个AngularJS应用中,控制器都应该是相互独立的代码单元,它们之间不应该有任何的相互引用.但是有些时候,你还是需要让控制器在你的应用中相互交流.例如你现在有一个叫做Orders的控制器,它需要告诉一个叫做Cart的控制器其中需要添加一个新项目. 实现这种类型的交流方式的最好的方法之一就是使用event bus. postal.js正是一个在JS中

SpringBoot中RedisTemplate订阅发布对象

解说 RedisMessageListenerContainer Redis订阅发布的监听容器,你的消息发布.订阅配置都必须在这里面实现 addMessageListener(MessageListenerAdapter,PatternTopic) 新增订阅频道及订阅者,订阅者必须有相关方法处理收到的消息. setTopicSerializer(RedisSerializer) 对频道内容进行序列化解析 MessageListenerAdapter 监听适配器 MessageListenerAd

性能优化——统计信息——SQLServer自动更新和自动创建统计信息选项 (转载)

原文译自:http://www.mssqltips.com/sqlservertip/2766/sql-server-auto-update-and-auto-create-statistics-options/?utm_source=dailynewsletter&utm_medium=email&utm_content=headline&utm_campaign=2012913 统计信息是如何提高SQLServer查询性能的?统计直方图用作在查询执行计划中查询优化器的选择依据.