【问题标题】:Can row-based counters be made to behave like SEQUENCE or IDENTITY?是否可以使基于行的计数器表现得像 SEQUENCE 或 IDENTITY?
【发布时间】:2020-03-30 11:37:11
【问题描述】:

SEQUENCE 和 IDENTITY 为所有会话和事务提供全局唯一的递增值,无论隔离级别如何,它们不受事务回滚的影响。是否可以使用基于行的计数器获得相同的行为,如果可以,如何实现?

最大的问题似乎是在任何可能打开的事务范围之外执行计数器更新。服务器能否链接自己以一种“远程模式”调用自己的过程,这种模式可以穿透任何当前活动的事务和隔离模式?

“远程”调用可以调用一个简单的过程,像这样在自动提交模式下运行:

create or alter procedure dbo.next_value (@id int, @result int = null output)
as
begin
    set nocount on;
    update dbo.RowBasedCounters
        set @result = counter_value = counter_value + 1
        where counter_id = @id;
    return @result;
end;

背景:我们需要与 SEQUENCE 或 IDENTITY 完全相同的行为作为外部通信伙伴的文件/传输计数器,每个伙伴需要三个独立的计数器。但是,使用 SEQUENCE 或 IDENTITY 并不真正可行,因为它们需要为每个计数器单独命名数据库对象(即它们是单例且没有键控),并且在任何情况下都有数千个伙伴,因此有数千个计数器。

P.S.:这些计数器编号传输(EDI 文件)和德国医疗保健系统中通信伙伴之间的传输尝试;每对合作伙伴的文件编号从每年 1 开始,传输尝试的编号以 1000 为模数,每对合作伙伴也是如此。 编号必须是连续的。编号方案由标准定义,不在我们的控制范围内。

我们使用的是 MS SQL Server 2014。

注意:由于事务回滚等导致的序列间隙不是问题,因为此类异常可以记录并因此自动记录。此外,可以在事后调查偶尔的辍学,并记录合作伙伴是否以及何时打电话询问序列中出现空缺的原因,只要这些空缺非常少且相距甚远。

【问题讨论】:

  • 这里实际使用的计数器是什么;不是用ROW_NUMBER来代替吗?
  • 真的没有办法按照你提议的方式处理这个问题。但这并不意味着没有一些可行的选择。每个合作伙伴的每个计数器都必须是唯一的,有什么理由吗?在我看来,身份应该适合您,除非您确实需要允许合作伙伴之间重复数字,否则您可以使用单个表格或序列来执行此操作。如果您还存储了 PartnerID,您甚至可以这样做。然后您可以使用 ROW_NUMBER 来获取顺序值。有很多方法可以处理这种情况。
  • 如果您将其用于更新,上述内容可能很容易受到竞争条件的影响。 RETURN 也用于说明 SP 的成功,而不是用于返回数据。 0表示成功,其他都表示失败。你应该在这里使用OUTPUTparameter。
  • @Larnu:这些计数器编号传输(EDI 文件)和德国医疗保健系统中通信伙伴之间的传输尝试;每对合作伙伴的文件编号从每年 1 开始,传输尝试的编号以 1000 为模数,每对合作伙伴也是如此。编号方案由标准定义,不在我们的控制范围内。
  • 这对我来说意义不大;我对德国医疗保健系统一无所知。但这并没有对我说“不,我们不能使用ROW_NUMBER。”,正如我自己和@SeanLange 所建议的那样。

标签: sql-server tsql sql-server-2014


【解决方案1】:

正如 Larnu 和我都说过的,您可以很容易地利用 ROW_NUMBER 来处理单个表。

我创建了一个 Transfers 表并创建了一些示例数据来表示有 2 个合作伙伴。我正在使用 sys.objects 从 2018 年和 2019 年生成一些随机日期,如果您在这两年都没有创建对象,则可能需要调整它,但似乎不太可能。这将为每个合作伙伴生成序列号,并每年重新启动。希望这将是一个很好的起点。

if OBJECT_ID('tempdb..#Transfers') is not null
    drop table #Transfers

create table #Transfers
(
    TransferID int identity
    , PartnerID int
    , TransferDate datetime
)

insert #Transfers
select top 5 1, create_date
from sys.objects
where create_date > '20180101' --to limit to only two years to demonstrate partitioning
order by NEWID()

insert #Transfers
select top 5 2, create_date
from sys.objects
where create_date > '20180101' --to limit to only two years to demonstrate partitioning
order by NEWID()

select *
    , MySeq = ROW_NUMBER()over(partition by PartnerID, datepart(year, TransferDate) order by TransferDate desc)
from #Transfers

【讨论】:

  • Sean,就涉及事务隔离和回滚问题而言,插入/计数行与更新/查询计数器字段有何不同?在这方面,您的方法与update foo set counter = counter + 1 where id = @id 有何不同?我根本看不到它。
  • 这里没有竞争条件。但是这些值也不是静态的。如果删除了一行或插入了一行,编号顺序将动态调整。也许这对你来说是一件坏事。
  • Sean,您的方法似乎遇到了类似于使用(select max(id) from foo) + 1 而不是 IDENTITY 列或序列的问题。并行事务会导致同一个值的多次分配,回滚会导致后面的值被重新编号;确切的效果取决于隔离级别。 AFAICS 您的方法既不能解决“穿透”事务隔离的问题,也不能解决回滚问题;它只会导致每个不同的 value 有一个表,而不是每个不同的 counter 有一个行。
  • 好吧,当您必须以零间隔进行顺序编号时,您正在创造一个不可能的情况。无论您做什么,都会遇到这些潜在问题。
  • 肖恩,由于回滚或删除而偶尔出现的罕见间隙不是问题,正如我在其他地方所说的那样。它们是在没有公司范围的全局序列化的情况下启用并发处理的不可避免的副产品,就像旧的 PDOXUSRS.NET。我要解决的问题是在任何当前活动的事务和/或隔离模式的范围之外有效地运行update T set n = n + 1 where k = @k,就像 SEQUENCE 和 IDENTITY 一样。我已经通过 SSMS、Delphi (ADO) 和 Fox (ODBC) 编写了代码,但是,在我编写它之前,我仍然需要对其进行一些尽职调查。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-02
  • 2020-05-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多