kry*_*tah 7 sql-server transaction-log sql-server-2016
问题:在 SQL Server 2016 中,将列更新为相同的值(例如将列从'john'to更新'john')是否会产生与将列更新为不同值时相同数量的事务日志?阅读下文了解更多详情。
我们有几个 SQL 代理作业按计划运行。这些作业从源表(复制数据、链接服务器)中选择数据,对其进行转换,然后相应地插入/更新/删除本地目标表的行。
在试图找到实现这一目标的最佳方式时,我们经历了各种策略。我们试过了
现在,我只是一个初级 DBD,我对事务日志如何工作的理解非常有限。话虽如此,我的前辈已经得出结论,我们不能使用 MERGE 或 UPDATE 语句,其中所有列都在同一语句中处理,因为它会创建过多的日志记录。对此的论点是,当您UPDATE在 SQL Server 中执行 -语句时,当您设置列值并且新值等于旧值时,它仍会在事务日志中标记为更新。当您执行大量无意义的 SET 操作时,这显然会变得昂贵。
在以下示例中,我们使用源表中的值更新目标表的first_name和last_name,由 连接id。
-- create source- and target-table
CREATE TABLE [#tgt] (
[id] Int PRIMARY KEY,
[first_name] NVarchar(10),
[last_name] NVarchar(10)
)
CREATE TABLE [#src] (
[id] Int PRIMARY KEY,
[first_name] NVarchar(10),
[last_name] NVarchar(10)
)
-- fill some dummy-data
INSERT INTO [#tgt]([id], [first_name], [last_name])VALUES(1, 'john', 'lennon')
INSERT INTO [#src]([id], [first_name], [last_name])VALUES(1, 'john', 'cena')
-- update target-table with values from source-table
UPDATE
[T]
SET
[T].[first_name] = [S].[first_name],
[T].[last_name] = [S].[last_name]
FROM
[#tgt] AS [T]
JOIN [#src] AS [S] ON [S].[id] = [T].[id]
DROP TABLE [#tgt]
DROP TABLE [#src]
Run Code Online (Sandbox Code Playgroud)
此示例不检查是否有任何值实际上已更改。如果我们NULL暂时忽略-checking 和 sane fallbacks,可以通过以下方式之一进行检查:
-- Example #1: updates all rows where first-name or last-name has changed
UPDATE ..
SET ..
FROM ..
WHERE [T].[first_name] <> [S].[first_name] OR [T].[last_name] <> [S].[last_name]
-- Example #2: updates all rows, sets target-value to source-value if value has changed
UPDATE ..
SET
ISNULL(NULLIF([S].[first_name], [T].[first_name]), [T].[first_name]),
ISNULL(NULLIF([S].[last_name], [T].[last_name]), [T].[last_name])
FROM ..
Run Code Online (Sandbox Code Playgroud)
在示例#1 中,SET 操作将更新所有列,即使只有 1 列发生了变化。
在示例#2 中,SET 操作将更新所有行的所有列,如果值不变,则回退到旧值。
在这两个示例中,所有列都受到 SET 操作的影响,根据我的前辈的说法,这会在频繁执行时产生不必要/有问题的事务日志量。
这同样适用于 -MERGE语句。即使您检查匹配的行是否有更改,更新也会命中所有列。
MERGE [#tgt] AS tgt
USING [#src] AS src
ON (tgt.id = src.id)
WHEN MATCHED AND ([tgt].[first_name] <> [src].[first_name] OR [tgt].[last_name] <> [src].[last_name])
THEN UPDATE SET
[tgt].[first_name] = [src].[first_name],
[tgt].[last_name] = [src].[last_name];
Run Code Online (Sandbox Code Playgroud)
那么我们该怎么办?对我们希望更新的每一列使用单个 UPDATE 语句。在这种情况下:
-- first_name
UPDATE [T]
SET [T].[first_name] = [S].[first_name]
FROM
[#tgt] AS [T]
JOIN [#src] AS [S] ON [S].[id] = [T].[id]
WHERE [T].[first_name] <> [S].[first_name]
-- last_name
UPDATE [T]
SET [T].[last_name] = [S].[last_name]
FROM
[#tgt] AS [T]
JOIN [#src] AS [S] ON [S].[id] = [T].[id]
WHERE [T].[last_name] <> [S].[last_name]
Run Code Online (Sandbox Code Playgroud)
现在,这种方法有几个缺点:
感觉必须有一个更聪明的方法来解决这个问题,我希望对这篇文章中的陈述进行任何澄清和更正。就像前面提到的那样,我只是尽力理解为什么必须这样。
对冗长的帖子深表歉意,并提前致谢。
我的前辈得出的结论是,我们不能使用 MERGE 或 UPDATE 语句,其中所有列都在同一语句中处理,因为它会创建过多的日志记录。
嗯,他们得出这样的结论真是太好了。但是,他们是否提供了任何证据或测试脚本来显示这种行为?我有兴趣看到这样的测试;-)
这样做的理由是,当您在 SQL Server 中执行 UPDATE 语句时,当您设置列值并且新值等于旧值时,它仍会在事务日志中标记为更新。
这是一些知识会产生误导的情况之一。是的,将列更新为完全相同的值被视为更新,就像对通过函数更新的列进行测试一样,只要该列位于 SET 语句中,无论值是否更改,UPDATE()都会返回。1
但是,缺少的部分是:
如果没有任何列的值发生变化,则该行实际上并未更新。如果根本没有更新任何行,则唯一的事务日志活动是两条记录:一条LOP_BEGIN_XACT标记事务的开始,一条LOP_COMMIT_XACT标记事务的结束。但没有实际的数据页或索引页被修改。这假设“受影响的行”> 0,但实际上没有任何变化。
如果所有行都被过滤掉,从而没有行被更新(即“受影响的行”= 0),则不存在Tran Log 活动。
如果任何列的值发生变化,则设置为其现有值的附加列在事务日志中看起来与未指定值未变化的列相同。
每个查询(除非在显式事务中与其他查询分组)都是其自己的事务,并且事务日志中的每个事务至少有 2 个条目:一个用于 BEGIN,一个用于 COMMIT 或 ABORT。
因此:
就 Tran Log 而言,“示例 1”和“示例 2”这两个选项几乎相同。如果至少有一行要更新,那么它们应该是相同的。但是,如果没有任何行的任何列的值发生变化,则“示例 1”(带有该WHERE子句)将导致较少的 Tran 日志活动,因为不会有任何条目,而在“示例 2”中(所有行“已更新”)将有 BEGIN 和 COMMIT 条目。因此,我建议使用该WHERE子句,因为它明确表达了您的意图,并且会导致 Tran Log 活动稍微减少。
遵循“前辈”的建议肯定会导致更多的Tran Log 活动,更不用说性能下降了。为什么?因为:
UPDATE语句包装到单个显式事务中以减少不一致以及额外的 BEGIN / END 日志条目,在某些情况下您仍然会多次更新行,并且每次修改都会被记录。UPDATE语句重新扫描它们仍然需要更多时间。确切地知道并亲眼目睹总是比依赖猜测或别人的说法更好。为此,您应该测试您的各种选项,包括您的前辈建议的两个单独的更新,并且在每次测试后,通过以下方式检查:
SELECT *
FROM sys.fn_dblog(NULL, NULL) tl
ORDER BY tl.[Transaction ID] DESC; -- most recent first
Run Code Online (Sandbox Code Playgroud)PS 我在 SQL Server 2012 (SP3) Developer Edition 上进行了初步测试。然后,我在 SQL Server 2016 (RTM) Express Edition 上再次进行测试,行为是相同的。
PPS 从逻辑上讲,[T].[first_name] = ISNULL(NULLIF([S].[first_name], [T].[first_name]), [T].[first_name])与 没有什么不同[T].[first_name] = [S].[first_name],只是包含了更多功能。但如果两列都是'A',那么使用'A'同一个表中的 更新它与'A'使用另一个表中的 更新是完全相同的操作。
PPPS 当检查字符串字段中的任何差异时,您确实需要使用二进制排序规则,否则可能会发生仅大小写(或宽度或组合字符等)的更改,以便列的排序规则将比较值是否相同。我确实意识到您提到这些是简化的示例,但我只是确保这方面不被忽视:-)。因此:
WHERE [T].[first_name] <> [S].[first_name] OR [T].[last_name] <> [S].[last_name]
Run Code Online (Sandbox Code Playgroud)
变成:
WHERE [T].[first_name] <> [S].[first_name] COLLATE Latin1_General_100_BIN2
OR [T].[last_name] <> [S].[last_name] COLLATE Latin1_General_100_BIN2
Run Code Online (Sandbox Code Playgroud)
和:
ISNULL(NULLIF([S].[first_name], [T].[first_name]), [T].[first_name]),
ISNULL(NULLIF([S].[last_name], [T].[last_name]), [T].[last_name])
Run Code Online (Sandbox Code Playgroud)
变成:
ISNULL(NULLIF([S].[first_name] COLLATE Latin1_General_100_BIN2, [T].[first_name]), [T].[first_name]),
ISNULL(NULLIF([S].[last_name] COLLATE Latin1_General_100_BIN2, [T].[last_name]), [T].[last_name])
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
296 次 |
| 最近记录: |