SQL Service Broker 的 Azure 替代品

SQL Azure Alternative to Service Broker

我们的软件是连接到 SQL 数据库的 Windows 应用程序的集合。目前,我们所有的客户站点都有自己的服务器和 SQL 服务器数据库,但是我正在努力使我们的软件也能与 Azure 托管的数据库一起使用。

我用它遇到了一个障碍,到目前为止,在谷歌搜索时没有发现任何特别有用的东西。

当前SQL服务器版本包括我编写的数据库审计系统,它执行以下操作:-

C# 应用程序在连接字符串中包含有关它是哪个程序和版本以及哪个用户当前登录的信息。

重要 tables 具有更新和删除触发器,它们将任何更改的详细信息发送到 Service Broker 队列。 (我不记录插入)。

Service Broker 然后遍历队列,并将更改的详细信息记录到单独的 AuditLog table。

这些详细信息包括:-

Table,行的PK改变了,字段,旧值,新值,是更新还是删除,date/time改变,登录我们软件的用户的UserID , 以及哪个程序和版本进行了更改。

一切都很好,我希望保持 Azure 版本的系统不变,但不幸的是 SQLAzure 没有 Service Broker。

所以,我需要寻找替代方案,正如我提到的那样,事实证明这很麻烦。

有 SQL Azure Managed Instances,它确实有 Service Broker,但它们太贵了,我们甚至无法考虑。我们的客户没有一个愿意每月支付那么多钱。

我看过的其他任何东西似乎都没有我需要的一切。特别是记录哪个程序、版本和 UserID。请注意,这不是 SQL 登录用户 ID,它对每个人都是相同的,这是用户 table 用来登录我们软件的 ID,并在连接中传递字符串.

所以,理想情况下,我想要类似于我所拥有的东西,只是用其他东西代替 Service Broker:-

C# 应用程序在连接字符串中包含有关它是哪个程序和版本以及哪个用户当前登录的信息。

重要 tables 具有更新和删除触发器,它们将任何更改的详细信息发送到某种 异步队列

然后在正常程序流之外通过队列,并将更改的详细信息记录到单独的 AuditLog table。

正常程序流程之外的异步队列和处理很重要。显然我可以很容易地让更新和删除触发器完成所有处理并将记录添加到 AuditLog table,事实上那是系统的 v1.0,但问题是 SQL将等到触发器完成后再返回 C# 程序。当发生多个更新或删除时,这会导致 C# 程序显着变慢。

我很乐意研究其他日志系统而不是上面的日志系统,但是只记录数据更改而没有我传递的额外信息的东西,特别是程序、版本和用户 ID,将没有任何用处我。我们的用户在查询他们认为不正确的更改时总是想知道此信息。

那么,对于 SQL Azure 的 Service Broker 的替代方案有什么建议吗? TIA!

好的,看来我有一个潜在的解决方案:临时 Tables

Temporal Tables 在 Azure 中工作,并在历史记录中记录一个新行 table 每当发生变化时:-

CREATE TABLE dbo.LMSTemporalTest   
(    
  [EmployeeID] INT NOT NULL PRIMARY KEY CLUSTERED   
  , [Name] NVARCHAR(100) NOT NULL  
  , [Position] NVARCHAR(100) NOT NULL   
  , [Department] NVARCHAR(100) NOT NULL  
  , [Address] NVARCHAR(1024) NOT NULL  
  , [AnnualSalary] DECIMAL (10,2) NOT NULL  
  , [UpdatedBy] UniqueIdentifier NOT NULL
  , [UpdatedDate] DateTime NOT NULL
  , [ValidFrom] DateTime2 (2) GENERATED ALWAYS AS ROW START HIDDEN
  , [ValidTo] DateTime2 (2) GENERATED ALWAYS AS ROW END HIDDEN
  , PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)  
)    
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.LMSTemporalTestHistory));  
GO

然后我可以在 table...

中插入一条记录
INSERT INTO LMSTemporalTest(EmployeeID,Name,Position,Department,Address,AnnualSalary, UpdatedBy, UpdatedDate)
VALUES(1, 'Bob', 'Builder', 'Fixers','Oops I forgot', 1, '0D7F5584-C79B-4044-87BD-034A770C4985', GetDate())
GO

更新行...

UPDATE LMSTemporalTest SET 
Address = 'Sunflower Valley, Bobsville',
UpdatedBy = '2C62290B-61A9-4B75-AACF-02B7A5EBFB80',
UpdatedDate = GetDate()
WHERE EmployeeID = 1
GO

再次更新行...

UPDATE LMSTemporalTest SET 
AnnualSalary = 420.69,
UpdatedBy = '47F25135-35ED-4855-8050-046CD73E5A7D',
UpdatedDate = GetDate()WHERE EmployeeID = 1
GO

