两个数据库的自动递增偶数和奇数用于同步而不影响自动递增属性

时间:2015-03-11 09:33:43

标签: sql sql-server primary-key sync

需要快速帮助。我正在使用bigint自动增量属性的数据库。我有两个需要同步的位置的数据库。由于bigint在不同站点上可能存在主键副本,因此不适合同步。我无法继续使用GUID,因为我需要更改我的代码以及数据库,这对我来说是不可能的。

现在我只有两个位置用于数据库,所以我想如果可以让我的主键自动增量总是在一个位置,在其他位置是奇数。它可以快速解决我的问题。

我如何使用计算列规范或任何其他方式来执行此操作。为了同步我正在使用Microsoft sycn框架。

如果我在同步后使用身份(1,2)服务器或身份(2,2)B服务器,则会干扰下一个增量值。例如,如果在服务器最大ID 3和B服务器当前id是4.在服务器上的同步最大ID现在将是4.我希望A服务器上的新ID应该只有5但实际上它插入6.如何能我解决了这个问题

5 个答案:

答案 0 :(得分:3)

大编辑:(好多了)选项1:

(补充说明:@VladimirBaranov在评论中提到了这一点,但我错过了,但这里有关于如何在这种情况下使用SEQUENCE的充实信息)

我最初的想法是进一步回答这个问题,并且仍然可行,但我认为这个更新的选项将适合您需要的许多服务器。令我困扰的是,我知道在TSQL中有一种正确的方法可以做到这一点,我不记得它是什么。我的大脑终于在今天疏通了它:SEQUENCE。 SQL Server 2012和2014允许您定义序列以生成要在表中使用的一系列数字:

CREATE SEQUENCE oddNums
  START WITH 1
  INCREMENT BY 2;
GO

CREATE SEQUENCE evenNums
  START WITH 0
  INCREMENT BY 2;
GO

然后,不是AUTO INCREMENT你的PK,而是从DEFAULT给他们一个SEQUENCE值(这些是下面链接的小提琴中的表格):

CREATE TABLE oddMirror (
 [id] int PRIMARY KEY DEFAULT NEXT VALUE FOR oddNums, 
 [data] varchar(7)
);

CREATE TABLE evenMirror (
 [id] int PRIMARY KEY DEFAULT NEXT VALUE FOR evenNums, 
 [data] varchar(7)
);

这些序列完全不受合并的影响,无论表中的最新PK是什么,它都将继续永远生成奇数或偶数。

Here is a SQLFiddle这在行动中。

请注意,如果执行此操作(由于DEFAULT子句),则无法将列定义为IDENTITY,因此您必须小心插入id列,否则这应该是关于尽可能直截了当。

这可以通过您想要的任意数量的服务器来完成,只需调整每个SEQUENCE增量的起点和起始位置,但是您可能会有一些困难(并非不可能)的时间添加额外的服务器一旦你的SEQUENCE被定义了。

此外,here is an MSDN blog讨论了在先前版本的SQL Server上模拟SEQUENCE的替代策略。

(不太好)选项2:

(注意:这是我原来的答案)今晚我一直在玩这个,取决于你如何设置东西,我想你可以放弃重新安排桌面完成同步后的每个服务器,基于表中当前的最高ID。你只需要为每个服务器做一些不同的操作,以便在一个甚至另一个上保持新的id奇怪。

所以你有:

CREATE TABLE oddMirror
(id INT NOT NULL IDENTITY(1,2),
data NVARCHAR(10))
GO

