我正在使用一组服务器,这些服务器都记录通常每天发生多次的事件,然后调用存储过程将这些记录复制到中央远程服务器上的匹配表。该存储过程的关键部分如下:
首先,因为事件需要几分钟,所以当它们被复制并且中央服务器中的某些记录在某些列中具有空值时,有时它们将无法完成。存储过程会更新上次发生的记录:
UPDATE r SET r.ToUpdateA = l.ToUpdateA, r.ToUpdateB = l.ToUpdateB
FROM LocalTable l INNER JOIN RemoteServer.RemoteDB.dbo.RemoteTable r
ON l.IdentifierA = r.IdentifierA AND l.IdentifierB = r.IdentifierB
WHERE r.ToUpdateB IS NULL AND l.ToUpdateB IS NOT NULL;
IdentifierA
和IdentifierB
都是识别给定记录所必需的;第一个标识它来自哪个服务器。
第二个是更新本身,识别远程表上不是的本地表上的记录并插入它们:
INSERT INTO RemoteServer.RemoteDB.dbo.RemoteTable (A, B, C...)
SELECT l.A, l.B, l.C...
FROM LocalTable l LEFT OUTER JOIN RemoteServer.RemoteDB.dbo.RemoteTable r
ON l.IdentifierA = r.IdentifierA AND l.IdentifierB = r.IdentifierB
WHERE r.uid IS NULL;
随着中央远程表的增长,这些连接将花费太长时间,特别是在较大的服务器上。估计的执行计划表明大部分工作是在UPDATE
的内部联接(与r.ToUpdateB IS NULL
部分相关)的远程扫描中完成的,并且是{{的远程查询1}}左外连接(从整个INSERT
中选择三列)。我可以想到三种类型的解决方案:
RemoteTable
列。指示是否已复制给定记录,并且具有" hub"除掉重复本身。BIT
更改为INNER JOIN
,但如果我正确地解释修改后的执行计划,则会花费数量级更长。#3可行吗?如果是这样,怎么样?
答案 0 :(得分:1)
诀窍是在桌子所在的服务器上进行DML工作。为了做到这一点,你:
NVARCHAR(MAX)
变量中,因为XML不是链接服务器调用的有效数据类型)我将此方法详细解释为两个答案:
上述方法涉及如何更快地传输数据,但没有解决在识别首先要移动的数据时可以进行的改进。
扫描目标表,即使它只是在同一实例上的不同数据库中,每次确定丢失的记录都非常昂贵,因为行计数会增加。通过将新记录转储到队列表中可以避免这种昂贵。此队列表仅包含需要插入和可能更新的记录。一旦您知道记录已远程同步,就会删除这些记录。这类似于问题中的选项#3,但在单个查询中没有这样做,因为无法识别" new"扫描目标表之外的记录(简单但不会缩放)或在它们进入时捕获它们(稍微努力但是可以很好地扩展)。
队列表可以是:
在任何一种情况下,你都可以采取以下措施:
创建队列表
CREATE TABLE RemoteTableQueue
(
RemoteTableQueueID INT NOT NULL IDENTITY(-2140000000, 1)
CONSTRAINT [PK_RemoteTableQueue] PRIMARY KEY,
IdentifierA DATATYPE NOT NULL,
IdentifierB DATATYPE NOT NULL,
StatusID TINYINT NOT NULL,
);
创建一个AFTER INSERT触发器
INSERT INTO RemoteTableQueue (IdentifierA, IdentifierB, StatusID)
SELECT IdentifierA, IdentifierB, 1
FROM INSERTED;
更新您的ETL过程(假设这是单线程的)
CREATE TABLE #TempUpdate
(
IdentifierA DATATYPE NOT NULL,
IdentifierB DATATYPE NOT NULL,
ToUpdateA DATATYPE NOT NULL,
ToUpdateB DATATYPE NOT NULL
);
BEGIN TRAN;
INSERT INTO #TempUpdate (IdentifierA, IdentifierB, ToUpdateA, ToUpdateB)
SELECT lt.IdentifierA, lt.IdentifierB, lt.ToUpdateA, lt.ToUpdateB
FROM LocalTable lt
INNER JOIN RemoteTableQueue rtq
ON lt.IdentifierA = rtq.IdentifierA
AND lt.IdentifierB = rtq.IdentifierB
WHERE rtq.StatusID = 2 -- rows eligible for UPDATE
AND lt.ToUpdateB IS NOT NULL;
DECLARE @UpdateData NVARCHAR(MAX);
SET @UpdateData = (
SELECT *
FROM #TempUpdate
FOR XML ...);
EXEC RemoteServer.RemoteDB.dbo.UpdateProc @UpdateData;
DELETE rtq
FROM RemoteTableQueue rtq
INNER JOIN #TempUpdate tmp
ON tmp.IdentifierA = rtq.IdentifierA
AND tmp.IdentifierB = rtq.IdentifierB;
TRUNCATE TABLE #TempUpdate;
INSERT INTO #TempUpdate (IdentifierA, IdentifierB, ToUpdateA, ToUpdateB)
SELECT lt.IdentifierA, lt.IdentifierB, lt.ToUpdateA, lt.ToUpdateB
FROM LocalTable lt
INNER JOIN RemoteTableQueue rtq
ON lt.IdentifierA = rtq.IdentifierA
AND lt.IdentifierB = rtq.IdentifierB
WHERE rtq.StatusID = 1 -- rows to INSERT;
SET @UpdateData = (
SELECT lt.*
FROM LocalTable lt
INNER JOIN #TempUpdate tmp
ON tmp.IdentifierA = rtq.IdentifierA
AND tmp.IdentifierB = rtq.IdentifierB
FOR XML ...);
EXEC RemoteServer.RemoteDB.dbo.InsertProc @UpdateData;
-- no need to check for changed value later if it already has it now
DELETE rtq
FROM RemoteTableQueue rtq
INNER JOIN #TempUpdate tmp
ON tmp.IdentifierA = rtq.IdentifierA
AND tmp.IdentifierB = rtq.IdentifierB
WHERE tmp.ToUpdateB IS NOT NULL;
-- we know these records will need to be checked later since they are NULL
UPDATE rtq
SET rtq.StatusID = 2 -- rows eligible for UPDATE
FROM RemoteTableQueue rtq
INNER JOIN #TempUpdate tmp
ON tmp.IdentifierA = rtq.IdentifierA
AND tmp.IdentifierB = rtq.IdentifierB
WHERE tmp.ToUpdateB IS NULL;
COMMIT;
其他步骤
将TRY / CATCH逻辑添加到ETL过程以正确处理ROLLBACK
更新远程INSERT和UPDATE过程以将传入数据批量处理到目标表中(循环通过传入XML填充的临时表,一次处理1000行直到完成)。
如果"讲话"之间存在太多争用服务器同时报告,在远程服务器上创建一个传入的队列表,只需插入传入的XML数据,无需额外的逻辑。这是一个非常干净和快速的操作。然后在远程服务器上创建本地作业以检查每隔几分钟,如果传入的队列表中存在行,则将它们处理到目标表中。这将源服务器/表与目标服务器/表之间的事务分开,从而减少争用。
[RemoteTableQueueID]
字段存在,以防您将ETL模型更改为全天每3-10分钟运行一次,抓取要处理的TOP (@BatchSize)
行,在这种情况下,您将想要ORDER BY [RemoteTableQueueID] ASC