检测SQL Server表中的更改


13

在我的应用程序中,有了在SQL Server 2012上运行的数据库,我得到了一个作业(计划任务),该作业会定期执行昂贵的查询并将结果写入表中,以供应用程序以后查询。

理想情况下,仅当自上次执行查询以来发生某些更改时,我才想运行该昂贵的查询。由于源表非常大,因此我不能只选择所有候选列之类的校验和。

我有以下想法:

  • 每当我更改源表中的内容时,都应将最后更改的时间戳,“必须查询”标志或类似的内容显式地写入跟踪表。
  • 使用触发器执行相同的操作。

但是,我真的很想知道是否存在一种轻量级的方法来检测表上的更改,而无需我明确跟踪写入。例如,我可以获取表的“当前” ROWVERSION值或诸如此类的东西吗?

Answers:


14

不,没有。任何类型的“最后更新时间”跟踪都会遇到严重的性能问题,因为来自所有事务的所有更新都将尝试更新跟踪“最后更新时间”的一条记录。这实际上将意味着只有一个事务可以随时更新表,而所有其他事务都必须等待第一个事务提交。完成序列化。仅仅为了知道最近一次更新何时发生而愿意忍受这种性能损失的管理员/开发人员的数量可能很小。

因此,您只能通过自定义代码来处理它。这意味着触发,因为替代方案(从日志记录中检测)是仅保留给事务复制(或CDC更改我)的特权。请注意,如果您尝试通过“最后更新时间”列进行跟踪,那么您将面临上述的序列化问题。如果更新并发很重要,则您必须使用队列机制(触发器使用INSERT,然后一个过程聚合插入的值以制定“上次更新时间”)。不要尝试用一些“聪明”的解决方案作弊,例如偷偷摸摸地使用当前身份或查找sys.dm_db_index_usage_stats。还有每条记录的“ updated_at”列,例如Rails的时间戳,

有没有“轻巧”的选择?确实有一个,但是很难说它是否对您有用,很难做到正确:查询通知。Query Notification正是这样做的,如果任何数据有更改并且您需要刷新查询,它将设置一个通知。尽管大多数开发人员只熟悉.Net化身为SqlDependency,但是查询通知可以用作检测数据更改的长期有效机制。与真正的更改跟踪相比,它将是真正的轻量级,并且其语义更接近您的需求(某些内容已更改,因此您需要重新运行查询)。

但是最后,在您的位置上,我真的会重新考虑我的假设,然后再回到制图板上。也许您可以使用日志传送或复制在其他服务器上设置报告数据库。我读到的内容是,您需要一条适当的ETL管道和一个分析数据仓库...


那么,如果不能依靠其提供的信息,Microsoft为什么还要麻烦创建sys.dm_db_index_usage_stats?
Craig Efrein 2014年

它不是为更改跟踪而设计的DMV 。对于预期的目的非常可靠,这是性能调整。
Remus Rusanu 2014年

8

在这里,我似乎已经晚了两年,但是确实有一种非常轻巧的方式来完成您所要求的工作。

有两种SQL Server机制可以为您提供帮助。您的最终解决方案可能是两者的混合。

变更跟踪。SQL Server能够监视特定的表,仅记录更改的行(按其主键值)以及更改的类型(插入,更新或删除)。在一组表上设置更改检测后,轻量级查询可以告诉您自上次检查以来是否对该表进行了任何更改。开销大约与维护其他简单索引相同。

Rowversion /时间戳。这是一种8字节的varbinary列类型(可广播到BigInt),每当包含或更新包含一行的行时,该列类型将在数据库范围内递增(这对删除操作没有帮助)。如果为这些列建立索引,则可以通过比较自上次评估以来的MAX(timestamp)与它的值来轻松判断行数据是否已更改。由于该值是单调递增的,因此如果新值大于您上次检查时的值,则可以可靠地表明数据已更改。


7