CREATE TABLE evenMirror
(id INT NOT NULL IDENTITY(2,2),
data NVARCHAR(10)
GO

同步两个表后,您不知道当前标识种子是奇数还是偶数,因此您需要将每个表上的重置为服务器的正确“下一个”值。所以,在oddMirror

DECLARE @maxId INT
DECLARE @newSeed INT
SET @maxId = (SELECT MAX(id) FROM oddMirror)
SET @newSeed = (SELECT CASE WHEN @maxId % 2 = 1 THEN @maxId ELSE @maxId -1 END)

DBCC CHECKIDENT('dbo.oddMirror', RESEED, @newSeed)
GO

evenMirror上几乎完全相同的过程:

DECLARE @maxId INT
DECLARE @newSeed INT
SET @maxId = (SELECT MAX(id) FROM evenMirror)
SET @newSeed = (SELECT CASE WHEN @maxId % 2 = 0 THEN @maxId ELSE @maxId -1 END)

DBCC CHECKIDENT('dbo.evenMirror', RESEED, @newSeed)
GO

所以基本上,在oddMirror,我们说,“获取当前的最大ID。如果它是奇怪的,不要改变它,但如果它是偶数,则将它备份一个。”

然后在'evenMirror'上做同样的事情,除了检查max id是否是偶数而不是奇数。

举个例子,拿这个数据:

oddMirror
1,"one"
3,"three"
5,"five"

evenMirror
2,"two"
4,"four"
6,"six"
8,"eight"

(注意evenMirror有更多行)

同步后,每个表都如下所示:

oddMirror
1,"one"
2,"two"
3,"three"
4,"four"
5,"five"
6,"six"
8,"eight"
--evenMirror looks the same as this now

通过以上查询运行:

MAX(id)上的{p> oddMirror88 % 2 = 0,因此设置@newSeed = 8 - 1 = 7,意味着oddMirror中的下一行将获得id = 9

MAX(id)上的

evenMirror也是8,但查询略有不同。 8 % x = 0设置@newSeed = 8,意味着'evenMirror will get id = 10`中的下一行

在这种情况下会跳过

id = 7,但我猜这并不是一件值得关注的问题。

如果您再查询:

INSERT INTO oddMirror (data) VALUE ("data")
GO

INSERT INTO evenMirror (data) VALUE ("otherData")
GO

表格如下所示:

oddMirror
1,"one"
2,"two"
3,"three"
4,"four"
5,"five"
6,"six"
8,"eight"
9,"data"

evenMirror
1,"one"
2,"two"
3,"three"
4,"four"
5,"five"
6,"six"
8,"eight"
10,"otherData"

理论上可以通过更改您采用的模数来扩展以适应更多数量的服务器,并为每种可能性在WHEN语句中添加额外的CASE,尽管这样做肯定会很麻烦。但是,我们已经知道正确的解决方案(GUID)在这里不可用,如果您正在阅读这篇文章,那么下一个最佳解决方案(SEQUENCE)可能无法使用,因此无论我们提出什么,不可避免地会变得很麻烦。

此方法的最大缺点是必须锁定表,直到同步过程完成。如果在完成同步并重新输入ID之前写入,则几乎肯定会发生冲突。如果这些表不是经常写的,或者你已经锁定它们进行同步,或者你的日常周期中有一个重要的“死”点(比如早上3-4点或其他什么),这可以在没有严重中断的情况下完成,这可能不是什么大不了的事,但只有你知道这是多么可行。

所以,你的设置可能会或者可能不会使这成为可能,但是我今晚在我的沙盒数据库中已经玩了很多这个,并且它似乎很好地确保在一个数据库中新的id总是奇怪的总是在另一个。

答案 1 :(得分:3)

这是一个非常简单的解决方案,但它只适用于两台服务器。它不能轻易扩展到更多服务器。

关于它的好处是它没有使用CHECKIDENT来重新设置表格,您无需担心同时运行事务以获得准确的MAX ID加入CHECKIDENT

此外,MSDN警告identity property on a column does not guarantee the following:

  

服务器重启或其他故障后的连续值 - SQL Server   可能因性能原因和某些原因而缓存身份值   在数据库故障或服务器期间,分配的值可能会丢失   重新开始。这可能导致插入时身份值的缺口。如果   差距是不可接受的,然后应用程序应该使用它自己的   生成键值的机制。使用序列生成器   NOCACHE选项可以限制从不的事务的差距   提交。

如果您选择基于使用CHECKIDENT转发身份的解决方案,则最好仔细检查它是否在这种情况下正常运行。

另外,要运行CHECKIDENT,您可能需要特定的permissions

  

调用者必须拥有该表,或者是sysadmin固定服务器的成员   角色,db_owner固定数据库角色或db_ddladmin已修复   数据库角色。

<强>解决方案

我的主要想法是在第一台服务器上使用IDENTITY(1,1),在第二台服务器上使用IDENTITY(-1,-1)

。而不是试图使IDs奇怪,甚至是积极和消极。

这是一个脚本,证明它可以按预期工作而无需任何额外的工作。

-- Sample data
CREATE TABLE #T1 (ID bigint IDENTITY(1,1), V1 int);
CREATE TABLE #T2 (ID bigint IDENTITY(-1,-1), V2 int);

INSERT INTO #T1 VALUES (11);
INSERT INTO #T1 VALUES (12);
INSERT INTO #T1 VALUES (13);
INSERT INTO #T1 VALUES (14);

INSERT INTO #T2 VALUES (21);
INSERT INTO #T2 VALUES (22);
INSERT INTO #T2 VALUES (23);

SELECT * FROM #T1;
SELECT * FROM #T2;

我们从表中的示例数据开始:

#T1
ID  V1
1   11
2   12
3   13
4   14

#T2 
ID  V2
-1  21
-2  22
-3  23

执行同步

-- Insert into T1 new values from T2
SET IDENTITY_INSERT #T1 ON;

MERGE INTO #T1 AS Dst
USING
(
    SELECT ID, V2
    FROM #T2
) AS Src
ON Dst.ID = Src.ID
WHEN NOT MATCHED BY TARGET
THEN INSERT (ID, V1)
VALUES (Src.ID, Src.V2);

SET IDENTITY_INSERT #T1 OFF;

-- Insert into T2 new values from T1
SET IDENTITY_INSERT #T2 ON;

MERGE INTO #T2 AS Dst
USING
(
    SELECT ID, V1
    FROM #T1
) AS Src
ON Dst.ID = Src.ID
WHEN NOT MATCHED BY TARGET
THEN INSERT (ID, V2)
VALUES (Src.ID, Src.V1);

SET IDENTITY_INSERT #T2 OFF;

SELECT * FROM #T1;
SELECT * FROM #T2;

同步结果 - 两个相同的表格

#T1
ID  V1
1   11
2   12
3   13
4   14
-1  21
-2  22
-3  23


#T2
ID  V2
-1  21
-2  22
-3  23
1   11
2   12
3   13
4   14

插入更多数据以检查同步后身份如何工作

-- Insert more data into T1 and T2
INSERT INTO #T1 VALUES (15);
INSERT INTO #T1 VALUES (16);

INSERT INTO #T2 VALUES (24);
INSERT INTO #T2 VALUES (25);
INSERT INTO #T2 VALUES (26);

SELECT * FROM #T1;
SELECT * FROM #T2;

-- Clean up
DROP TABLE #T1;
DROP TABLE #T2;

同步后生成的身份

#T1
ID  V1
1   11
2   12
3   13
4   14
-1  21
-2  22
-3  23
5   15
6   16

#T2
ID  V2
-1  21
-2  22
-3  23
1   11
2   12
3   13
4   14
-4  24
-5  25
-6  26

您可以看到T1中的新身份仍然是积极的,T2中的新身份仍然是负面的。

答案 2 :(得分:1)

您有两个用于插入目标表的源表:
所以我建议你这样做:

  • [pkId]:在目标表中将标识字段作为PK。
  • [src]:添加integer -or any other as you want- field and update it from 1st. source data by 1 and for the 2nd one by 2`。
  • [Id]:您还有一个来自消息来源的字段。
  • [nId]:添加bigint的{​​{1}}字段。

现在使用此查询在null字段中显示您的枚举ID:

nId
  

要在任何插入后运行此查询,您可以使用触发器。

我认为使用此解决方案,您可以获得所需的所有数据。

修改 一些结果:

Update <table> 
Set nId = isnull((select count(ti.*) from <table> as ti where ti.pkId < <table>.pkId), 0) + 1

答案 3 :(得分:1)

一个选项,一个服务器设置为identity(1,1),另一个设置为identity(-1,-1)。身份不会重叠,将数据从一台服务器复制到另一台服务器不会影响“下一个”ID或重新播种。

当然,不适用于两台以上的服务器。

答案 4 :(得分:0)

我认为偶数/奇数方法使得这很难。此外,当您向每个节点添加行时,您将遇到页面拆分问题,尤其是当您的PK是聚簇索引时。

这些表是使用点对点复制进行复制还是手动同步?如果涉及复制,页面拆分问题将会发挥作用。

为什么不为每个节点使用数字范围?节点1的1-X和节点#2的X + 1-Y?估计行量并将范围设置得如此之大,以免发生重叠。

BIGINT的例子:

节点1:1-200000000000(2000亿行) 节点2:200000000001-600000000000(4000亿行)

离开600000000001以备将来使用。警告,身份没有最大值,你需要手动报警。

要将标识值设置为正确的数字,请使用带有RESEED选项的DBCC CHECKIDENT。如果你与偶数/奇数场景结婚,这也会奏效。

这也有一个优点,即每次插入不会分页一次,特别是如果每​​个节点的活动不均衡。