我有一个 Web 服务,可以触发订单的一系列事件。当它收到一条消息告诉它(每秒最多约 2 次)时,它会执行此操作。
我遇到的问题是我有多个 Web 服务实例,有时订单的不同部分可以同时到达每个实例。这会导致事件链针对订单多次触发(错误)。
所以,我在想,我可以做一个表,它只是订单的 ID 和一个标志,说明“事件链”是否已经被触发。
然后我可以在我的代码中开始交易。是这样的:
mySqlConnection.BeginTransaction(); // C# Code
我可以像这样为有问题的订单选择行:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT OrderId, IsChainFired
FROM OrderedChainFlag WITH ( ROWLOCK, XLOCK, HOLDLOCK )
WHERE OrderId = 123456
然后,如果 IsChainFired 为假,则触发“事件链”。然后我会运行:
UPDATE OrderedChainFlag
SET IsChainFired = 1
WHERE OrderId = 123456
然后运行(即使 IsChainFired 为真):
myConnection.CommitTransaction(); // C# Code
这将防止多个服务实例读取链可以同时自由运行。
我的担心(也是我的问题)是锁升级。如果这保持在行锁上,那么它将是解决我的问题的绝佳方法。但是,如果锁定升级到页面或表,那么我就会在我的系统中造成瓶颈。
那么,我能做些什么来确保它保持在行锁级别吗? (或者像这样将数据库锁定系统用作信号量是个好主意吗?)
注意:OrderId 将是表的主键、聚簇索引和分区索引。
您可以使用该
OUTPUT
子句查看是否应“触发”事件链:我的测试台:
运行此命令以查看输出:
使用a
DataReader
读取UPDATE
语句的结果;如果返回一行,.Read()
您知道表已更新,并且实际上,您可以运行事件链。运行
UPDATE
上面的语句只会在它实际将IsChainFired
列更新为1
. 您可以通过运行更新语句两次来证明这一点。第一个显示输出,第二个不显示。OrderId
由于这条语句是原子的,假设您在和上有一个良好的索引IsChainFired
,并且该索引已配置,那么一对一更新将成功,而不会影响锁升级WITH (ALLOW_ROW_LOCKS = ON)
。第二种选择,与上述略有不同,是不指定下一个
OrderID
要处理的;而是简单地选择下一个具有IsChainFired = 0
:本
UPDATE
例中的语句直接从索引返回OrderID
单行。IX_OrderedChainFlag_IsFired
我在我的测试平台的 OrderedChainFlag 表中插入了超过 750,000 行,并运行上面的
UPDATE TOP(1)
语句,使用我提到的索引,并得到以下计划:这里的一个(有争议的)好处是您可能会返回多
TOP(1)
行数据进行处理,也许在您的 Web 服务中使用多个线程。您可能还应该阅读这个问题,以及有关并发性的出色答案。