在数据库开发过程中,经常会遇到性能问题,尤其是在处理复杂的查询时。本文将探讨使用SQLDependency时可能遇到的问题,并提供一些优化策略,以避免数据库性能下降和数据锁定问题。
在生产环境中,曾经遇到性能严重下降的问题,这在去年几乎成为了一个日常问题。一个简单的更新查询最多需要2分钟。该更新的查询计划显示了一个充满并行性的庞大计划。查询看起来像这样:
UPDATE table SET FIELD = VALUE WHERE ID = IdValue;
这种并行性是完全出乎意料的行为。在数据库重启后,正常的查询计划又出现了。但在一天之内,问题又回来了。
微软为SQLDependency中使用的查询提供了以下要求。只要查询简单,很容易在数据库中制造灾难而不知。特别是对于懒惰的开发者来说,这是一个学习曲线。对于微软来说,他们对情况反应迅速,但在两周后似乎失去了兴趣,声称发现的并不是不可想象的。如何使用服务代理而不陷入麻烦,这是一个感知问题。
有很多关于服务代理和问题的文章,但没有一篇文章指出了原因:
在本文中,将提供一个解决方案。
数据库(近100张表)有6个重要的表,代表了工作流程。这些表可以通过主键Id字段连接。这6个表有47个最需要的列,以便能够理解并完成客户项目中涉及和完成的内容。在测量完成后,工作人员必须拒绝或接受测量。那个复选框是服务代理应该响应的事件。其他流程将在工作流程中的该事件之后开始。
在设置SQLDependency的服务中,开发人员认为在查询中请求所有这些47个字段是明智的。由于在这些查询中使用JOIN并不被禁止,而且查询相当直接,没有给出错误。进行测量的应用程序得到了处理所有问题。更好的是:电话响个不停。
最初认为测量系统有问题,因为在那里看到了问题。额外的问题是:永远无法告诉哪6个表中的哪一个必须处理更新查询中的并行性问题。它总是更新。在给定时间后,决定停止依赖服务,每个人都可以再次工作。
那是调查查询的时刻。真正需要从事件中知道什么?
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对象查询。在CodeProject上,提出了几个关于这个问题的问题,但没有得到任何有效的回应:
上述查询的最大问题是没有人设置了字段IsAccepted。当一个有400人的组织向所有这6个表添加数据行时,由于等待IsAccepted被设置为false或true,所以元组的集合变得巨大。所以,在SQL服务器重启后,大约一个小时后,问题又出现了。
首先,必须知道SQLDependency最多设置为服务代理2分钟。如果事件早点发生,事件会通知应用程序它已经发生了。如果没有,那么服务代理将再次为2分钟设置事件。
服务代理将查询设置为数据库中的索引视图,模式sys!这是微软的某个人可以告诉。在这里,当在SQLDependency中连接更多表时,问题就开始了。使用了6个表中的47个字段,而只需要知道测量表的涉及Id在测量被接受或拒绝后。
所以当47个字段在索引视图中创建时(在较低的优先级-仍然是优先级-),它对数据库有各种影响。在UPDATE查询中发现的并行性显示了一些奇怪的信息。
<OutputList>
<ColumnReference Database="[TDMS-TEST]" Schema="[sys]" Table="[query_notification_1626853258]" Column="id" />
<(OutputList>
应该将结尾的数字读作数据库中的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;
不能删除这些索引。服务代理可以!但当要求6个表中的47个字段作为索引视图被监视时,数据库有很多工作要做,每2分钟创建和删除索引。继续检查6个表中的确切1个表的变化。
真正需要从服务代理得到什么?一个表中的一个位字段从null变为非null相对于lastmodified值的事件。那么为什么需要其他45个字段呢?只是为了在生产数据库中降低性能。
在案例中,希望每个人都提供大量数据。最好将查询分成两部分。
第1部分是反映想要被通知的事件的依赖查询。第2部分是需要满足数据需求的查询。
例如:
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值(以防止在内存中有大量的记录),作为事件。