SQL Server Service Broker的陷阱

文章讨论了在C#中使用SQLDependency时遇到的问题,特别是当查询涉及多个表的JOIN操作时可能导致数据库锁定和性能下降。作者强调了Microsoft文档中未明确指出的限制,即不应在依赖查询中使用JOIN。文中通过一个实际案例展示了复杂的查询如何导致服务代理和数据库的高负载,提出了解决方案,即简化查询并分离事件触发与数据获取两个步骤,以提高系统效率。
摘要由CSDN通过智能技术生成

目录

介绍

背景

SQLDependency 要求

案例介绍

什么是构建

进一步调查

提醒

看法


介绍

C# SQLDependency对象中使用查询并将该查询设置为数据库中的事件时,很容易误用SQL Server中的服务代理。当查询从多个表请求数据时,数据库最终可能会陷入数据锁定状态。当您遇到此问题时,您将从BINGGoogle获得0个答案来解决这个问题。

在微软官方文档中,为查询设置了要求。只要保持查询简单,就很容易在数据库中造成灾难而不知情。特别是对于懒惰的开发人员来说,这是一个学习曲线。此外,对于微软来说,它对我们的情况做出了快速反应,但在两周后似乎失去了兴趣,因为我表示我的发现并非不可想象。如何与服务代理合作而不会遇到麻烦是一个感知问题。

还有更多关于服务代理和问题的文章,但没有一篇指出原因:

很好的例子:

在本文中,给出了一个修复程序。

背景

在我们的生产环境中,我们有时会出现很大的性能下降。去年,它已成为日常问题。简单的更新查询最多需要2分钟。该更新的查询计划显示了一个充满并行性的巨大计划。查询如下所示:

UPDATE table SET FIELD = VALUE Where ID = IdValue;

因此,并行性是完全出乎意料的行为。重新启动数据库后,正常的查询计划再次出现。一天之内,问题又回来了。

SQLDependency 要求

Microsoft在此处提供了你在SQLDependency中使用的查询的下一个要求。

在支持的SELECT语句的标题下,未提及最重要的规则:语句不得包含JOIN。我将解释为什么我认为您不应该在查询中使用超过1个表。

案例介绍

我们的数据库(近100个表)有6个代表工作流程的重要表格。这些表可以通过主键ID字段联接。这6个表包含47个最需要的列,以便能够了解客户项目中涉及的内容和完成的工作。测量完成后,机组人员必须拒绝或接受测量。该复选框是服务代理应响应的事件。在该事件之后,其他流程将在我们的工作流程中启动。

什么是构建

在设置SQLDependency的服务中,开发人员认为在查询中询问所有这47个字段是明智的。由于不禁止在这些查询中使用JOIN,并且查询非常简单,因此没有给出任何错误。用于测量的应用程序解决了所有问题。更好的是,我的电话响个不停。

我们最初认为测量系统有问题,因为我们在那里看到了问题。额外的问题:我们永远无法分辨6个表中的哪一个必须处理update查询中的并行性问题。它总是更新。给定时间后,我们决定停止具有依赖项的服务,每个人都可以再次工作。

那是我们调查查询的那一刻。我们真正需要从活动中了解什么?

我们的查询如下所示:

SELECT tbl1.Field1, tbl1.Field2, tbl1.Field3, _
       tbl1.Field4, tbl1.Field5, tbl1.Field6, tbl1.Field7,
tbl2.Field1, tbl2.Field2, tbl2.Field3, tbl2.Field4, _
             tbl2.Field5, tbl2.Field6, tbl2.Field7,
tbl3.Field1, tbl3.Field2, tbl3.Field3, tbl3.Field4, _
             tbl3.Field5, tbl3.Field6, tbl3.Field7,
tbl4.Field1, tbl4.Field2, tbl4.Field3, tbl4.Field4, _
             tbl4.Field5, tbl4.Field6, tbl4.Field7,
tbl5.Field1, tbl5.Field2, tbl5.Field3, tbl5.Field4, _
             tbl5.Field5, tbl5.Field6, tbl5.Field7,
tbl6.Field1, tbl6.Field2, tbl6.Field3, tbl6.Field4, _
             tbl6.Field5, tbl6.Field6, tbl6.Field7, tbl6.lastModified
FROM dbo.Table1 as tbl1
Join dbo.Table2 as tbl2 on tbl2.table1Id = tbl1.Id
Join dbo.Table3 as tbl3 on tbl3.table2Id = tbl2.Id
Join dbo.Table4 as tbl4 on tbl4.table3Id = tbl3.Id
Join dbo.Table5 as tbl5 on tbl5.table4Id = tbl4.Id
Join dbo.Table6 as tbl6 on tbl6.table5Id = tbl5.Id
Where tbl6.lastModified > @lastModified
AND tbl6.IsAccepted IS NOT NULL