如果源仅用于插入,请为其提供一IDENTITY列。进行数据传输时,您将记录写入的最高值。在下一次传输期间,您只需要查询大于上一次传输记录的值。我们这样做是为了将日志记录传输到数据仓库。

对于可更新的行,请添加“脏”标志。它将具有三个值-干净,脏和已删除。日常查询将不得不省略标记设置为“已删除”的行。这在维护,测试和运行时将是昂贵的。在大查询之后,您提到必须删除标记为删除的所有行,并为所有其他行重置标志。这将无法很好地扩展。

变更跟踪是更轻松的变更数据捕获替代方案。它不会告诉您哪些值已更改,只是自上次查询以来该行已更改。内置功能有助于检索更改的值并管理跟踪。我们已经成功地使用CT在一个100,000,000行表中每天处理大约100,000个更改。

查询通知仍然在结果集级别上发挥更高的作用。从概念上讲,这就像定义一个视图。如果SQL Server检测到通过该视图返回的任何行已更改,它将向应用程序发送一条消息。没有指示更改了多少行或哪些列。只有一条简单的消息说“发生了什么事”。查询和响应取决于应用程序。正如您所想象的,实际上它要复杂得多。在如何定义查询方面存在一些限制,并且可能会为更改后的数据以外的条件触发通知。通知触发时将其删除。如果随后发生了其他感兴趣的活动,则不会再发送任何消息。

在OP的问题中,QN的优点是安装开销低,运行时成本低。建立和维持严格的订阅消息反应机制可能是一项巨大的努力。由于数据表很大,因此可能会对其进行频繁更改,这意味着该通知很可能在大多数处理周期中触发。由于没有迹象表明增量变化的增量处理将无法实现,就像CT或CDC那样。由于错误触发而造成的开销令人厌烦,但是即使在最坏的情况下,也不必比现在更频繁地运行昂贵的查询。


3

SqlTableDependency

SqlTableDependency是一个高级实现组件,用于访问包含SQL Server数据库上的表记录值的通知。

SqlTableDependency是通用C#组件,用于在指定数据库表的内容更改时接收通知。

.NET SqlDepenency有什么区别?

基本上,主要区别在于SqlTableDependency发送事件,该事件包含插入,更改或删除的记录的值,以及在表上执行的DML操作(插入/删除/更新):SqlDepenency不会告诉您数据已更改。在数据库表中,他们只说发生了什么变化。

看看GITHUB项目


1

如果您期望的更新影响索引(并且在此情况下),则可以使用系统表sys.dm_db_index_usage_stats来检测对有关表的索引的最新更新。您将使用该last_user_update字段。

例如,要获取最近更新的表:

select
    object_name(object_id) as OBJ_NAME, *
from
    sys.dm_db_index_usage_stats
where
    database_id = db_id(db_name())
order by
    dm_db_index_usage_stats.last_user_update desc

或者,检查自特定日期以来是否更改了特定表:

select
    case when count(distinct object_id) > 0 then 1 else 0 end as IS_CHANGED
from
    sys.dm_db_index_usage_stats
where
    database_id = db_id(db_name())
    and object_id = object_id('MY_TABLE_NAME')
    and last_user_update > '2016-02-18'

您对雷木斯(Remus)的上述评论有何看法?“不要试图欺骗一些'聪明'的解决方案,例如偷偷摸摸地使用当前身份或查找sys.dm_db_index_usage_stats。” (另请参见他的回答下方的评论。)
Fabian Schmied

1
@FabianSchmied有趣的是-我没有看到,当我添加答案时,除了Remus的其他答案之外,我找不到任何权威的内容来表明它在此用例中不可靠;MS页面用于dm_db_index_operational_stats显示问题(已清除为元数据缓存清除),但不适用于dm_db_index_usage_stats。我发现的唯一问题是索引重建,服务器重新启动和数据库分离清除了使用情况统计信息,似乎不适用于此处。有兴趣查看有关此方面的可靠信息。
Geoff
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.