然后查看结果:-

SELECT * FROM LMSTemporalTest
GO

EmployeeID  Name    Position    Department  Address AnnualSalary    UpdatedBy   UpdatedDate
1   Bob Builder Fixers  Sunflower Valley, Bobsville 420.69  47F25135-35ED-4855-8050-046CD73E5A7D    2019-07-01 16:20:00.230

注意:因为我将它们设置为隐藏,所以不会显示 Valid From 和 Valid To

检查日期/时间范围的变化:-

SELECT * FROM LMSTemporalTest  
FOR SYSTEM_TIME BETWEEN '2019-Jul-01 14:00' AND '2019-Jul-01 17:10'   
WHERE EmployeeID = 1
ORDER BY ValidFrom;
GO

EmployeeID  Name    Position    Department  Address AnnualSalary    UpdatedBy   UpdatedDate
1   Bob Builder Fixers  Oops I forgot   1.00    0D7F5584-C79B-4044-87BD-034A770C4985    2019-07-01 16:20:00.163
1   Bob Builder Fixers  Sunflower Valley, Bobsville 1.00    2C62290B-61A9-4B75-AACF-02B7A5EBFB80    2019-07-01 16:20:00.197
1   Bob Builder Fixers  Sunflower Valley, Bobsville 420.69  47F25135-35ED-4855-8050-046CD73E5A7D    2019-07-01 16:20:00.230

我什至可以查看历史记录table

SELECT * FROM LMSTemporalTestHistory
GO

EmployeeID  Name    Position    Department  Address AnnualSalary    UpdatedBy   UpdatedDate ValidFrom   ValidTo
1   Bob Builder Fixers  Oops I forgot   1.00    0D7F5584-C79B-4044-87BD-034A770C4985    2019-07-01 16:20:00.163 2019-07-01 16:20:00.16  2019-07-01 16:20:00.19
1   Bob Builder Fixers  Sunflower Valley, Bobsville 1.00    2C62290B-61A9-4B75-AACF-02B7A5EBFB80    2019-07-01 16:20:00.197 2019-07-01 16:20:00.19  2019-07-01 16:20:00.22

注意:当前行不会显示,因为它仍然有效

我们所有重要的 table 都已经有 CreatedBy、CreatedDate、UpdatedBy 和 UpdatedDate,因此我可以将它们用于 UserID 日志记录。没有明显的方法来处理程序和版本作为标准,但我总是可以添加另一个隐藏字段并使用触发器来设置它。

编辑:实际测试过

第一个障碍是:您真的可以将现有的 table 更改为临时 Table,答案是:可以!

ALTER TABLE Clients ADD 
    [ValidFrom] DateTime2 (2) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL DEFAULT '1753-01-01 00:00:00.000',
    [ValidTo] DateTime2 (2) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL DEFAULT '9999-12-31 23:59:59.997',
    PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)  
GO

ALTER TABLE Clients SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.ClientsHistory))
GO

上面重要的一点是 ValidFrom 和 ValidTo 字段的默认值。它仅在 ValidTo 是 DateTime2 的最大值时才有效,因此为“9999-12-31 23:59:59.997”。 ValidFrom 似乎并不重要,所以我将其设置为最小值以涵盖所有内容。

好的,所以我已经转换了 table,但它现在有两个额外的字段,non-Azure table 没有,理论上是隐藏的,但我们的软件抱怨他们?

好像不是。启动软件,在客户端table编辑一条记录并保存,软件一点反应都没有。

检查了 Clients 和 ClientsHistory tables:-

SELECT * FROM Clients  
FOR SYSTEM_TIME BETWEEN '1753-01-01 00:00:00.000' AND '9999-12-31 23:59:59.997'   
WHERE sCAccountNo = '0001064'
ORDER BY ValidFrom

显示两条记录,原始记录和编辑后的记录,并且现有的 UpdatedUser 和 UpdatedDate 字段显示正确,因此我知道谁在何时进行了更改。

SELECT * FROM ClientsHistory

显示原始记录,ValidTo 设置为更改日期,

一切似乎都很好,现在我只需要检查它仍然只是 returns 查询和我们软件的当前记录:-

SELECT * FROM Clients  
WHERE sCAccountNo = '0001064'

只返回一条记录,不显示 HIDDEN 字段、ValidFrom 和 ValidTo。

在我们的软件中搜索了 Client 0001064,再次返回了一条记录,并没有抱怨多出的两个字段。

仍然需要设置一些触发器并添加另一个 HIDDEN 字段来记录连接字符串中的程序和版本,但看起来 Temporal Tables 给了我一个可行的审计选项。

到目前为止唯一的缺点是它为每组更改创建了一个完整的记录行,这意味着您必须将它与其他记录进行比较以找出更改的内容,但我可以写一些东西来简化它足够容易。