根据微软的说法,这是对SqlDependency对象的有效查询。

进一步调查

上述查询的最大问题是没有人设置字段IsAccepted。当一个拥有400人的组织向所有这6个表添加数据行时,元组的集合会变得很大,因为您永远等待IsAccepted设置为falsetrue。因此,重新启动SQL服务器后,大约需要一个小时才能再次出现相同的问题。

提醒

首先,您必须知道SQLDependency在服务代理中设置为最多2分钟。如果事件发生较早,则该事件会通知应用程序它已发生。如果没有,则服务代理将再次设置事件2分钟。

服务代理将您的查询设置为数据库中的索引视图,模式系统!这是微软的某个人可以告诉我的。在这里,当连接SQLDependency中的多个表时,问题就开始了。我们使用了6个表中的47个字段,而我们只需要知道测量被接受或拒绝后测量表的涉及Id

因此,当在索引视图中创建 47 个字段(优先级较低 - 仍然是优先级)时,它会对您的数据库产生各种影响。我们在查询中发现的并行性显示了一些奇怪的信息。UPDATE

<OutputList>
    <ColumnReference Database="[TDMS-TEST]" Schema="[sys]" 
    Table="[query_notification_1626853258]" Column="id" />

您应该在数据库中将结尾的数字读取为object_id。通过下一个查询,您可以找到以下索引:

SELECT IndexName = i.Name, ColName = c.Name,  *
FROM sys.indexes i
     INNER JOIN sys.index_columns ic ON ic.object_id = i.object_id
                                        AND ic.index_id = i.index_id
     INNER JOIN sys.columns c ON c.object_id = ic.object_id
                                 AND c.column_id = ic.column_id
WHERE 1 = 1
AND I.OBJECT_ID = 1626853258
ORDER BY i.Name;
You cannot drop these indexes. The service broker can!

但是,当要求将6个表中的47个字段作为索引视图进行监视时,您的数据库需要每2分钟创建和删除索引。继续检查6个表,其中1个表是否有更改。

看法

我们真正需要从服务代理那里得到什么?在一个表中,一个位域相对于上一次修改的值从null变为非null的事件。那么为什么我们需要其他45个字段呢?只是在生产数据库中性能下降。

在我们的例子中,我们希望每个人都提供大量数据。最好将查询分为两部分。

1部分是依赖项查询,它反映了要通知的事件。第2部分是满足数据需求所需的查询。

例:

DependencyQuery就像这样:

SELECT tbl6.Id, tbl6.lastModified
FROM dbo.Table6 as tbl6
WHERE tbl6.LastModified > @LastModified
AND tbl.IsAccepted IS NOT NULL

工作流的查询为:

SELECT tbl1.Field1, tbl1.Field2, tbl1.Field3, tbl1.Field4, _
       tbl1.Field5, tbl1.Field6, tbl1.Field7,
tbl2.Field1, tbl2.Field2, tbl2.Field3, tbl2.Field4, _
             tbl2.Field5, tbl2.Field6, tbl2.Field7,
tbl3.Field1, tbl3.Field2, tbl3.Field3, tbl3.Field4, _
             tbl3.Field5, tbl3.Field6, tbl3.Field7,
tbl4.Field1, tbl4.Field2, tbl4.Field3, tbl4.Field4, _
             tbl4.Field5, tbl4.Field6, tbl4.Field7,
tbl5.Field1, tbl5.Field2, tbl5.Field3, tbl5.Field4, _
             tbl5.Field5, tbl5.Field6, tbl5.Field7,
tbl6.Field1, tbl6.Field2, tbl6.Field3, tbl6.Field4, _
             tbl6.Field5, tbl6.Field6, tbl6.Field7
FROM dbo.Table1 as tbl1
Join dbo.Table2 as tbl2 on tbl2.table1Id = tbl1.Id
Join dbo.Table3 as tbl3 on tbl3.table2Id = tbl2.Id
Join dbo.Table4 as tbl4 on tbl4.table3Id = tbl3.Id
Join dbo.Table5 as tbl5 on tbl5.table4Id = tbl4.Id
Join dbo.Table6 as tbl6 on tbl6.table5Id = tbl5.Id
Where tbl6.Id = @Id

因此,在C#类中,我们维护在服务代理中设置为事件的表的lastmodified值(以防止内存中有大量记录)。

结果(可能超过1行记录,具体取决于数据库的使用范围)后,我们将Id值设置为第二个查询的参数,该查询直接调用数据库。

https://www.codeproject.com/Articles/5344594/Pitfalls-of-the-SQL-Server-Service-Broker

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值